You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:08:10