ImageViewの表示領域内に収まらない大きな画像を自動スクロールで表示する

##　きっかけ
- 2019 Material Design Awardで入賞した**Trip.com**を見て、自動スクロールで画像を表示していて良い感じだなと思ったので試してみました。

---

## <img width="300" alt="record-191125110141.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36517/9f1eb79d-fce9-6c61-a487-c5ab1c44129c.gif">

---
## 動作環境

- Android Studio 3.5.2
- Android 10, 5

---

##　サンプルコードについて
- 画像の自動スクロール
- カスタムビューで実装する

---
##　画像の自動スクロール

---

##　画像の自動スクロール
```xml:activity_scroll_image_view.xml
    <ImageView
        android:id="@+id/sushi"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:contentDescription="@null"
        android:scaleType="matrix"
        android:src="@drawable/sushi"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
```

--- 
##　画像の自動スクロール

```kotlin:ScrollImagaeViewActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scroll_imagae_view)

        val image = findViewById<ImageView>(R.id.sushi)
        image.doOnLayout {
            var repeatCount = 0
            // imageViewに格納したDrawableの幅を取得する
            val drawableWidth = image.drawable.intrinsicWidth
            val imageWidth = image.width
            var isScrollLeftToRight = true
            var currentX = 0

            val handler = Handler()
            val r = object : Runnable {
                override fun run() {

                    // 右端まで表示したらスクロール方向を左へ変更する
                    if (drawableWidth < currentX + imageWidth) {
                        isScrollLeftToRight = false
                    }

                    if (currentX < 0) {
                        repeatCount++
                        isScrollLeftToRight = true
                    }

                    if (repeatCount == 2) return

                    val scrollX = getScrollX(isScrollLeftToRight)
                    currentX += scrollX

                    image.scrollBy(
                        scrollX,
                        0
                    )

                    handler.postDelayed(this, 10)
                }
            }
            handler.post(r)
        }
    }
```

--- 

## 動かしてみました

![record-191125112627.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36517/a7f425e5-7824-1412-fb10-83b8e016c74b.gif)


--- 
## 美味しそうな寿司が動きました :relaxed:

--- 
## カスタムビューで実装する

---
## カスタムビューで実装する

```xml:activity_scroll_image_view.xml
    <jp.co.yiwaisako.ui_sample.AutoHorizontalScrollImageView
        android:id="@+id/sushi"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:contentDescription="@null"
        android:src="@drawable/sushi"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
```

---
## カスタムビューで実装する

```kotlin:AutoHorizontalScrollImageView（１）
    init {
        scaleType = ScaleType.MATRIX
    }

    // スクロールを繰り返す上限回数
    var maxRepeatCount = 1

    // 1回のスクロール量
    var scrollValueXForOneTime = 1

    // スクロールした回数
    private var repeatCount = 0

    // 現在のX位置
    private var currentX = 0

    // 繰り返すかどうか
    private val isAutoScroll: Boolean
        get() {
            return repeatCount < maxRepeatCount
        }

    // スクロール方向
    private var direction: Direction = Direction.TO_RIGHT

    // スクロール量
    private val scrollValueX: Int
        get() {
            return when (direction) {
                Direction.TO_RIGHT -> {
                    scrollValueXForOneTime
                }
                Direction.TO_LEFT -> {
                    -scrollValueXForOneTime
                }
            }
        }

    enum class Direction {
        TO_RIGHT,
        TO_LEFT
    }
```
---
## カスタムビューで実装する

```kotlin:AutoHorizontalScrollImageView（２）
    override fun onDraw(canvas: Canvas?) {
        if (isAutoScroll) {
            scrollTo(scrollValueX)
            // scrollTo()の後に実行してください
            decideDirectionAfterScrolled()
        }
        super.onDraw(canvas)
    }

    private fun scrollTo(scrollValueX: Int) {
        currentX += scrollValueX
        scrollBy(scrollValueX, 0)
    }

    private fun decideDirectionAfterScrolled() {
        if (currentX <= 0) {
            repeatCount++
            direction = Direction.TO_RIGHT
        }
        if (drawable.intrinsicWidth <= currentX + width) {
            direction = Direction.TO_LEFT
        }
    }
```

---

## サンプルレイアウトに配置 

- ![record-191126194714.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36517/6813a694-ec84-efcf-67e5-a94b6df5a85b.gif)

---

## まとめ
- 水平方向の自動スクロールを実装しました
- リピート回数、スクロール量などの調整をDatabindingで対応しても良さそう（今回は未対応です）
- コードの`scrollTo()の後に実行してください`のコメントを無くしたい（メソッドの実行順番を利用者に意識させない）のですがよい修正案が思いつかなく、アドバイスありましたらお願いします。 :clap:


---

## リンク
- [サンプルコード](https://github.com/yiwaisako/ui_sample)
- [kenBurnsView](https://github.com/flavioarfaria/KenBurnsView)
- [ImageView](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/ImageView.java)