3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アプリのアイコンを希望に合わせて変更できる

Last updated at Posted at 2023-11-23

Jetpack Composeでアプリのアイコンを希望に合わせて変更できる

Android アプリのアイコンをプログラムで変更する。
最近、𝕏 Blue ユーザーがアプリのアイコンを変更できることが発表されました。 Reddit アプリでも同様の機能が提供されているため、この機能は新しいものではありません。
実装は特に難しいものではないので、ここで共有したいと思います。

AndroidManifest.xml で activity-alias 要素を宣言することで、複数のランチャー アイコンを作成できます。 アプリのアイコンを変更すると、現在のアイコンを無効にして新しいアイコンを有効にすることで効果が得られます。

設定:

*こちらでMainActivity一つActivity使用してます

AndroidManifest.xmlactivity-alias要素を宣言する

Step 1:

android:launchMode="singleTop" にする

AndroidManifest.xml
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:theme="@style/Theme.AndroidBlog">
</activity>

Step 2:

複数のランチャー アイコンを作成

Screen Recording 2023-11-23 at 21.45.48.gif

Step 3:

values/strings.xml<string name="labelName">App Label Name</string> します

strings.xml
    <string name="app_name_a">Android A</string>
    <string name="app_name_b">Android B</string>
    <string name="app_name_c">Android C</string>
    <string name="app_name_d">Android D</string>
    <string name="app_name_e">Android E</string>
    <string name="app_name_f">Android F</string>
    <string name="app_name_g">Android G</string>
    <string name="app_name_h">Android H</string>

Step 4:

activity-alias要素を宣言する
https://gist.github.com/ihridoydas/20c02eea3f711b42298e8234226161ed

AndroidManifest.xml
 <!--End For App Icon Changer-->
        <activity-alias
            android:name=".MainActivity"
            android:enabled="true"
            android:exported="true"
            android:label="@string/app_name"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <!--Start For App Icon Changer-->
        <activity-alias
            android:name=".MainActivityA"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_a"
            android:label="@string/app_name_a"
            android:roundIcon="@mipmap/ic_launcher_a_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityB"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_b"
            android:label="@string/app_name_b"
            android:roundIcon="@mipmap/ic_launcher_b_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityC"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_c"
            android:label="@string/app_name_c"
            android:roundIcon="@mipmap/ic_launcher_c_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityD"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_d"
            android:label="@string/app_name_d"
            android:roundIcon="@mipmap/ic_launcher_d_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityE"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_e"
            android:label="@string/app_name_e"
            android:roundIcon="@mipmap/ic_launcher_e_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityF"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_f"
            android:label="@string/app_name_f"
            android:roundIcon="@mipmap/ic_launcher_f_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityG"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_g"
            android:label="@string/app_name_g"
            android:roundIcon="@mipmap/ic_launcher_g_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".MainActivityH"
            android:enabled="false"
            android:exported="true"
            android:icon="@mipmap/ic_launcher_h"
            android:label="@string/app_name_h"
            android:roundIcon="@mipmap/ic_launcher_h_round"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
        <activity-alias
            android:name=".StartActivity"
            android:exported="true"
            android:targetActivity=".MainActivity">
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
        <!--End For App Icon Changer-->

Step 5:

AppIcon.kt dataクラスを作成
https://gist.github.com/ihridoydas/c19b70c004ec774ddb2e65f361e34a61

AppIcon.kt
data class AppIcon(
    val component: String,

    @DrawableRes
    val foregroundResource: Int,
)

Step 6:

AndroidAppIcons.kt AppIconのListを作成
https://gist.github.com/ihridoydas/ba0a4b63bb0978b347feeff3c15f1594

AndroidAppIcons.kt
val androidAppIcons: List<AppIcon> = listOf(
    AppIcon(
        component = "com.hridoy.androidblog.MainActivity",
        foregroundResource = R.drawable.ic_launcher_foreground,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityA",
        foregroundResource = R.drawable.a,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityB",
        foregroundResource = R.drawable.b,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityC",
        foregroundResource = R.drawable.c,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityD",
        foregroundResource = R.drawable.d,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityE",
        foregroundResource = R.drawable.e,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityF",
        foregroundResource = R.drawable.f,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityG",
        foregroundResource = R.drawable.g,
    ),
    AppIcon(
        component = "com.hridoy.androidblog.MainActivityH",
        foregroundResource = R.drawable.h,
    ),

)

Step 7:

Enabling a Component setIcon:AppIconをpackageManagerEnabledする

AndroidManifest.xml ではクラス名だけを指定できますが、コンポーネント名を指定する場合は、パッケージ名を含む完全修飾クラス名を指定する必要があります。 COMPONENT_ENABLED_STATE_ENABLED 状態で setComponentEnabledSetting() を実行すると、ランチャーに新しいアプリ アイコンが表示されます。

setIcon.kt
private fun setIcon(context: Context, componentName: String) {
    val packageManager = context.packageManager

    androidAppIcons.filter {
        it.component != componentName
    }.forEach {
        packageManager.setComponentEnabledSetting(
            ComponentName(context, it.component),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP,
        )
    }

    packageManager.setComponentEnabledSetting(
        ComponentName(context, componentName),
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP,
    )
}

Step 8:

Compose View を作成する
https://gist.github.com/ihridoydas/25b8bceee5fa93c5977149f648fc8980

AppIconChangerScreen.kt
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AppIconChangerScreen(
    modifier: Modifier = Modifier,
    icons: List<AppIcon>,
) {
    Column(modifier = modifier.fillMaxSize()) {
        Spacer(modifier = Modifier.size(48.dp))

        Text(
            modifier = Modifier.fillMaxWidth(),
            text = "App Icon Change What you want!!",
            color = Color.LightGray,
            style = MaterialTheme.typography.headlineMedium,
            fontWeight = FontWeight.Bold,
            textAlign = TextAlign.Center,
        )

        Spacer(modifier = Modifier.size(48.dp))

        FlowRow(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp),
            horizontalArrangement = Arrangement.SpaceEvenly,
        ) {
            icons.forEach {
                AppIconOption(
                    modifier = Modifier
                        .padding(16.dp)
                        .size(54.dp),
                    appIcon = it,
                )
            }
        }
    }
}

@Composable
private fun AppIconOption(
    modifier: Modifier = Modifier,
    appIcon: AppIcon,
) {
    val context = LocalContext.current

    Image(
        modifier = modifier
            .drawBehind {
                drawCircle(color = Color(0xFF536972))
            }
            .clip(CircleShape)
            .clickable(
                enabled = true,
                onClick = {
                    setIcon(
                        context = context,
                        componentName = appIcon.component,
                    )
                },
            )
            .padding(all = 4.dp),
        painter = painterResource(id = appIcon.foregroundResource),
        contentDescription = null,
    )
}

@Preview(
    showBackground = true,
    showSystemUi = true,
)
@Composable
fun GreetingPreview() {
    AndroidBlogTheme {
        AppIconChangerScreen(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color(0xFF161D26)),
            icons = androidAppIcons,
        )
    }
}

結果:

Github サンプル

AppChanger.gif

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?