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

如何通过EventChannel将macOS原生NSColorPanel的颜色选择流传输至Flutter?解决macos_ui插件中颜色选择事件无法回调的问题

解决macOS原生颜色选择器NSColorPanel的事件流式传输到Flutter的问题

我仔细看了你的代码,发现核心问题是事件传递的链路没有打通——你在ColorPanelProvider里实现了FlutterStreamHandler但并没有把它注册到Flutter的EventChannel上,它的eventSink根本没法把事件传给Flutter端。另外还有几处细节问题导致颜色变化事件无法正确触发,下面一步步给你修正:

问题拆解

  1. 重复的StreamHandler实现:MacOSUiPlugin已经被注册为EventChannel的StreamHandler,但ColorPanelProvider也实现了这个协议,却没有和Flutter通道关联,导致它的eventSink调用完全无效。
  2. 通知注册重复:每次调用startStream都会添加新的通知观察者,会导致颜色变化时多次触发回调。
  3. NSColorPanel配置顺序错误:isContinuous应该在打开面板前设置,否则连续颜色变化的事件不会触发。

修正后的代码实现

第一步:重构ColorPanelProvider

去掉多余的FlutterStreamHandler实现,改用闭包回调的方式把颜色变化传递给MacOSUiPlugin:

import Cocoa

class ColorPanelProvider: NSObject {
    let colorPanel = NSColorPanel.shared
    var colorChangeCallback: ((String) -> Void)?
    
    func openPanel() {
        // 先配置面板属性,再打开
        colorPanel.isContinuous = true
        colorPanel.setTarget(self)
        colorPanel.setAction(#selector(colorDidChange))
        colorPanel.makeKeyAndOrderFront(self)
        
        // 发送初始颜色
        sendCurrentColor()
    }
    
    @objc private func colorDidChange() {
        sendCurrentColor()
    }
    
    private func sendCurrentColor() {
        let hexColor = colorPanel.color.asFlutterHexString
        print("currentColor: \(hexColor)")
        colorChangeCallback?(hexColor)
    }
    
    // 清理资源
    func cleanUp() {
        colorPanel.setTarget(nil)
        colorPanel.setAction(nil)
        colorChangeCallback = nil
    }
}

extension NSColor {
    var asFlutterHexString: String {
        let red = Int(round(self.redComponent * 0xFF))
        let green = Int(round(self.greenComponent * 0xFF))
        let blue = Int(round(self.blueComponent * 0xFF))
        return String(format: "0xFF%02X%02X%02X", red, green, blue)
    }
}

第二步:修正MacOSUiPlugin

让它负责管理EventChannel的流,并把ColorPanelProvider的颜色变化回调和eventSink关联:

import Cocoa
import FlutterMacOS

public class MacOSUiPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
    private let colorPanelProvider: ColorPanelProvider
    private var eventSink: FlutterEventSink?

    init(colorPanelProvider: ColorPanelProvider) {
        self.colorPanelProvider = colorPanelProvider
        super.init()
        // 设置颜色变化回调,关联到eventSink
        self.colorPanelProvider.colorChangeCallback = { [weak self] hexColor in
            self?.eventSink?(hexColor)
        }
    }

    public static func register(with registrar: FlutterPluginRegistrar) {
        let methodChannel = FlutterMethodChannel(
            name: "dev.groovinchip.macos_ui",
            binaryMessenger: registrar.messenger)
        let eventChannel = FlutterEventChannel(
            name: "dev.groovinchip.macos_ui/color_panel",
            binaryMessenger: registrar.messenger)
        let colorPanelProvider = ColorPanelProvider()
        let instance = MacOSUiPlugin(colorPanelProvider: colorPanelProvider)
        eventChannel.setStreamHandler(instance)
        registrar.addMethodCallDelegate(instance, channel: methodChannel)
    }

    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "color_panel":
            colorPanelProvider.openPanel()
            result(true)
        default:
            result(FlutterMethodNotImplemented)
        }
    }

    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        print("listening to color panel events")
        eventSink = events
        // 如果面板已经打开,发送当前颜色
        if colorPanelProvider.colorPanel.isVisible {
            colorPanelProvider.sendCurrentColor()
        }
        return nil
    }

    public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        eventSink = nil
        colorPanelProvider.cleanUp()
        return nil
    }
}

关键修正点说明

  • 闭包替代双StreamHandler:用colorChangeCallback把ColorPanelProvider的颜色变化事件传递给MacOSUiPlugin,再通过后者的eventSink发送到Flutter,保证链路唯一且有效。
  • 正确配置NSColorPanel:把isContinuous = true放在打开面板前,这样拖动颜色滑块时会连续触发colorDidChange事件,符合流式传输的需求。
  • 清理资源:在onCancel里调用cleanUp,避免内存泄漏,同时取消面板的target和action。
  • 初始颜色发送:打开面板时和监听流时都发送当前颜色,保证Flutter端能拿到初始值。

Flutter端接收示例

你可以在Flutter端这样监听EventChannel:

final _colorPanelEventChannel = EventChannel('dev.groovinchip.macos_ui/color_panel');

StreamSubscription? _colorSubscription;

void startListening() {
  _colorSubscription = _colorPanelEventChannel.receiveBroadcastStream().listen((hexColor) {
    // 处理收到的颜色,比如转换成Color对象
    final color = Color(int.parse(hexColor));
    print('Received color: $color');
  });
}

// 记得在适当的时候取消订阅
void stopListening() {
  _colorSubscription?.cancel();
}

这样修改后,NSColorPanel的颜色变化就能实时流式传递到Flutter端了。

内容的提问来源于stack exchange,提问作者GroovinChip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 19:47:47