如何在Flutter/Android/iOS实现类似Pocket的自定义分享接收界面
Android原生实现
核心思路是用透明主题的Activity接收分享Intent,直接展示自定义界面,而非依赖悬浮窗。这也是Pocket等应用的实现方式,无需申请SYSTEM_ALERT_WINDOW权限。
1. 配置Manifest与主题
在AndroidManifest.xml中注册处理分享的Activity,添加intent-filter并设置透明主题:
<activity android:name=".ShareReceiveActivity" android:theme="@style/TransparentShareActivity" android:launchMode="singleTop" android:excludeFromRecents="true"> <!-- 处理单内容分享 --> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> <data android:mimeType="image/*" /> </intent-filter> <!-- 处理多内容分享 --> <intent-filter> <action android:name="android.intent.action.SEND_MULTIPLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> </intent-filter> </activity>
在res/values/styles.xml中定义透明主题:
<style name="TransparentShareActivity" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> </style>
2. 处理分享数据并展示自定义界面
在ShareReceiveActivity中解析Intent数据,弹出自定义的分享处理面板(比如BottomSheet):
class ShareReceiveActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_transparent_container) intent?.let { intent -> when (intent.action) { Intent.ACTION_SEND -> { val text = intent.getStringExtra(Intent.EXTRA_TEXT) val mediaUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) showCustomSharePanel(text, mediaUri) } Intent.ACTION_SEND_MULTIPLE -> { val mediaUris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM) showCustomMultiSharePanel(mediaUris) } } } } private fun showCustomSharePanel(text: String?, mediaUri: Uri?) { val bottomSheet = CustomShareBottomSheet.newInstance(text, mediaUri) bottomSheet.show(supportFragmentManager, "CustomShareSheet") // 面板关闭时结束Activity bottomSheet.setOnDismissListener { finish() } } }
CustomShareBottomSheet是自定义的Fragment,包含保存、编辑、取消等操作按钮,处理分享内容。
iOS原生实现
iOS通过Share Extension接收分享请求,再跳转到主应用展示自定义界面(Extension内存有限,复杂界面建议放在主应用)。
1. 配置Share Extension
创建Share Extension Target,修改Info.plist支持的分享类型:
<key>NSExtension</key> <dict> <key>NSExtensionAttributes</key> <dict> <key>NSExtensionActivationRule</key> <dict> <key>NSExtensionActivationSupportsText</key> <true/> <key>NSExtensionActivationSupportsImageWithMaxCount</key> <integer>10</integer> </dict> </dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.share-services</string> </dict>
2. 扩展中获取数据并跳转主应用
在ShareViewController中解析分享内容,通过App Groups共享数据,再跳转主应用:
import Social import UIKit class ShareViewController: SLComposeServiceViewController { private var sharedData: [String] = [] private let appGroupID = "group.com.your.app.id" override func viewDidLoad() { super.viewDidLoad() fetchSharedContent() } private func fetchSharedContent() { guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let attachments = extensionItem.attachments else { return } for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier("public.text") { attachment.loadItem(forTypeIdentifier: "public.text") { [weak self] item, _ in if let text = item as? String { self?.sharedData.append(text) self?.openMainApp() } } } else if attachment.hasItemConformingToTypeIdentifier("public.image") { attachment.loadItem(forTypeIdentifier: "public.image") { [weak self] item, _ in guard let self = self, let sourceURL = item as? URL else { return } let destURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: self.appGroupID)! .appendingPathComponent("shared_\(UUID().uuidString).jpg") try? FileManager.default.copyItem(at: sourceURL, to: destURL) self.sharedData.append(destURL.path) self.openMainApp() } } } } private func openMainApp() { let dataString = sharedData.joined(separator: "|") guard let encodedData = dataString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "yourapp://share?data=\(encodedData)") else { return } extensionContext?.open(url) { success in if success { self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } } } }
3. 主应用接收数据并展示自定义界面
在SceneDelegate中监听URL Scheme,解析数据后弹出自定义界面:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let url = URLContexts.first?.url, url.scheme == "yourapp", url.host == "share", let dataString = url.queryParameters?["data"]?.removingPercentEncoding else { return } let dataItems = dataString.split(separator: "|").map(String.init) let shareVC = CustomShareViewController(data: dataItems) shareVC.modalPresentationStyle = .overFullScreen shareVC.modalTransitionStyle = .crossDissolve (scene as? UIWindowScene)?.windows.first?.rootViewController?.present(shareVC, animated: true) }
CustomShareViewController是自定义的控制器,包含分享处理的UI逻辑。
Flutter实现
Flutter通过Platform Channel对接原生逻辑,分别处理Android和iOS的分享请求:
1. Android端对接
按照Android原生方案配置透明Activity,通过MethodChannel将分享数据传递给Flutter:
// 在ShareReceiveActivity中 private val channel = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, "share_receive") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val flutterEngine = FlutterEngine(this) flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) setContentView(R.layout.activity_flutter_container) val flutterView = findViewById<FlutterView>(R.id.flutter_view) flutterView.attachToFlutterEngine(flutterEngine) // 传递分享数据 val data = mutableMapOf<String, Any?>() intent.getStringExtra(Intent.EXTRA_TEXT)?.let { data["text"] = it } intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { data["mediaUri"] = it.toString() } channel.invokeMethod("onShareReceived", data) }
2. iOS端对接
按照iOS原生方案配置Share Extension跳转主应用,使用app_links插件监听URL Scheme:
import 'package:app_links/app_links.dart'; final AppLinks _appLinks = AppLinks(); @override void initState() { super.initState(); _appLinks.uriLinkStream.listen((Uri uri) { if (uri.scheme == "yourapp" && uri.host == "share") { final dataString = uri.queryParameters['data']!; final dataItems = dataString.split("|"); // 显示自定义分享界面 showModalBottomSheet( context: context, builder: (_) => CustomShareBottomSheet(data: dataItems), ); } }); }
3. Flutter自定义界面
用Flutter Widget实现分享处理面板:
class CustomShareBottomSheet extends StatelessWidget { final List<String> data; const CustomShareBottomSheet({super.key, required this.data}); @override Widget build(BuildContext context) { return Container( height: 300, padding: const EdgeInsets.all(16), child: Column( children: [ Text("收到分享内容: ${data.join(", ")}"), const SizedBox(height: 20), ElevatedButton(onPressed: () => _saveShare(), child: const Text("保存")), ElevatedButton(onPressed: () => Navigator.pop(context), child: const Text("取消")), ], ), ); } void _saveShare() { // 处理保存逻辑 } }
演示素材参考
- Android:录制从微信/QQ分享文本/图片,应用直接弹出自定义底部面板的操作视频,或截图展示透明背景上的自定义界面。
- iOS:录制从Safari/相册分享内容,跳转到主应用后弹出自定义分享界面的视频。
- Flutter:跨平台演示,同时展示Android和iOS端的效果对比。
内容的提问来源于stack exchange,提问作者nain

