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

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

https://qiita.com/ryo_mm2d/items/2826037e1a0b6bed8213

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

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/64e3548d-5e16-02fb-dd1f-c5a2e0670cee.gif)

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

# PageTransformer

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

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

以上！

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

# Drawableを用意する

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

```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内にあるとき、その左側に描画します。

```kotlin
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)
            }
        }
    }
}
```

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

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

以上です。

---

追記

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

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

```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="0"
        android:endColor="#00000000"
        android:centerColor="#04000000"
        android:startColor="#10000000"
        android:type="linear"
        />
</shape>
```

右側にシャドウを描画

```kotlin
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にも細工をして、上下逆に表示させます。

```kotlin
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
}
```