LoginSignup
5
1

More than 1 year has passed since last update.

CollapsingToolbarLayoutとSwipeRefreshLayoutの併用

Last updated at Posted at 2022-01-05

よくあるプロフィール画面のヘッダー画像をparallaxにしつつ、Pull-to-Refreshもできる画面を作ってみたいと思い、いざ実装してみたら一工夫必要だったので記事にしました。

この記事で紹介すること

goal
この画面のようなprallaxなヘッダー画像Pull-to-Refreshが共存した画面の実装方法を紹介します。

結論

そのまま併用すると、スクロール仕切っていないのにPull-to-Refreshが発動してしまいます。
解決策として、AppBarLayout.OnOffsetChangedListenerにSwipeRefreshLayout.isEnabledを上書きする処理を登録します。

AppBarLayout.OnOffsetChangedListenerが受け取るverticalOffsetにおいて、verticalOffset == 0の時、すなわちヘッダー画像が全て表示し切っている状態をスクロールし切ったと状態だと判定するようSwipeRefreshLayout.isEnabledに上書きすることで、ヘッダー画像をスクロールしきっていないのにPull-to-Refreshが発動してしまうことを防ぐことができます。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //省略

        binding.appBar.addOnOffsetChangedListener(
            AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                binding.swipeRefresh.isEnabled = verticalOffset == 0
            }
        )
    }

もっと詳細な説明が欲しい方は以下から解説していますので、手元で動かしつつ実装してみてください。

やり方と解説

プロジェクト作成

AndroidStudioのNewProjectからScrolling Activityを選択してください。
しばらくするとプロジェクトが立ち下がりますので、ビルドしてみてください。
スクリーンショット 2022-01-04 21.14.40.png
スクリーンショット 2022-01-04 21.14.58.png

parallaxなヘッダー画像を実装

サンプルにちょっと手を加えます。
activity_scrolling.xmlを次のように書き換えてください。

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".ScrollingActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
            app:toolbarId="@+id/toolbar">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="180dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_launcher_background"
                app:layout_collapseMode="parallax"
                tools:ignore="ContentDescription" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_scrolling" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/fab_margin"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

修正点は次の通りです。

  • CollapsingToolbarLayoutのapp:layout_scrollFlagsを"scroll|enterAlwaysCollapsed"に変更
  • CollapsingToolbarLayout配下のToolbarLayoutをImageViewに変更し、android:scaleType="centerCrop"android:src="@drawable/ic_launcher_background"を指定。(parallaxしているのをわかりやすくするため)
  • style="@style/Widget.MaterialComponents.Toolbar.Primary"とapp:contentScrim="?attr/colorPrimary"を一通り削除(これはやらなくてもいいです)

これでparallaxなヘッダー画像が実装できました。
parallax

なぜこれでparallaxになるのかという説明については、多くの記事がありますので割愛します。

Pull-to-Refreshを実装

SwipeRefreshLayoutを使ってPull-to-Refreshを実装していきます。
まずはbuild.gradleからdependenciesに以下を追加してください。

dependencies {
    ~
    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
}

activity.scrolling.xmlにてSwipeRefreshLayoutが一番上の親になるよう記述します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    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/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".ScrollingActivity">
        <!-- 省略 -->
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

ScrollingActivityのonCreateでOnRefreshListenerをセットします。
今回はダミーのリフレッシュ処理として1000MillisecondsのDelayを置いておきます。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //省略

        binding.swipeRefresh.setOnRefreshListener {
            // 本来はここにdataLoad()みたいなリフレッシュ時に動かしたい処理を書く。
            Handler().postDelayed({
                binding.swipeRefresh.isRefreshing = false
            }, 1000)
        }
    }

これで正常に動いてくれれば簡単なんですが、案の定うまく動きません。
スクロールしきる前にリフレッシュ処理が走ってしまいます。

これは、parallax化したヘッダー画像の高さ分のスクロール可能領域を検知できていないためです。
NestedScrollViewのTopが描画された時点でスクロールし切ったと判定し、SwipeRefreshLayoutのcanChildScrollUp()がfalseを返却してしまっています。

最後に一工夫

AppBarLayout.OnOffsetChangedListenerが受け取るverticalOffsetを使って解決していきます。
verticalOffsetはAppBarLayoutの初期位置で0を返し、スクロールアップすると移動した分を負のpx値が順次返ってくるようになります。
つまりverticalOffset == 0の時がparallax化したヘッダー画像が完全に表示し切っている状態なので、この条件をスクロールしきったと判定するように修正してあげる必要があります。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //省略

        binding.appBar.addOnOffsetChangedListener(
            AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                binding.swipeRefresh.isEnabled = verticalOffset == 0
            }
        )
    }

ここまでお疲れ様でした。
では動かしてみましょう!
prallaxなヘッダー画像Pull-to-Refreshが以下のように動作していると思います。
goal

参考文献

5
1
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
5
1