ラベル android の投稿を表示しています。 すべての投稿を表示
ラベル android の投稿を表示しています。 すべての投稿を表示

FloatingActionButtonをスクロール中に消す

NestedScrollViewを下にスクロールしている間は, FloatingActionButtonを表示させないようにする. 少し変えれば, RecyclerView等の他のScrollViewにも使えそう(未確認). 下に実際の挙動を示した.



目次


方法

  1. 縦スクロールする場合に対してのみ有効にする.
  2. 現在のFloatingActionButtonのアニメーション状態を4つ, スクロールの状態を5つで管理する.
  3. スクロール時のアニメーションを設定する
  4. 停止時のアニメーションを設定する
  5. FloatingActionButtonのbehaviorを設定する

コード全文

参考にしたサイト


方法


  1. 縦スクロールする場合に対してのみ有効にする.


  2. 現在のFloatingActionButtonのアニメーション状態を4つ, スクロールの状態を5つで管理する.

  3. FloatingActionButtonが消えている間は, 押しても反応しないようにする. ここのアニメーションはListenerに, AnimatorListenerAdapterAnimator.AnimatorListenerのインスタンスを代入できればよく, もっと複雑にできるかもしれない. また, アニメーションの時間はそこまで長くしないほうが良い.

    現在のスクロール状態(scrollingState)と, (状態が変わる)1つ前のスクロール状態(preScrollingState)を保持しておく. 実際の状態管理については, 最後のコード全文を参照

  4. スクロール時のアニメーションを設定する
  5. 下にスクロールまたは, フリック(以降, スクロールに含める)したときに, FloatingActionButtonを消す. また, FloatingActionButtonが消失している場合, 上にスクロールしたとき再表示する. 4行目から7行目, 23行目から26行目では, 一番下までスクロールしたFloatingActionButtonが消えないようにしている(下のコードでは, 見やすさのためにスクロール状態の管理を省いている).

  6. 停止時のアニメーションを設定する
  7. FloatingActionButtonが消失している場合, 放置していると再表示する.
    上のコードで, 9行目から13行目までは, 以下のような挙動を抑えるために記述している.


    すなわち, スクロール(フリックは含めない)後, フリックすると, イベントとしては, onNestedPreScroll -> onStopNestedScroll -> onNestedPreFlingScroll (コードに関係しないイベントは除く)と, onStopNestedScrollが途中で呼ばれてしまい, 何もしないとここで, 一回FloatingActionButtonが消える.

    また, 15行目から22行目では, 200(=duration)ms未満の下スクロールを行うと, onStopNestedScrollが呼ばれたときに, stateSTATE.VANISHINGのままで, 該当箇所を抜くと, FloatingActionButtonが消えたままとなる(下のgif参照)ので, これを回避するために, そうなったときに, 400(=delayDuration)ms後, FloatingActionButtonを再表示させている.

    放置しているときに, FloatingActionButtonを再表示させたくない場合は, onStopNestedScrollをオーバーライドしなければよい.

  8. FloatingActionButtonのbehaviorを設定する
  9. MainActivityOnCreateで次のようにすればよい.


コード全文


コード全文を掲載する

VanishFloatingActionButtonBehavior.java

MainActivity.java


参考にしたサイト




NestedScrollViewで最下部にスクロールしたかを判定する

まず, NestedScrollViewのサイズを取得する方法 :

よって, これとNestedScrollView.getScrollY()を比較すればよい:


参考にしたサイト



Toolbarの挙動をコードで変える

例えば, 最初の画面ではToolbarを動かさないようにしたいが,次の画面ではToolbarをcollapseさせたい時の方法

方法


android.support.design.widget.CollapsingToolbarLayoutapp:layout_scrollFlags属性をJavaコード中で変更する.
実際には、以下のように行う.
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;

...

CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
AppBarLayout.LayoutParams appBarLayoutParams = (AppBarLayout.LayoutParams)collapsingToolbarLayout.getLayoutParams();
appBarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
toolbarLayout.setLayoutParams(appBarLayoutParams);

setScrollFlagsメソッドに代入する定数は以下の通りで, 複数指定したい場合は, 上のコードのようにビット演算子OR|をはさんでやる.

定数 対応するapp:layout_scrollFlagsの値
SCROLL_FLAG_ENTER_ALWAYS enterAlways
SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED enterAlwaysCollapsed
SCROLL_FLAG_SCROLL scroll
SCROLL_FLAG_EXIT_UNTIL_COLLAPSED exitUntilCollapsed
SCROLL_FLAG_SNAP snap

