67
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ActionBarをスクロールに合わせて表示したり非表示にしたりする

Posted at

MaterialDesignを適用したAndroidアプリのお手本として公開されているGoogle I/O 2014の公式アプリのソースコードから、ActionBarまわりのUIについて参考にしたい部分のコードを読んでみました。

ソースコードの取得

Google I/O 2014アプリのソースコード公開については、Android Developers Blogで紹介されていますが、日本語訳の記事もGoogle Developer Relations Japan Blogでも公開されています。

ソースコード自体はGitHubで公開されているので、cloneなりforkなりしてローカルにダウンロードして、AndroidStudioやElipseなどで見る方が見やすいです。

参考にしたソースファイル

Activityの共通処理の1つとして、リストのスクロールに合わせてActionBarを表示させたり非表示にしたりするアニメーションを行っているコードを中心に読みました。

今回参考にしたいコードは、com.google.samples.apps.iosched.ui.BaseActivity.javaの(L:1152〜)辺りにありました。

処理の流れ

ざっくり見ると、初期化〜スクロール量の計算〜UI操作という流れのようです。(当たり前かもしれませんが。)
メソッドで追いかけると、

  • L:1198 : protected void enableActionBarAutoHide(final ListView listView)
  • L:1152 : private void initActionBarAutoHide()
  • L:1168 : private void onMainContentScrolled(int currentY, int deltaY)
  • L:1188 : protected void autoShowOrHideActionBar(boolean show)
  • L:1434 : protected void onActionBarAutoShowOrHide(boolean shown)

という流れです。
途中、Android-Lとそれ以前との互換性を保つ処理(getLPreviewUtils().showHideActionBarIfPartOfDecor(show))もありました。

リスナーの設定

BaseActivity.java
    protected void enableActionBarAutoHide(final ListView listView) {
        initActionBarAutoHide();
        listView.setOnScrollListener(new AbsListView.OnScrollListener() {

			省略

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                onMainContentScrolled(firstVisibleItem <= ITEMS_THRESHOLD ? 0 : Integer.MAX_VALUE,
                        lastFvi - firstVisibleItem > 0 ? Integer.MIN_VALUE :
                                lastFvi == firstVisibleItem ? 0 : Integer.MAX_VALUE
                );
                lastFvi = firstVisibleItem;
            }
        });
    }

ListViewのOnScrollイベントの中で、リストのindexの差分からActionBarの表示を切り替えてるようです。

初期化

BaseActivity.java
    /**
     * Initializes the Action Bar auto-hide (aka Quick Recall) effect.
     */
    private void initActionBarAutoHide() {
        mActionBarAutoHideEnabled = true;
        mActionBarAutoHideMinY = getResources().getDimensionPixelSize(
                R.dimen.action_bar_auto_hide_min_y);
        mActionBarAutoHideSensivity = getResources().getDimensionPixelSize(
                R.dimen.action_bar_auto_hide_sensivity);
    }

ここで表示切り替えを行う基準となる数値を、設定しているようです。きちんとdimenリソースで管理されてます。
実際は、mActionBarAutoHideMinY = 152dpmActionBarAutoHideSensivity = 48dpとなっていました。

MinYの方は表示を切り替える際のしきい値のようです。Sensivityの方はイベントを拾うかどうかのスクロール量のしきい値みたいです。
この2つの値をベースに後述するスクロール量の計算を行っています。

スクロール量から表示切り替えの判定

