FloatingActionButtonをスクロール中に消す

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



目次


方法

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

コード全文

参考にしたサイト


方法


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

  2. 1
    2
    3
    4
    5
    6
    7
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull FloatingActionButton child, @NonNull View directTargetChild,
                                       @NonNull View target, int axes, int type) {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
    }

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

  4. FloatingActionButtonが消えている間は, 押しても反応しないようにする. ここのアニメーションはListenerに, AnimatorListenerAdapterAnimator.AnimatorListenerのインスタンスを代入できればよく, もっと複雑にできるかもしれない. また, アニメーションの時間はそこまで長くしないほうが良い.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    private static final int duration = 200;
     
    ...
     
    // 消失アニメーション
    private void shrinkOut(final FloatingActionButton child) {
     
        child.animate()
                .setDuration(duration)
                .scaleX(0.0f)
                .scaleY(0.0f)
                .setInterpolator(new AccelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        state = STATE.VANISHED;
                        child.setEnabled(false);
                    }
     
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        state = STATE.VANISHING;
                        child.setEnabled(false);
                    }
                });
    }
     
    // 表示アニメーション
    private void expandIn(final FloatingActionButton child) {
     
        child.animate()
                .setDuration(duration)
                .scaleX(1.0f)
                .scaleY(1.0f)
                .setInterpolator(new AccelerateDecelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        state = STATE.APPEARED;
                        child.setEnabled(true);
                    }
     
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        state = STATE.APPEARING;
                        child.setEnabled(false);
                    }
                });
    }

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

  5. スクロール時のアニメーションを設定する
  6. 下にスクロールまたは, フリック(以降, スクロールに含める)したときに, FloatingActionButtonを消す. また, FloatingActionButtonが消失している場合, 上にスクロールしたとき再表示する. 4行目から7行目, 23行目から26行目では, 一番下までスクロールしたFloatingActionButtonが消えないようにしている(下のコードでは, 見やすさのためにスクロール状態の管理を省いている).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
     
        NestedScrollView nestedScrollView = (NestedScrollView) target;
        if (nestedScrollView.getChildAt(0).getMeasuredHeight() - nestedScrollView.getHeight() == nestedScrollView.getScrollY()) {
            return;
        }
     
        if (dy > 0) {
            if (state == STATE.APPEARED) {
                shrinkOut(child);
            }
        } else if (dy < 0){
            if (state == STATE.VANISHED) {
                expandIn(child);
            }
        }
    }
     
    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, float velocityX, float velocityY) {
     
        NestedScrollView nestedScrollView = (NestedScrollView) target;
        if (nestedScrollView.getChildAt(0).getMeasuredHeight() - nestedScrollView.getHeight() == nestedScrollView.getScrollY()) {
            return false;
        }
     
        if (velocityY > 0) {
            if (state == STATE.APPEARED) {
                shrinkOut(child);
            }
        } else if (velocityY < 0) {
            if (state == STATE.VANISHED){
                expandIn(child);
            }
        }
     
        return false;
    }

  7. 停止時のアニメーションを設定する
  8. FloatingActionButtonが消失している場合, 放置していると再表示する.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    private static final int delayDuration = 400;
     
    ...
     
    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull final FloatingActionButton child, @NonNull View target, int type) {
     
        if (state == STATE.VANISHED) {
            if (scrollingState != SCROLLING_STATE.FLUNG_DOWN) {
                expandIn(child);
            } else if (preScrollingState != SCROLLING_STATE.SCROLLED_DOWN){
                expandIn(child);
            }
        } else if (state == STATE.VANISHING) {
            coordinatorLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (state == STATE.VANISHED && scrollingState == SCROLLING_STATE.STOP) {
                        expandIn(child);
                    }
                }
            }, delayDuration);
        }
    }

    上のコードで, 9行目から13行目までは, 以下のような挙動を抑えるために記述している.


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

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

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

  9. FloatingActionButtonのbehaviorを設定する
  10. MainActivityOnCreateで次のようにすればよい.
    1
    2
    3
    CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
    p.setBehavior(new VanishFloatingActionButtonBehavior());
    floatingActionButton.setLayoutParams(p);


コード全文


コード全文を掲載する

VanishFloatingActionButtonBehavior.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.NestedScrollView;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
 
public class VanishFloatingActionButtonBehavior extends FloatingActionButton.Behavior {
 
    private static final int duration = 200;
    private static final int delayDuration = 400;
 
