Flutter插件开发之物理音量按键监听
Flutter 2.0 来啦,我还是很兴奋的,因为它让我感受到了 Google 对做大做强 Flutter 的决心。但今天想记录的是如何实现物理音量按键监听的 Flutter 插件,之前介绍过 Flutter 与原生的通信。这里在加深一下两者通信方式的印象。另外: 对 Flutter 2 感兴趣的可以去官网围观。
当时写这个插件就是为了实现,点击物理音量键 + - 实现小说翻页的功能。
Android 端物理音量按键的监听
这里思路简单,我用一个变量 interceptKeyDownEnabled 控制当前是否响应按键监听事件,重写 Android 的 onKeyDown 方法,当检测到点击物理音量按键时,通过 EventChannel.EventSink 把按键事件传到 Flutter 端,以此实现监听通信。
变量定义
注意这里 CHANNELENABLEPATH,CHANNELEVENTPATH 定义的值必须和 Flutter 端定义的通道一致。
//用一个变量控制是否启用监听音量按键private var interceptKeyDownEnabled: Boolean = false;private val CHANNEL_ENABLE_PATH: String = "volume_channel_enable_path_method";private val CHANNEL_EVENT_PATH: String = "volume_channel_path_event";//监听按键的Eventvar eventSink: EventChannel.EventSink? = null
初始化
重写 configureFlutterEngine 方法,进行通道初始化。
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)GeneratedPluginRegistrant.registerWith(flutterEngine);MethodChannel(flutterEngine.dartExecutor, CHANNEL_ENABLE_PATH).setMethodCallHandler { call, result ->when (call.method) {"setVolumeListenEnable" -> {val map = call.arguments as HashMap<*, *>//控制是否响应监听interceptKeyDownEnabled = map["state"] as Boolean;// 如果成功返回true,失败返回falseresult.success(true)// result.error("ERROR_CODE", "error message", null)}else -> {// 未实现result.notImplemented()}}};EventChannel(flutterEngine?.dartExecutor?.binaryMessenger, CHANNEL_EVENT_PATH).setStreamHandler(object : EventChannel.StreamHandler {override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {eventSink = events!!}override fun onCancel(arguments: Any?) {eventSink=null;}})}
重写 onKeyDown 事件方法
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {if(interceptKeyDownEnabled){// keydown 拦截事件when (keyCode) {KeyEvent.KEYCODE_VOLUME_DOWN -> {eventSink?.success("volumeDown")return true}KeyEvent.KEYCODE_VOLUME_UP -> {eventSink?.success("volumeUp")return true}}//进行 按键事件拦截事件return true;}return super.onKeyDown(keyCode, event)}
Android 端的编码至此完结。
IOS 端音量按键监听
IOS 端我找了好久,没有发现类似 Android 重写按键监听事件的方法。所以我换种思路,通过监听音量大小的变化来判断用户点击了音量+ 还是 -的操作。
定义变量
//监听当前音量变化var currentVolume:Float?var eventSink: FlutterEventSink?//是否监听var listenEnable=false;
初始化
在 AppDelegate.swift 的 application 中进行初始化。
let controller : FlutterViewController = window?.rootViewController as! FlutterViewControllerlet enableChannel = FlutterMethodChannel.init(name: "volume_channel_enable_path_method",binaryMessenger: controller.binaryMessenger);let eventChannel = FlutterEventChannel(name: "volume_channel_path_event", binaryMessenger: controller.binaryMessenger)eventChannel.setStreamHandler(self);enableChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in// Handle battery messages.if call.method == "setVolumeListenEnable" {self.setVolumeListenEnable(call: call,result: result)}else {result(FlutterMethodNotImplemented);}})
// eventChannel.setStreamHandler(self);extension AppDelegate: FlutterStreamHandler {public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {self.eventSink=events;return nil}public func onCancel(withArguments arguments: Any?) -> FlutterError? {return nil}}
然后紧接着在application中调用 initAndLoadVolum 先把 当前音量传到 Flutter 端。
//初始化,首次获取音量initAndLoadVolum();
//注册监听func initAndLoadVolum() {do{try AVAudioSession.sharedInstance().setActive(true)}catch let error as NSError{print("\(error)")}currentVolume = AVAudioSession.sharedInstance().outputVolume//初次启动的时候把当前音量传递至Flutter 端进行赋值self.eventSink?(currentVolume);NotificationCenter.default.addObserver(self, selector: #selector(self.changeVolumSlider), name: NSNotification.Name(rawValue: "AVSystemController_SystemVolumeDidChangeNotification"), object: nil)UIApplication.shared.beginReceivingRemoteControlEvents()}
IOS 音量变化监听
func changeVolumSlider(notifi:NSNotification) {if let volum:Float = notifi.userInfo?["AVSystemController_AudioVolumeNotificationParameter"] as! Float?{//返回if !self.listenEnable {return;}self.eventSink?(volum);}}deinit {NotificationCenter.default.removeObserver(self)UIApplication.shared.endReceivingRemoteControlEvents()}func setVolumeListenEnable(call: FlutterMethodCall,result: @escaping FlutterResult) {let dic: Dictionary<String, Any> = call.arguments as! Dictionary<String, Any>self.listenEnable=dic["state"] as! Bool;}
Flutter 端实现
通过定义 MethodChannel 和 EventChannel 通道分别调用,监听 Android,IOS 端的方法,和传回来的值。
通道定义
const String VolumeChanelEnableName = "volume_channel_enable_path_method";const String VolumeChanelEventName = "volume_channel_path_event";
通道初始化
static const MethodChannel _volume_channel =const MethodChannel(VolumeChanelEnableName);static const EventChannel _volume_event_channel =const EventChannel(VolumeChanelEventName);
调用方法,监听方法的实现
调用方法没啥说的,监听方法,因为 Android 端和 IOS 端我传过来的值是不同类型的所以区分一下平台。
//音量控制监听相关static void setVolumeListenEnable(bool isStop) async {await _volume_channel.invokeMethod("setVolumeListenEnable", {"state": isStop});}
//监听,此处因为Ios没有直接监听音量按键的点击响应事件,所以我改为直接监听Ios变化情况,//但IOS除了物理按键,也可以从控制中心改变音量,//所以如果要在实际中运用,可以设置点时间间隔控制。static void onVolumeListen(ValueChanged<String> onKeyDownEvent) {double _currentValue = -100.0;_volume_event_channel.receiveBroadcastStream().listen((dynamic msgData) {print("volume ---->" + msgData.toString());if (Platform.isAndroid) {onKeyDownEvent(msgData);return;}if (Platform.isIOS) {if (_currentValue == -100.0) {//接收到的初始化值_currentValue = msgData;return;}if (msgData > _currentValue) {onKeyDownEvent("volumeUp");} else if (msgData < _currentValue) {onKeyDownEvent("volumeDown");} else if (msgData == 1.0) {onKeyDownEvent("volumeUp");} else if (msgData == 0.0) {onKeyDownEvent("volumeDown");}_currentValue = msgData;}}, onError: (Object error) {print("volume:error---->" + error.toString());});}
插件的运用
调用 setVolumeListenEnable 方法启用或者关闭监听,onVolumeListen 注册监听。
@overridevoid initState() {// TODO: implement initStatesuper.initState();VolumePlugin.setVolumeListenEnable(true);VolumePlugin.onVolumeListen((value) {if (mounted) {setState(() {_content = value;});}});}
void dispose() {// TODO: implement disposesuper.dispose();VolumePlugin.setVolumeListenEnable(false);}
完整的代码和效果图我就不贴了,这篇看下来,我觉得你对 Flutter 和原生端通信会更加深刻。
