目次
方法
- 縦スクロールする場合に対してのみ有効にする.
- 現在のFloatingActionButtonのアニメーション状態を4つ, スクロールの状態を5つで管理する.
- スクロール時のアニメーションを設定する
- 停止時のアニメーションを設定する
- FloatingActionButtonのbehaviorを設定する
コード全文
参考にしたサイト
方法
- 縦スクロールする場合に対してのみ有効にする.
- 現在のFloatingActionButtonのアニメーション状態を4つ, スクロールの状態を5つで管理する.
- スクロール時のアニメーションを設定する 下にスクロールまたは, フリック(以降, スクロールに含める)したときに, FloatingActionButtonを消す. また, FloatingActionButtonが消失している場合, 上にスクロールしたとき再表示する. 4行目から7行目, 23行目から26行目では, 一番下までスクロールしたらFloatingActionButtonが消えないようにしている(下のコードでは, 見やすさのためにスクロール状態の管理を省いている).
- 停止時のアニメーションを設定する FloatingActionButtonが消失している場合, 放置していると再表示する.
- FloatingActionButtonのbehaviorを設定する MainActivityのOnCreateで次のようにすればよい.
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); } |
FloatingActionButtonが消えている間は, 押しても反応しないようにする. ここのアニメーションはListenerに, AnimatorListenerAdapterか Animator.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; |
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 ; } |
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が呼ばれたときに, stateがSTATE.VANISHINGのままで, 該当箇所を抜くと, FloatingActionButtonが消えたままとなる(下のgif参照)ので, これを回避するために, そうなったときに, 400(=delayDuration)ms後, FloatingActionButtonを再表示させている.
放置しているときに, FloatingActionButtonを再表示させたくない場合は, onStopNestedScrollをオーバーライドしなければよい.
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); } } |