0
1

ViewPager2でページ境界にドロップシャドウを入れる方法

Last updated at Posted at 2024-05-18

ViewPager2でもPageTransformerを使うことで、様々なページ遷移を実装できます。

ただ、これだけではページそのものに細工することしかできません。
例えば、2ページ目がかぶってくるように変更した場合は、以下のようにページ境界にドロップシャドウぐらい入れたくなりますよね。

この実装方法について説明します。

PageTransformer

まずは左側のページが右側のページの上にかぶってくるような遷移をするPageTransformerを実装します。

binding.viewPager.setPageTransformer { page, position ->
    page.translationX = if (position < 0f) -page.width * position else 0f
}

以上!

軽く説明すると、PageTransformerは何もしないと、ViewPagerのデフォルトの動作になります。
positionが負の値の場合、何もしなければposition*ページ幅分左側に移動しているので、同じ移動量右方向に移動させて、動かさないようにしているという形です。

Drawableを用意する

描画方法はいろいろあるとは思いますが、Drawableを用意するのが安直でしょう。今回は以下のようなxmlを用意しました

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <size android:width="24dp" />
    <gradient
        android:angle="180"
        android:endColor="#00000000"
        android:centerColor="#04000000"
        android:startColor="#10000000"
        android:type="linear"
        />
</shape>

ItemDecoration

ViewPager2は内部の実装にRecyclerViewが使われており、ViewPager2にもRecyclerView.ItemDecorationを設定することができます。
RecyclerView.ItemDecorationは以下の3つのメソッドをOverrideすることで様々な装飾を実装できます。

メソッド 意味
getItemOffsets 要素間のOffsetを定義します。デフォルトではOffsetなしとなります。
onDraw デコレーションの描画を行います。要素の描画前に実行されるため、要素の下に描画されます。
onDrawOver デコレーションの描画を行います。要素の描画後に実行されるため、要素の上に描画されます。

今回の目的ではonDrawOverを実装するだけで良いです。

ItemDecorationではparent(RecylerView)に対するCanvasが与えられ、childViewを調べて描画することになります。
pageの左座標が、canvas内にあるとき、その左側に描画します。

class DropShadowDecoration(context: Context) : ItemDecoration() {
    private val drawable = AppCompatResources.getDrawable(context, R.drawable.shadow)!!

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        parent.forEach {
            if (it.left in 1 until c.width) {
                drawable.setBounds(it.left - drawable.intrinsicWidth, 0, it.left, c.height)
                drawable.draw(c)
            }
        }
    }
}

併せて、以下のような指定をすることで冒頭のようなページ境界にドロップシャドウを表示させることができます。

binding.viewPager.addItemDecoration(DropShadowDecoration(this))
binding.viewPager.setPageTransformer { page, position ->
    page.translationX = if (position < 0f) -page.width * position else 0f
}

以上です。


追記

左向きにスライドさせる場合

グラデーションを逆向きにして

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <size android:width="24dp" />
    <gradient
        android:angle="0"
        android:endColor="#00000000"
        android:centerColor="#04000000"
        android:startColor="#10000000"
        android:type="linear"
        />
</shape>

右側にシャドウを描画

class DropShadowDecoration(context: Context) : ItemDecoration() {
    private val drawable = AppCompatResources.getDrawable(context, R.drawable.shadow)!!

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        parent.forEach {
            if (it.right in 0 until c.width) {
                drawable.setBounds(it.right, 0, it.right + drawable.intrinsicWidth, c.height)
                drawable.draw(c)
            }
        }
    }
}

PageTransformerでtranslationXの操作を右側のページに行うように変更しますが、
そのままだと右側のページの方が上になってしまうため、translationZにも細工をして、上下逆に表示させます。

binding.viewPager.addItemDecoration(DropShadowDecoration(this))
binding.viewPager.setPageTransformer { page, position ->
    page.translationZ = if (position > 0f) -1f else 0f
    page.translationX = if (position > 0f) -page.width * position else 0f
}
0
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
0
1