BaseActivity.java
    /**
     * Indicates that the main content has scrolled (for the purposes of showing/hiding
     * the action bar for the "action bar auto hide" effect). currentY and deltaY may be exact
     * (if the underlying view supports it) or may be approximate indications:
     * deltaY may be INT_MAX to mean "scrolled forward indeterminately" and INT_MIN to mean
     * "scrolled backward indeterminately".  currentY may be 0 to mean "somewhere close to the
     * start of the list" and INT_MAX to mean "we don't know, but not at the start of the list"
     */
    private void onMainContentScrolled(int currentY, int deltaY) {
        if (deltaY > mActionBarAutoHideSensivity) {
            deltaY = mActionBarAutoHideSensivity;
        } else if (deltaY < -mActionBarAutoHideSensivity) {
            deltaY = -mActionBarAutoHideSensivity;
        }

        if (Math.signum(deltaY) * Math.signum(mActionBarAutoHideSignal) < 0) {
            // deltaY is a motion opposite to the accumulated signal, so reset signal
            mActionBarAutoHideSignal = deltaY;
        } else {
            // add to accumulated signal
            mActionBarAutoHideSignal += deltaY;
        }

        boolean shouldShow = currentY < mActionBarAutoHideMinY ||
                (mActionBarAutoHideSignal <= -mActionBarAutoHideSensivity);
        autoShowOrHideActionBar(shouldShow);
    }

コメントにあるように、currentYdeltaYは実際には "0", "Integer.MIN_VALUE", "Integer.MAX_VALUE" のどれかしか入ってきません。Math.signum()は引数の符号を返すメソッドなので、ユーザーがスクロールの上下を反転させたタイミングで、スクロール量の蓄積をリセットしてます。

Android-L以前のための処理

BaseActivity.java
    protected void autoShowOrHideActionBar(boolean show) {
        if (show == mActionBarShown) {
            return;
        }

        mActionBarShown = show;
        getLPreviewUtils().showHideActionBarIfPartOfDecor(show);
        onActionBarAutoShowOrHide(show);
    }
LPreviewUtilsBase.java
    public void showHideActionBarIfPartOfDecor(boolean show) {
        // pre-L, action bar is always part of the window decor
        if (show) {
            mActivity.getActionBar().show();
        } else {
            mActivity.getActionBar().hide();
        }
    }

Android4.4までは、ActionBar.show()ActionBar.hide()を呼ばないと切り替えられないので、API Levelによってコードが別になってました。上記がAndroid-L以前のための処理です。

Android-Lだとビルド時に"src/main"ではなく、"src/lpreview"以下のソースになり同じshowHideActionBarIfPartOfDecor()が、以下のようになってました。

LPreviewUtilsImpl.java
    @Override
    public void showHideActionBarIfPartOfDecor(boolean show) {
        if (mActionBarToolbar != null) {
            // Action bar is part of the layout
            return;
        }

        // Action bar is part of window decor
        super.showHideActionBarIfPartOfDecor(show);
    }

ActionBarまわりの表示切り替え処理

BaseActivity.java
    protected void onActionBarAutoShowOrHide(boolean shown) {
        if (mStatusBarColorAnimator != null) {
            mStatusBarColorAnimator.cancel();
        }
        mStatusBarColorAnimator = ObjectAnimator.ofInt(mLPreviewUtils, "statusBarColor",
                shown ? mThemedStatusBarColor : Color.BLACK).setDuration(250);
        mStatusBarColorAnimator.setEvaluator(ARGB_EVALUATOR);
        mStatusBarColorAnimator.start();

        updateSwipeRefreshProgressBarTop();

        for (View view : mHideableHeaderViews) {
            if (shown) {
                view.animate()
                        .translationY(0)
                        .alpha(1)
                        .setDuration(HEADER_HIDE_ANIM_DURATION)
                        .setInterpolator(new DecelerateInterpolator());
            } else {
                view.animate()
                        .translationY(-view.getBottom())
                        .alpha(0)
                        .setDuration(HEADER_HIDE_ANIM_DURATION)
                        .setInterpolator(new DecelerateInterpolator());
            }
        }
    }

最後に表示を切り替えるViewにアニメーションを適用しているのですが、面白いなーと思ったのが滑らかさの演出の部分です。ちゃんとViewのまとまり毎にアニメーションをセットしていて、折りたたむようにViewが出たり引っ込んだりするように見せてるところです。

まとめ

コードを読んでみて、スクロール方向が反転するときの判定が特に参考になったなーと思います。他にも、上記では触れてませんがAndroid-Lとそれ以前のビルドを切り替えるためのbuild.gradleの書き方も、かなり参考になりました。

67
70
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?