    private STATE state = STATE.APPEARED;
    private SCROLLING_STATE preScrollingState = SCROLLING_STATE.STOP, scrollingState = SCROLLING_STATE.STOP;
 
    @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton child, @NonNull View dependency) {
        return super.layoutDependsOn(parent, child, dependency) || dependency instanceof NestedScrollingChild;
    }
 
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull FloatingActionButton child, @NonNull View directTargetChild,
                                       @NonNull View target, int axes, int type) {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
    }
 
    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull final FloatingActionButton child, @NonNull View target, int type) {
 
        if (state == STATE.VANISHED) {
            if (scrollingState != SCROLLING_STATE.FLUNG_DOWN) {
                expandIn(child);
            } else if (preScrollingState != SCROLLING_STATE.SCROLLED_DOWN){
                expandIn(child);
            }
        } else if (state == STATE.VANISHING) {
            coordinatorLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (state == STATE.VANISHED && scrollingState == SCROLLING_STATE.STOP) {
                        expandIn(child);
                    }
                }
            }, delayDuration);
        }
 
        if (scrollingState != SCROLLING_STATE.STOP) {
            preScrollingState = scrollingState;
        }
        scrollingState = SCROLLING_STATE.STOP;
    }
 
    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
 
        NestedScrollView nestedScrollView = (NestedScrollView) target;
        if (nestedScrollView.getChildAt(0).getMeasuredHeight() - nestedScrollView.getHeight() == nestedScrollView.getScrollY()) {
            return;
        }
 
        if (dy > 0) {
            if (scrollingState != SCROLLING_STATE.SCROLLED_DOWN) {
                preScrollingState = scrollingState;
            }
            scrollingState = SCROLLING_STATE.SCROLLED_DOWN;
            if (state == STATE.APPEARED) {
                shrinkOut(child);
            }
        } else if (dy < 0){
            if (scrollingState != SCROLLING_STATE.SCROLLED_UP) {
                preScrollingState = scrollingState;
            }
            scrollingState = SCROLLING_STATE.SCROLLED_UP;
            if (state == STATE.VANISHED) {
                expandIn(child);
            }
        }
    }
 
    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, float velocityX, float velocityY) {
 
        NestedScrollView nestedScrollView = (NestedScrollView) target;
        if (nestedScrollView.getChildAt(0).getMeasuredHeight() - nestedScrollView.getHeight() == nestedScrollView.getScrollY()) {
            return false;
        }
 
        if (velocityY > 0) {
            if (scrollingState != SCROLLING_STATE.FLUNG_DOWN) {
                preScrollingState = scrollingState;
            }
            scrollingState = SCROLLING_STATE.FLUNG_DOWN;
            if (state == STATE.APPEARED) {
                shrinkOut(child);
            }
        } else if (velocityY < 0) {
            if (scrollingState != SCROLLING_STATE.FLUNG_UP) {
                preScrollingState = scrollingState;
            }
            scrollingState = SCROLLING_STATE.FLUNG_UP;
            if (state == STATE.VANISHED){
                expandIn(child);
            }
        }
 
        return false;
    }
 
 
 
    private void shrinkOut(final FloatingActionButton child) {
 
        child.animate()
                .setDuration(duration)
                .scaleX(0.0f)
                .scaleY(0.0f)
                .setInterpolator(new AccelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        state = STATE.VANISHED;
                        child.setEnabled(false);
                    }
 
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        state = STATE.VANISHING;
                        child.setEnabled(false);
                    }
                });
    }
 
    private void expandIn(final FloatingActionButton child) {
 
        child.animate()
                .setDuration(duration)
                .scaleX(1.0f)
                .scaleY(1.0f)
                .setInterpolator(new AccelerateDecelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        state = STATE.APPEARED;
                        child.setEnabled(true);
                    }
 
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        state = STATE.APPEARING;
                        child.setEnabled(false);
                    }
                });
    }
 
    private enum STATE {
        VANISHING,
        VANISHED,
        APPEARING,
        APPEARED
    }
 
    private enum SCROLLING_STATE {
        SCROLLED_DOWN,
        SCROLLED_UP,
        FLUNG_DOWN,
        FLUNG_UP,
        STOP
    }
}

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        FloatingActionButton floatingActionButton = findViewById(R.id.floatingActionButton);
        CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
        p.setBehavior(new VanishFloatingActionButtonBehavior());
        floatingActionButton.setLayoutParams(p);
    }
}


参考にしたサイト




0 件のコメント:

コメントを投稿

FloatingActionButtonをスクロール中に消す

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