上の定数は, android.support.design.widget.AppBarLayout.LayoutParamsのフィールドであるが, ToolBarの挙動については, こちらが詳しい.

実装例


FloatingActionButtonを押すことで, Toolbarの挙動をenterAlways <-> scroll|exitUntilCollapsedと相互に変える. 以下のような感じ.

styles.xml

ToolBarをつかうので, アプリケーションテーマとしてNoActionBarを採用している.
また, 11行目から13行目, 15行目から18行目は, ToolBarのタイトル文字がデフォルトでは黒色なので, 見やすい白色にするために追加している(後に, activity_main.xmlのCollaspingToolbarLayoutapp:collapsedTitleTextAppearance, app:expandedTitleTextAppearanceで指定してやる).

activity_main.xml

styles.xmlで述べたが, 17行目と19行目でToolBarのタイトルのテキストスタイルを変更している
54行目に出てくる, FloatingActionButtonに使うdrawableリソースはここには載せないが, 適当な画像を使えばよい.

MainActivity.java

ハイライトしたところで, Toolbarの挙動を変更している.
基本的に, statusで現在の状態を管理している. 詳しい説明は, コード中のコメントを参照.

実装例の最初に挙げたgifを見て気づいた方もいるかもしれないが, このコードの問題として, AppBarLayouheightwrap_contentか, あまり大きくない値を指定するとタイトルが表示されないことが挙げられる(Toolbarapp:titleを設定してないことは関係ない!). この問題は, CollapsingToolbarLayoutを使わないようにすれば回避できる:

しかし, この方法では, Toolbarの挙動をJavaコード中では変えられないっぽく, 自作のカスタムViewを作る等面倒なことをしないといけないみたい(? 現在模索中)

参考にしたサイト




VisibilityAwareImageButton.setVisibility can only be called from within the same library group

sdkのバージョンを28 beta1に変えてみると、android.support.design.widget.FloatingActionButton.setVisiblityメソッドを使っていたときに次のようなエラーが出た.
VisibilityAwareImageButton.setVisibility can only be called from within the same library group (groupId=com.android.support)

build.gradle(Module: app)でこのエラーに関すると思われるものは以下:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        ...
        targetSdkVersion 28
        ...
    }
    ...
}

dependencies {
    implementation 'com.android.support:design:28.0.0-beta01'
    ...
}

雑に言えば、FloatingActionButtonvisibilityを直接変えてはいけないようである. setVisiblityの代わりにshowメソッドかhideメソッドを使えば、エラーを回避できる.
floatingActionButton.show();
floatingActionButton.hide();

ただし、この場合、FloationActionButtonvisibilityinvisibleにすることは工夫をしないとできないようである.

参考にしたサイト



Parcelabeと実行時エラー

なかなか直せなかったバグが、Parcelable関連を見直すことで直ったので、その時に気づいた注意点を残しておく.

Parcelableを実装するときの注意


例えば、Stringの配列をreadする際は、readStringArrayではなく、createStringArrayを使う :
public class MyData implements Parcelable {
    
    private String mStringArray[];
    
    public String[] getStringArray() {
        return mStringArray;
    }
    
    public void setStringArray(String stringArray[]) {
        this.mStringArray = stringArray;
    }
    
    private MyData(Parcel in) {
        mStringArray = in.createStringArray();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStringArray(mStringArray);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<MyData> CREATOR = new Creator<MyData>() {
        @Override
        public MyData createFromParcel(Parcel in) {
            return new MyData(in);
        }

        @Override
        public MyData[] newArray(int size) {
            return new MyData[size];
        }
    };
}

もし、readStringArray使った場合、画面回転程度の読み込みではエラーは出ないが、「アクティビティ破棄する(アクティビティを保持しない)」設定をオンにした状態で再起動する(ホームボタン→アプリ起動)と、私の環境では次のエラーが出た.
Caused by: java.lang.NullPointerException: Attempt to get length of null array
...
at MainActivity.onCreate(MainActivity.java:xxx)


Parcelableでデータを受渡しするときの注意


特に、onRestoreInstanceStateonSavedInstanceStateでアクティビティの復元を行う際、putParcelableArraygetParcelableArrayなどで、Parcelableの配列を使わないほうが良い.
...
private MyData myData[];
...
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    ...
    myData = (MyData[])savedInstanceState.getParcelableArray(Key1);
    ...
}

