如何在Flutter应用内实现Launcher Icon(应用启动图标)自定义?
Flutter实现应用启动图标自定义方案
首先要明确:系统桌面的应用启动图标无法在运行时直接动态修改,因为这由Android/iOS的系统规则限制,只能通过预配置多套图标,让用户选择后切换应用的图标配置来实现类似效果。结合你常用的flutter_launcher_icons包,具体实现步骤如下:
一、预生成多套启动图标
用flutter_launcher_icons为不同图标样式生成对应平台资源:
- 在项目根目录创建多个图标源文件,比如
assets/icons/icon_default.png、assets/icons/icon_custom1.png、assets/icons/icon_custom2.png - 修改
pubspec.yaml配置,为每套图标单独指定生成规则:flutter_launcher_icons: android: true ios: true image_path: "assets/icons/icon_default.png" flavors: custom1: image_path: "assets/icons/icon_custom1.png" custom2: image_path: "assets/icons/icon_custom2.png" - 执行命令生成全平台图标资源:
生成后,Android的flutter pub run flutter_launcher_icons:mainres/mipmap-*目录、iOS的Assets.xcassets中会自动生成对应尺寸的多套图标文件。
二、Android平台配置图标切换
Android通过activity-alias实现多图标切换逻辑:
- 打开
android/app/src/main/AndroidManifest.xml,在主Activity外添加多个activity-alias节点,每个对应一套自定义图标:<!-- 主Activity保留默认启动配置 --> <activity android:name=".MainActivity" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 自定义图标1的启动入口 --> <activity-alias android:name=".CustomIcon1" android:enabled="false" android:exported="true" android:icon="@mipmap/ic_launcher_custom1" android:label="@string/app_name_custom1" android:targetActivity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity-alias> <!-- 自定义图标2的启动入口 --> <activity-alias android:name=".CustomIcon2" android:enabled="false" android:exported="true" android:icon="@mipmap/ic_launcher_custom2" android:label="@string/app_name_custom2" android:targetActivity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity-alias> - 通过MethodChannel在Flutter和Android原生间通信,切换
activity-alias的启用状态:
原生Kotlin代码示例:class MainActivity : FlutterActivity() { private val CHANNEL = "com.your_app/icon_switch" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "switchIcon" -> { val targetIcon = call.argument<String>("iconType") ?: "default" switchLauncherIcon(targetIcon) result.success(true) } else -> result.notImplemented() } } } private fun switchLauncherIcon(iconType: String) { // 先禁用所有启动入口 packageManager.setComponentEnabledSetting( ComponentName(this, MainActivity::class.java), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ) packageManager.setComponentEnabledSetting( ComponentName(this, ".CustomIcon1"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ) packageManager.setComponentEnabledSetting( ComponentName(this, ".CustomIcon2"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ) // 启用目标图标对应的入口 when(iconType) { "default" -> packageManager.setComponentEnabledSetting( ComponentName(this, MainActivity::class.java), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ) "custom1" -> packageManager.setComponentEnabledSetting( ComponentName(this, ".CustomIcon1"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ) "custom2" -> packageManager.setComponentEnabledSetting( ComponentName(this, ".CustomIcon2"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ) } } } - Flutter层调用代码:
static const MethodChannel _iconChannel = MethodChannel('com.your_app/icon_switch'); Future<void> switchLauncherIcon(String iconType) async { await _iconChannel.invokeMethod('switchIcon', {'iconType': iconType}); // 提示用户重启应用生效 if (Platform.isAndroid) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('图标已切换,请重启应用生效')) ); } }
三、iOS平台配置图标切换
iOS通过系统原生的Alternate App Icons实现:
- 打开
ios/Runner/Info.plist,添加CFBundleAlternateIcons配置:<key>CFBundleIcons</key> <dict> <key>CFBundlePrimaryIcon</key> <dict> <key>CFBundleIconFiles</key> <array> <string>AppIcon</string> </array> </dict> <key>CFBundleAlternateIcons</key> <dict> <key>CustomIcon1</key> <dict> <key>CFBundleIconFiles</key> <array> <string>AppIcon-Custom1</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> <key>CustomIcon2</key> <dict> <key>CFBundleIconFiles</key> <array> <string>AppIcon-Custom2</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> </dict> </dict> - 通过MethodChannel实现Flutter与iOS原生的通信:
原生Swift代码示例:import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { let CHANNEL = "com.your_app/icon_switch" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [weak self] call, result in guard call.method == "switchIcon" else { result(FlutterMethodNotImplemented) return } if let args = call.arguments as? [String: String], let iconType = args["iconType"] { self?.switchLauncherIcon(iconType: iconType, result: result) } else { result(FlutterError(code: "INVALID_ARG", message: "缺少图标类型参数", details: nil)) } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func switchLauncherIcon(iconType: String, result: FlutterResult) { guard UIApplication.shared.supportsAlternateIcons else { result(FlutterError(code: "NOT_SUPPORTED", message: "设备不支持图标切换", details: nil)) return } let iconIdentifier: String? = iconType == "default" ? nil : iconType UIApplication.shared.setAlternateIconName(iconIdentifier) { error in if let error = error { result(FlutterError(code: "SWITCH_FAILED", message: error.localizedDescription, details: nil)) } else { result(true) } } } } - Flutter层复用Android的调用代码即可,iOS切换后会自动弹出系统提示。
四、用户交互界面实现
在Flutter应用内添加图标选择入口,比如设置页的列表选项:
Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("选择应用图标", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ListTile( leading: Image.asset("assets/icons/icon_default.png", width: 40), title: const Text("默认图标"), onTap: () => switchLauncherIcon("default"), ), ListTile( leading: Image.asset("assets/icons/icon_custom1.png", width: 40), title: const Text("自定义图标1"), onTap: () => switchLauncherIcon("custom1"), ), ListTile( leading: Image.asset("assets/icons/icon_custom2.png", width: 40), title: const Text("自定义图标2"), onTap: () => switchLauncherIcon("custom2"), ), ], )
注意事项
- Android切换图标后必须重启应用才能生效,需主动提示用户
- iOS的Alternate Icons功能仅支持真机,模拟器可能无法正常显示切换效果
- 所有图标源文件需符合平台尺寸规范,
flutter_launcher_icons会自动生成适配各分辨率的图标
内容的提问来源于stack exchange,提问作者Guim
相关产品推荐
相关产品推荐

