【Android】アプリアイコンをさくっと作成する方法

アプリのアイコンが付いていないとコードを書く気が出ない・・・そんなときに、とりあえずそれっぽいアイコンをさくっく作成する方法です。先日、[iOS](https://qiita.com/yuppejp/items/0cc2b8276d215cc24f14)でやったので、今回はAndroidでもやってみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/94d4cae1-1dd4-071f-5e71-0678fa4186a4.png)
↑ やる気がでないやつ


# アプリアイコンの作成の流れ
1. アイコン画像を表示するViewを作成
1. Viewのスナップショットをファイルに保存(PNG)
1. Android Studioでアイコン画像を設定

## アイコン画像を表示するViewの作成
Jetpack Composeで[マテリアルアイコン](https://developer.android.com/reference/kotlin/androidx/compose/material/icons/package-summary)を使って適当なアイコンをViewに表示して、背景色にグラデーションをかけるだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/97b204de-5d11-08f8-61eb-6d5610787eff.png)

```kotlin:AppIconView.kt
@Composable
fun AppIconView(iconVector: ImageVector, baseColor: Color, modifier: Modifier = Modifier) {
    val endColor = baseColor.copy(alpha = 0.7f)

    BoxWithConstraints(
        modifier = modifier
    ) {
        val boxWith = constraints.maxWidth.toFloat()
        val boxHeight = constraints.maxHeight.toFloat()

        val gradientBrush = Brush.linearGradient(
            colors = listOf(baseColor, endColor),
            start = Offset(boxWith / 2, boxHeight / 2),
            end = Offset(boxWith / 2, 0f))

        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(brush = gradientBrush),
            contentAlignment = Alignment.Center
        ) {
            Icon(
                imageVector = iconVector,
                contentDescription = null,
                tint = Color.White,
                modifier = Modifier.fillMaxSize(0.4f)
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun IconViewPreview() {
    Column {
        AppIconView(
            iconVector = Icons.Default.Draw,
            baseColor = Color(235, 78, 61),
            modifier = Modifier.size(200.dp).padding(8.dp)
        )
        AppIconView(
            iconVector = Icons.Default.Train,
            baseColor = Color(101, 196, 102),
            modifier = Modifier.size(200.dp).padding(8.dp)
        )
        AppIconView(
            iconVector = Icons.Default.Sailing,
            baseColor = Color(52, 120, 246),
            modifier = Modifier.size(200.dp).padding(8.dp)
        )
    }
}
```

## すべてのマテリアルアイコンを使用できるようにする
デフォルトではCoreライブラリに入っている基本アイコンのみのため、すべてのマテリアルアイコンを使用するためには追加パッケージが必要です。

```kotlin:build.gradle.kts
implementation("androidx.compose.material:material-icons-extended")
```

Transportationの例:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/829f2c33-1032-da19-6e83-4841c5f11806.png)

## Viewのキャプチャをファイルに保存
ファイル保存用のボタンを設置し、 アイコンのキャプチャをファイルに保存します。キャプチャ対象をAppIconViewだけに絞るために、AndroidViewでラップしViewのインスタンスを取得します。画像ファイルは端末の画像フォルダーに保存されます（エミュレータ時のファイルパスは後述）。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/c128f5f0-32e9-3b12-f3b7-f9e09d77d1dc.png" width="300">

アイコンや色を変えるUIは今回用意していなので、コード上のiconとbaseColorを直接書き換えます。

```kotlin:MainActivity.kt
@Composable
fun MainView() {
    val scope = rememberCoroutineScope()
    val context = LocalContext.current
    val density = LocalDensity.current
    var composeViewRef by remember { mutableStateOf<ComposeView?>(null) }
    var isGranted by remember { mutableStateOf(true) }
    val icon: ImageVector = Icons.Default.Draw
    val requestPermissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) {
        if (it) {
            isGranted = true
        } else {
            Toast.makeText(context, "Permission denied", Toast.LENGTH_SHORT).show()
        }
    }

    isGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        true
    } else {
        ContextCompat.checkSelfPermission(
            context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        contentAlignment = Alignment.Center
    ) {

        Column(
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            // キャプチャ時にインスタンスを取得するために、AndroidViewでアイコンを描画
            AndroidView(
                factory = { ctx ->
                    ComposeView(ctx).apply {
                        layoutParams = ViewGroup.LayoutParams(
                            (300 * density.density).toInt(),
                            (300 * density.density).toInt()
                        )
                        setContent {
                            AppIconView(
                                iconVector = icon,
                                baseColor = Color(235, 149, 0),
                                modifier = Modifier
                                    .size(300.dp)
                                    .background(Color.White)
                            )
                        }
                        composeViewRef = this
                    }
                }
            )

            Text(icon.name)
        }

        Button(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(top = 16.dp),
            onClick = {
                if (isGranted) {
                    val view = composeViewRef
                    view?.let {
                        val bitmap = it.drawToBitmap()
                        scope.launch {
                            saveImageToGallery(context, bitmap)
                        }
                    }
                } else {
                    requestPermissionLauncher.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
            }
        ) {
            Text("Save Image")
        }
    }
}

fun saveImageToGallery(context: Context, bitmap: Bitmap) {
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "Icon_${System.currentTimeMillis()}.png")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
    }
    val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    uri?.let {
        val outputStream: OutputStream? = context.contentResolver.openOutputStream(it)
        outputStream.use { stream ->
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
            Toast.makeText(context, "Image file saved to: " + it.path, Toast.LENGTH_LONG).show()
        }
    }
}
```

## エミュレータで実行している場合の保存場所
Androidエミュレータでアプリを実行すると、アプリの画像ファイルはAndroid StudioのDevice Explorerで参照できます。
storage > emulated > 0 > Picturesに保存されているので、pngファイルを右クリックしてダウンロードフォルダーなどに保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/a353cf56-30a4-ed81-0556-ecb0e761d6ef.png)

## 画像ファイルをAndroid StudioのImage Assetに設定

1. Android StudioのFileメニューから、New > Image Asset を選択して Asset Studio を起動します。
1. 画像ファイルをPathに設定します。上書きするかの警告は無視して問題ありません。
1. あとはデフォルトのまま　Next > Finish　と進めると完了です。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/5a27ca2e-2b4f-7968-ec88-2f2fbb557da1.png)

# 完成サンプル
いい感じにできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/c33317a9-35dd-35f3-b52c-f2a67125cf8a.png)

おしまい〜！

## サンプルコード一式
https://github.com/yuppejp/IconMaker-Android

## iOS版はこちら
[【iOS】アプリアイコンをさくっと作成する方法](https://qiita.com/yuppejp/items/0cc2b8276d215cc24f14)

https://github.com/yuppejp/IconMaker-iOS