Jetpack Composeでアプリのアイコンを希望に合わせて変更できる
Android アプリのアイコンをプログラムで変更する。
最近、𝕏 Blue ユーザーがアプリのアイコンを変更できることが発表されました。 Reddit アプリでも同様の機能が提供されているため、この機能は新しいものではありません。
実装は特に難しいものではないので、ここで共有したいと思います。
AndroidManifest.xml で activity-alias 要素を宣言することで、複数のランチャー アイコンを作成できます。 アプリのアイコンを変更すると、現在のアイコンを無効にして新しいアイコンを有効にすることで効果が得られます。
設定:
*こちらでMainActivity
一つActivity使用してます
AndroidManifest.xml
にactivity-alias
要素を宣言する
Step 1:
android:launchMode="singleTop"
にする
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.AndroidBlog">
</activity>
Step 2:
複数のランチャー アイコンを作成
Step 3:
values/strings.xml
に<string name="labelName">App Label Name</string>
します
<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
<!--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
data class AppIcon(
val component: String,
@DrawableRes
val foregroundResource: Int,
)
Step 6:
AndroidAppIcons.kt
AppIconのListを作成
https://gist.github.com/ihridoydas/ba0a4b63bb0978b347feeff3c15f1594
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をpackageManager
Enabledする
AndroidManifest.xml ではクラス名だけを指定できますが、コンポーネント名を指定する場合は、パッケージ名を含む完全修飾クラス名を指定する必要があります。 COMPONENT_ENABLED_STATE_ENABLED 状態で setComponentEnabledSetting() を実行すると、ランチャーに新しいアプリ アイコンが表示されます。
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
@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,
)
}
}