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";
//监听按键的Event
var 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,失败返回false
result.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! FlutterViewController
let 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 注册监听。
@override
void initState() {
// TODO: implement initState
super.initState();
VolumePlugin.setVolumeListenEnable(true);
VolumePlugin.onVolumeListen((value) {
if (mounted) {
setState(() {
_content = value;
});
}
});
}
void dispose() {
// TODO: implement dispose
super.dispose();
VolumePlugin.setVolumeListenEnable(false);
}
完整的代码和效果图我就不贴了,这篇看下来,我觉得你对 Flutter 和原生端通信会更加深刻。