上のようにキャストしないとコンパイルエラーが発生するが、アクティビティが破棄された後(「アクティビティを破棄する」設定をオンにしてアプリケーションを終了する)では、Bundle内で型の情報が保持されないようで、次のようなエラーが出た.
Caused by: java.lang.ClassCastException: android.os.Parcelable[] cannot be cast to com.foo.example.MyData[]
        at com.foo.example.MainActivity.onViewStateRestored(MainActivity.java:xxx)

代わりに、putParcelableArrayListgetParcelableArrayListを使うことで、このエラーを回避できた.
...
private ArrayList<MyData> myData;
...
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    ...
    myData = savedInstanceState.getParcelableArrayList(Key1);
    ...
}


参考にしたサイト



CheckBoxのチェックボックスを右側に表示させる。

題名の日本語が少し怪しい感じがするが、CheckBoxはデフォルトでは次のようになる.


これを次のようにするのが当記事の目的である.


API 17以上の場合


xmlの方で、CheckBoxlayoutDirction属性をrtlにしてやるだけ.
android:layoutDirection="rtl"


API 17未満の場合


同じくxmlの方で、CheckBoxに、次の2つを加える.
android:button="@null"
android:drawableEnd="?android:attr/listChoiceIndicatorMultiple"

ただ、この方法だとCheckBoxをクリックしたとき、チェックボックスではなくCheckBoxの中心がハイライトされるので注意(下の画像を参照).



参考にしたサイト



ConstraintLayoutの制約をJavaで設定する

ConstraintLayout#LayoutParamsを使う. まず、ConstraintLayout上のViewを取得する.
Button button = findViewById(R.id.button);

もしくは、追加したい適当なオブジェクトを作成し、ConstraintLayoutにViewを追加する.
Button button = new Button(this);
constrintLayout.addView(button)

※先にaddViewしないと、次のlayoutParamsにnullが入る(エラーが発生する)ので注意.

次に、上のViewのLayoutParamsを取得し、適当に設定する. 親View(?)と連結したいときは、PARENT_IDを代入する. ほかのViewと連結したいときは、View#getIdメソッドを使う. LayoutParamsのフィールドについては、xmlのものとほとんど同じなので、そちら(例えば、Android Studioのデザインビューとか)を見るとわかりやすいと思う.
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams)button.getLayoutParams();
layoutParams.startToEnd = anotherView.getId();
layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
layoutParams.verticalBias = (float) 0.1;
layoutParams.horizontalBias = (float) 0.1;
button1.setLayoutParams(layoutParams);

最後に、上の変更を適用する.
button.setLayoutParams(layoutParams);

他にもこちらと同様に、ViewのWidthやHeight、Marginを変更できたりする(MarginLinearLayoutにキャストしなくてもよい、次の適用例を参照).

適用例


ConstraintLayout上に2つのボタンを表示させる.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayoutParamsLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.blogspot.ko-ika.constraintlayoutparams.MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button1" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button2" />
</android.support.constraint.ConstraintLayout>

activity_main.xml
package jp.blogspot.ko-ika.constraintlayoutparams;

import android.support.constraint.ConstraintLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button1 = findViewById(R.id.button1);
        Button button2 = findViewById(R.id.button2);

        ConstraintLayout constraintLayout = findViewById(R.id.constraintLayoutParamsLayout);

        // Button1の設定
        ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams)button1.getLayoutParams();
        layoutParams.topMargin = 8;
        layoutParams.rightMargin = 16;
        layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.verticalBias = (float) 0.1;
        layoutParams.horizontalBias = (float) 0.1;
        button1.setLayoutParams(layoutParams);

        // Button2の設定
        layoutParams = (ConstraintLayout.LayoutParams)button2.getLayoutParams();
        layoutParams.leftMargin = 16;
        layoutParams.startToEnd = button1.getId();
        layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.verticalBias = (float)0.1;
        layoutParams.horizontalBias = 0;
        button2.setLayoutParams(layoutParams);
    }
}

実行例


さらに


ConstraintSetを使うと同じ様に設定できるらしい.


が、この方法だと、Biasが設定できない?

また、Viewが実際に配置される(?)まで、そのIDが取得できないみたいなので、注意が必要.
実際に、上の適用例を次のようにしてみる.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayoutParamsLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.blogspot.ko-ika.constraintlayoutparams.MainActivity">

