アクティビティの開始をアニメーション化(Kotlin)。簡易ImageViewerアプリ。

# 今回作るもの
[アニメーションを使ったアクティビティの開始](https://developer.android.com/training/transitions/start-activity?hl=ja)
[アクティビティ間の切り替えをアニメーション化する](https://developer.android.com/training/animation/overview?hl=ja#:~:text=%E3%81%94%E8%A6%A7%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%93%E3%83%86%E3%82%A3%E9%96%93%E3%81%AE%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%82%92%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%8C%96%E3%81%99%E3%82%8B,-Android%205.0%EF%BC%88API)

公式にある画像拡大表示するアニメーションを自分なりに簡略化して
作っていきたいと思います。

一応公式に載っているGitイメージです。
![layout-transition.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1080547/8e069ce4-86d5-a9e5-c23e-474d403d9b9e.gif)

> 参考文献
> * [ActivitySceneTransitionBasic 公式GitHub](https://github.com/android/animation-samples/tree/master/ActivitySceneTransitionBasic)
> * [GridView Kotlinでの使い方](https://www.tutorialspoint.com/how-to-create-gridview-layout-in-an-android-app-using-kotlin)

# 詳しく
色と色の名前を一枚のカードとします。
配列はGridViewで表示しています。

> 今回はAPIレベルを30でやっていきます。  

> いろいろ試してみましたが、APIレベル30未満だと
> 画像を拡大したときにimageViewにうまく色が反映されないバグがありました。
> しかし、drawableにある画像は問題なく表示できましたので、画像拡大で使いたい場合はAPI30未満でやっても問題はないと思います。

[GitHub](https://github.com/Myuy973/ImageViewerAppRepository)
<img width="300" alt="ezgif.com-gif-maker (2).gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1080547/7f16de80-4617-d249-e9da-b0cee59c555f.gif">

# 実装
## GridView
まず最初に新規にプロジェクトを作成していきます。
今回は空のアクティビティで作っていきます。

### activity_main.xml
プロジェクトが立ち上がったらまずactivity_main.xmlにGridViewを追加します。
と同時にGridView内に表の行数を指定しておきます。
```android:numColumns="2"```

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <GridView
        android:id="@+id/gridView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:numColumns="2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
```

### grid_item_layout.xml
layoutに```grid_item_layout.xml```ファイルを追加してください。
色と色の名前をペアーとしたアイテムのレイアウトを作っていきます。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/detailImage"
                android:layout_width="0dp"
                android:layout_height="100dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:srcCompat="@color/black" />

            <TextView
                android:id="@+id/detailText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="not set"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/detailImage" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
```


### MainActivity.kt
次にMainActivity.ktを編集していきます。
ここでは、GridViewに入れたい要素を定義しAdapterのセットもしていきます。

```kotlin
class MainActivity : AppCompatActivity() {

    lateinit var colorsAdapter: ColorsAdapter
    private val colors: Array<Int> = arrayOf(
        R.color.black,
        R.color.purple_200,
        R.color.purple_500,
        R.color.purple_700,
        R.color.teal_200,
        R.color.teal_700
    )
    private val colorsName: Array<String> = arrayOf(
        "black",
        "purple_200",
        "purple_500",
        "purple_700",
        "teal_200",
        "teal_700"
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

　　　　// ColorsAdapterは後ほど作成します。
        colorsAdapter = ColorsAdapter(this, colors, colorsName)
        findViewById<GridView>(R.id.gridView).apply {
            adapter = colorsAdapter
        }
    }
}
```
### Adapter
GridViewのAdapterを作っていきます。

if文についてですがどういう働きをするのかが
申し訳ないのですがわかっていません（汗

ですが、削除するとアプリが落ちることがあるので
入れておきます。。。
詳しい方がいらっしゃいましたらご教授いただけると幸いです。

```kotlin
class ColorsAdapter(
    private val context: Context,
    private val colors: Array<Int>,
    private val colorsName: Array<String>
): BaseAdapter() {

    private var layoutInflater: LayoutInflater? = null
    private lateinit var imageView: ImageView
    private lateinit var textView: TextView

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

        var view = convertView
        if (layoutInflater == null) {
            layoutInflater =
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        }
        if (view == null) {
            view = layoutInflater!!.inflate(R.layout.grid_item_layout, null)
        }

        imageView = view!!.findViewById(R.id.detailImage)
        textView = view!!.findViewById(R.id.detailText)
        imageView.setImageResource(colors[position])
        textView.text = colorsName[position]
        return view

    }

    override fun getItem(position: Int): Any? {
        return null
    }

    override fun getItemId(position: Int): Long {
        return 0
    }

    override fun getCount(): Int {
        return colors.size
    }
}
```

ここまで出来たらGridViewの表示はできると思うので
各自で確認してみてください。

## imageの表示アニメーションの実装
やっと本題です。
GridViewの説明が長くなって申し訳ないのですが、
ここから上記に比べるとアニメーションについての記述は少なめになっています。
アニメーション自体は簡単に実装できるので助かりました♪(´▽｀)

### MainActivity.kt
アイテムが押されたときにでる、画像が拡大された画面はActivityで作ることにします。
そのためここでは、アイテムが押されたときにActivityを起動する処理を記述します。
あとその他表示のための情報やアニメーションに関する情報を添付します。

```kotlin
      
        findViewById<GridView>(R.id.gridView).apply {
            adapter = colorsAdapter
            // 追加
            setOnItemClickListener { parent, view, position, id ->
                changeActivity(view, position)
            }
        }
    }

    private fun changeActivity(view: View, position: Int) {
        val intent = Intent(this, ShowActivity::class.java)
        // 拡大表示用のActivityで表示する色と名前をセット
        intent.putExtra("COLOR", colors[position])
        intent.putExtra("COLOR_NAME", colorsName[position])

        // GridViewにある要素(image,text)に共通させる名前を紐づける
        val pair1 = UtilPair.create(view.findViewById<View>(R.id.detailImage),
                ShowActivity().VIEW_NAME_HEADER_IMAGE)
        val pair2 = UtilPair.create(view.findViewById<View>(R.id.detailText),
                ShowActivity().VIEW_NAME_HEADER_TEXT)

　　　　// アニメーションをするには、GridViewにある要素と表示用のActivityで
       // idとは別途で共通の名前を付ける必要がある(今回ではimageとtextに)
        @SuppressWarnings("unchecked")
        val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
                this as Activity,
                pair1,
                pair2)
        ActivityCompat.startActivity(this, intent, activityOptions.toBundle())
    }
```

### ShowActivity.kt
拡大表示するためのActivityを作っていきます。


```kotlin:ShowActivity
class ShowActivity : AppCompatActivity() {

    val VIEW_NAME_HEADER_IMAGE = "detail:Image"
    val VIEW_NAME_HEADER_TEXT = "detail:Text"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_show)

        // 色と色の名前を取り出す
        val color = intent.getIntExtra("COLOR", 0)
        val colorName = intent.getStringExtra("COLOR_NAME") ?: "error"

        val imageView = findViewById<ImageView>(R.id.detailImage)
        val textView = findViewById<TextView>(R.id.detailText)

        // 共通の名前と紐づける
        ViewCompat.setTransitionName(imageView, VIEW_NAME_HEADER_IMAGE)
        ViewCompat.setTransitionName(textView, VIEW_NAME_HEADER_TEXT)
        // 値のセット
        imageView.setImageResource(color)
        textView.text = colorName

        // 画面を押すことでActivityを閉じる
        findViewById<ConstraintLayout>(R.id.showConteiner).setOnClickListener {
            onBackPressed()
        }

    }

}
```

```xml:activity_show.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/showConteiner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ShowActivity">

    <ImageView
        android:id="@+id/detailImage"
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="100dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/detailText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="16dp"
        android:textAlignment="viewStart"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/detailImage"
        tools:text="color Name" />
</androidx.constraintlayout.widget.ConstraintLayout>
```




### grid_detail_transition.xml
resフォルダ内にtransitionというフォルダーを作り
その中に```grid_detail_transition.xml```ファイルを作成します。

ここでは具体的ににどういったアニメーションをするのか定義します。
```android:duration```アニメーションにかかる時間も指定できます。

その他のアニメーションについては以下を参照ください。
[android.transition Classes](https://developer.android.com/reference/kotlin/android/transition/package-summary)

```xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<!--    android:duration="2000">-->
    <changeBounds/>
    <changeImageTransform/>
</transitionSet>
```

### themes.xml
res/value/themesにあるthemes.xml(上のほう)を編集します。
ちなみに下のthemes.xml(night)を編集しなくても
ダークモードでもちゃんと動作するみたいです。

```xml
<style>
       ......
        
        // アニメーションを有効
        <item name="android:windowActivityTransitions">true</item>

       // 開始と終了時のアニメーションを指定
        <item name="android:windowSharedElementEnterTransition">
            @transition/grid_detail_transition</item>
        <item name="android:windowSharedElementExitTransition">
            @transition/grid_detail_transition</item>
</style>
```

## まとめ
以上が公式に載ってるコードを自分なりに簡略したアプリになります。
自分的には公式の文章とコードがわかにくかったので
これを読んでスムーズに実装できる方がいらっしゃれば幸いです。