SwipeRefreshLayout+RecyclerViewでローディング画面を出すのに苦労した話

Android support libraryのSwipeRefreshLayoutは直下のviewがscrollableでないといけないようで、下記のような構造だとスクロールアップのイベントを常にSwipeRefreshLayoutが奪ってしまい、RecyclerViewをスクロールアップできなくなってしまいました。

<!-- 動かない例 -->
<SwipeRefreshLayout>
  <RelativeLayout>
    <RecyclerView/>
    <ProgressBar/>
  </RelativeLayout>
</SwipeRefreshLayout>

AndroidのWebviewでSwipeRefreshLayoutを使用した際のスプラッシュ画面の実装方法 - Qiita で触れられているのも同じ問題だと思われます。

これ自体はバグだと思いますが、考えてみるとAppBarLayoutのToolbar同期をいれると3つのscrollable viewがそれぞれのスクロールイベントを待機している状態だったので問題が起こりがちな話ではあります。

それはそれとしてHeliumでローディング画面をだしたかったので別案を考えました。このアプリはカードUIなので、faceboo.com (web)のように、空のカードをだしてProgressBarの代わりにしました。また、動きがないとフリーズしているように見えるので、alpha値をいじってふわふわしたアニメーションにしてみました*1

まず空要素のための値を用意して (TimelineFragment.java#L83):

    final HatebuEntry emptyEntry = new HatebuEntry();

RecyclerView.Adapterのデフォルトはそれをいれておく (TimelineFragment.java#L106-L108):

        // タブレットでも1行目をすべて埋めるため、1ではなくspanCount個の要素をつくる
        for (int i = 0, max = layoutManagers.getSpanCount(); i < max; i++) {
            adapter.addItem(emptyEntry);
        }

adapterのほうでは、enptyEntryがきたらローディングのためのアニメーションを開始し、それ以外のケースではアニメーションを止める処理をれると(TimelineFragment.java#L223-L227):

            if (entry == emptyEntry) {
                loadingAnimation.start(binding.getRoot());
                return;
            }
            loadingAnimation.cancel(binding.getRoot());

LoadingAnimationはこんな感じで ViewPropertyAnimator を使います(LoadingAnimation.java):

public class LoadingAnimation {
    static long DURATION = 1000;

    public void start(final View view) {
        view.animate()
                .alpha(0.3f)
                .setDuration(DURATION)
                .withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        view.animate()
                                .alpha(0.9f)
                                .setDuration(DURATION)
                                .withEndAction(new Runnable() {
                                    @Override
                                    public void run() {
                                        start(view);
                                    }
                                })
                                .start();
                    }
                })
                .start();
    }

    public void cancel(final View view) {
        view.clearAnimation();

    }
}

なおObjectAnimator+AnimatorSetでもやってみたのですが、アニメーションの停止をうまくコントロールできませんでした。

loadingindicator用のviewを用意するよりも本来そのadapter使うべきcard viewを出すほうが見た目が自然なのでいいかなという判断です。このアプリに関してはしばらくこれでいってみます。

*1:ここで実装したローディングアニメーションの実際の挙動は Helium - Google Play の Android アプリ の v2.1.0 以上で確認できます。