</android.support.constraint.ConstraintLayout>

activity_main.xml
        

        Button button1 = new Button(this);
        Button button2 = new Button(this);
        ConstraintLayout constraintLayout = findViewById(R.id.constraintLayoutParamsLayout); 

        // Button1の設定
        constraintLayout.addView(button1);
        button1.setText("Button1"); 
        ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams)button1.getLayoutParams();
        layoutParams.topMargin = 8;
        layoutParams.rightMargin = 16;
        layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.verticalBias = (float) 0.1;
        layoutParams.horizontalBias = (float) 0.1;
        button1.setLayoutParams(layoutParams);

        // Button2の設定
        constraintLayout.addView(button2);
        button2.setText("Button2"); 
        layoutParams = (ConstraintLayout.LayoutParams)button2.getLayoutParams();
        layoutParams.leftMargin = 16;
        layoutParams.startToEnd = button1.getId();
        layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
        layoutParams.verticalBias = (float)0.1;
        layoutParams.horizontalBias = 0;
        button2.setLayoutParams(layoutParams);

変更箇所をマークアップした. すると、次のようになってしまう.

参考にしたサイト



removeViewが動かない

ViewGroup#removeViewメソッドの引数がnullでないのにnullだったため、動かなくなった.

まず、引数がnullかどうか確かめよう(引数をnullにしても例外が発生しないので気づかなかった).
以下の方法は、消去したいViewに変数を割り当てなくてもできる方法である

コードは以下のようである.
parentLinearLayout.removeView(childLinearLayout);

どうも描画中のアニメーションのせいで、動かないらしい.


上記サイトの方法だと、LinearLayoutを継承したクラスを作って、削除するときのアニメーションを表示させないようにしているようだ.

この方法だとかなりの手間がかかりそうだったので、代わりにViewGroup#removeViewAtメソッドを使ったところ、うまくいった.
parentLinearLayout.removeViewAt(index);

indexとしてchildLinearLayoutのポジションを指定する.

ちなみに、子Viewのindexを取得するには、ViewGroup#indexOfChildメソッドを使う.
int index = parentLinearLayout.indexOfChild(childLinearLayout);

参考にしたサイト


LinearLayoutのサイズをJavaコード中で変更する

タイトルにある通り

LinearLayoutでは、setHeight等が使えないので、android.view.ViewGroup.LayoutParamsを使う.
LinearLayout linearLayout = findViewById(R.id.LinearLayout);
ViewGroup.LayoutParams layoutParams  = linearLayout.getLayoutParams();

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = 200;

ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams)linearLayout.getLayoutParams();

marginLayoutParams.leftMargin = 8;
marginLayoutParams.rightMargin = 8;
marginLayoutParams.topMargin = 8;
marginLayoutParams.bottomMargin = 8;

linearLayout.setLayoutParams(marginLayoutParams);


単位は、pixelだそう. dip(dp)を使いたい場合は、例えば、5行目を
layoutParams.width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());


とする. (試してない.)

この方法は、ConstraintLayoutや、ImageButton等でも使えるっぽい.

参考にしたウェブサイト

Validates resource references inside Android XML files.

Android Studio 2.3でアンドロイドアプリを作っていた時のこと


マニフェストファイルにreceiverタグを追加する必要があって、実際に追加していると、android:name属性の値(?)に以下のようなエラーメッセージが表示された。

'com.example.hoge.testapp.MyadminReceiver' is not public
Validates resource references inside Android XML files.


いろいろと調べたけど、どうやっても解決しない。
一方、別に問題なくコンパイルは通る。

で、落ち着いてメッセージをよく見ると、一行目に"is not public"。
該当するクラス(この場合、MyadminReceiverクラス)のアクセスレベルがpublicじゃな
いってこと。

アクセスレベルは無指定だったので、classなんたらの前にpublic指定子をつけたらすぐ解決。


冷静になれば簡単だったのにてこずったので、また同じ過ちを犯さないようにここに記しておく。(きっと誰の役にも立たないと思うが)

FloatingActionButtonをスクロール中に消す

NestedScrollView を下にスクロールしている間は, FloatingActionButton を表示させないようにする. 少し変えれば, RecyclerView 等の他のScrollViewにも使えそう(未確認). 下に実際の挙動を示した. 目次 ...