[Android] ViewPagerの遷移アニメーションをカスタマイズする

- 以前からAndroidで標準で用意されている仕組み
- `ViewPager.PageTransformer` を実装(implements)したクラスを作成し `ViewPager` にセットする

```java:
someViewPager.setPageTransformer(
        true,
        new SomeTransformer()
);
```

- 第一引数(`boolean: reverseDrawingOrder`)は1枚目のページを2枚目以降のページの上に描画するか否か

---

## 汎用的に利用できる PageTransformer を作っておくと便利

---

```java:PageSwipeTransformer.java
public class PageSwipeTransformer implements ViewPager.PageTransformer {
    private float minAlphaOut = 1.0F;
    private float minAlphaIn = 1.0F;
    private float minScaleOut = 1.0F;
    private float minScaleIn = 1.0F;
    private float positionXOffsetIn = 1.0F;
    private float positionXOffsetOut = -1.0F;
    private float positionYOffsetIn = 0.0F;
    private float positionYOffsetOut = 0.0F;

    public PageSwipeTransformer setMinAlphaOut(float minAlpha) {
        this.minAlphaOut = minAlpha;
        return this;
    }

    public PageSwipeTransformer setMinAlphaIn(float minAlpha) {
        this.minAlphaIn = minAlpha;
        return this;
    }

    public PageSwipeTransformer setMinScaleOut(float minScale) {
        this.minScaleOut = minScale;
        return this;
    }

    public PageSwipeTransformer setMinScaleIn(float minScale) {
        this.minScaleIn = minScale;
        return this;
    }

    public PageSwipeTransformer setPositionXOffsetIn(float offsetPositionX) {
        this.positionXOffsetIn = offsetPositionX;
        return this;
    }

    public PageSwipeTransformer setPositionXOffsetOut(float offsetPositionX) {
        this.positionXOffsetOut = offsetPositionX;
        return this;
    }

    public PageSwipeTransformer setPositionYOffsetIn(float offsetPositionY) {
        this.positionYOffsetIn = offsetPositionY;
        return this;
    }

    public PageSwipeTransformer setPositionYOffsetOut(float offsetPositionY) {
        this.positionYOffsetOut = offsetPositionY;
        return this;
    }

    @Override
    public void transformPage(View page, float position) {
        final float width = (float) page.getWidth();
        final float height = (float) page.getHeight();

        if (-1.0F < position && position <= 0) {
            page.setVisibility(View.VISIBLE);
            page.setAlpha(Math.max(minAlphaOut, 1 + position));
            page.setScaleX(Math.max(minScaleOut, 1 + position));
            page.setScaleY(Math.max(minScaleOut, 1 + position));
            page.setTranslationX(width * -position + width * -position * positionXOffsetOut);
            page.setTranslationY(height * -position * positionYOffsetOut);
        }

        if (0 < position && position < 1.0F) {
            page.setVisibility(View.VISIBLE);
            page.setAlpha(Math.max(minAlphaIn, 1 - position));
            page.setScaleX(Math.max(minScaleIn, 1 - position));
            page.setScaleY(Math.max(minScaleIn, 1 - position));
            page.setTranslationX(-width * position + width * position * positionXOffsetIn);
            page.setTranslationY(height * position * positionYOffsetIn);
        }

        if (position <= -1.0F || 1.0F <= position) {
            page.setVisibility(View.GONE);
        }
    }
}
```

---

例①： 縮小してスライド

```java
viewPager.setPageTransformer(
        true,
        new PageSwipeTransformer()
                .setMinScaleOut(0.9F)
                .setMinScaleIn(0.9F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/e9a0d9c5-099e-e742-2c55-b26cb5563fca.gif" width=300>

---

例②： スライドアウト

```java
viewPager.setPageTransformer(
        true,
        new PageSwipeTransformer()
                .setPositionXOffsetIn(0.0F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/f0ecb106-9293-8fd8-7b40-e74108ac6216.gif" width=300>

---

例③： スライドイン

```java
viewPager.setPageTransformer(
        false,
        new PageSwipeTransformer()
                .setPositionXOffsetOut(0.0F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/be566776-cdfb-287e-fbf8-603a61e9fb2e.gif" width=300>

---

例④： スライドアウト + スタック(重なっている)

```java
viewPager.setPageTransformer(
        true,
        new PageSwipeTransformer()
                .setPositionXOffsetIn(0.5F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/6d0dd3b1-f310-6606-d027-7affc0375545.gif" width=300>

---

例⑤： フェードイン

```java
viewPager.setPageTransformer(
        false,
        new PageSwipeTransformer()
                .setPositionXOffsetOut(0.0F)
                .setMinAlphaIn(0.0F)
                .setPositionXOffsetIn(0.0F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/bb011db5-3864-2e5e-7b1e-e6907b417ae0.gif" width=300>

---

例⑥： 拡大して現れる

```java
viewPager.setPageTransformer(
        true,
        new PageSwipeTransformer()
                .setPositionXOffsetIn(0.0F)
                .setMinScaleIn(0.5F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/0b5030ef-1543-8d67-9dbe-3b9c8f5d7dd7.gif" width=300>

---

例⑦： 斜め上にスライド

```java
viewPager.setPageTransformer(
        true,
        new PageSwipeTransformer()
                .setPositionYOffsetOut(-0.5F)
                .setPositionYOffsetIn(0.5F)
);
```

---

- 動作イメージ

<img src="https://qiita-image-store.s3.amazonaws.com/0/35733/70da2743-38c6-69c2-c7f6-219282940918.gif" width=300>

---

## おまけ：コードで ViewPager のページを変更すると速すぎる問題

---

- `setCurrentItem()` の第二引数(`boolean: smoothScroll`) に `true` を指定してもスムーズになってくれない
- 解決するには ViewPager を継承したカスタムViewを作る

---

```java:SmoothScrollViewPager.java
public class SmoothScrollViewPager extends ViewPager {

    public SmoothScrollViewPager(Context context) {
        super(context);
        setMyScroller();
    }

    public SmoothScrollViewPager(Context context, AttributeSet attr) {
        super(context, attr);
        setMyScroller();
    }

    private void setMyScroller() {
        try {
            Class<?> viewpager = ViewPager.class;
            Field scroller = viewpager.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            scroller.set(this, new MyScroller(getContext()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class MyScroller extends Scroller {
        private MyScroller(Context context) {
            super(context, new FastOutSlowInInterpolator());
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, 500);
        }
    }
}
```

---

- `Interpolator`(前項の例だと`FastOutSlowInInterpolator`)とアニメーション時間(前項の例だと`500`ミリ秒)はお好みで
    - レイアウトXMLから指定できるようにすると良いかもしれない

---

## END