Flutter实战-自定义键盘(三)
注意:无特殊说明,flutter版本为3.0+
当初写该组件的时候flutter的版本是2.1,直到有一天flutter 升级到2.5版本后,发现键盘突然不能用了,发现是官方废弃了TestDefaultBinaryMessenger,
/// [WidgetTester], [TestWidgetsFlutterBinding], [TestDefaultBinaryMessenger],
/// and [TestDefaultBinaryMessenger.handlePlatformMessage] respectively).
///
/// To register a handler for a given message channel, see [setMessageHandler].
///
/// To send a message _to_ a plugin on the platform thread, see [send].
// TODO(ianh): deprecate this method once cocoon and other customer_tests are migrated:
// @NotYetDeprecated(
// 'Instead of calling this method, use ServicesBinding.instance.channelBuffers.push. '
// 'In tests, consider using tester.binding.defaultBinaryMessenger.handlePlatformMessage '
// 'or TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.handlePlatformMessage. '
// 'This feature was deprecated after v2.1.0-10.0.pre.'
// )
Future<void> handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback);
当时cool_ui的作者也没有发布新版本,那么如何解决这个问题呢,那就是重新写一个自己的BinaryMessenger。
一.定义自己的WidgetsFlutterBinding
不了解WidgetsFlutterBinding 的读者可以看我之前的文章,那首先我们要复写ServicesBingding,这个bingding是 用来处理flutter和原生交互用的
class MyWidgetsFlutterBinding extends WidgetsFlutterBinding with MyServicesBinding {
static WidgetsBinding? ensureInitialized() {
MyWidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
mixin MyServicesBinding on BindingBase, ServicesBinding {
@override
BinaryMessenger createBinaryMessenger() {
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
}
}
看源码中我们知道默认走的是系统的DefaultBinaryMessenger。
我们先看源码
class _DefaultBinaryMessenger extends BinaryMessenger {
const _DefaultBinaryMessenger._();
@override
Future<void> handlePlatformMessage(
String channel,
ByteData? message,
ui.PlatformMessageResponseCallback? callback,
) async {
ui.channelBuffers.push(channel, message, (ByteData? data) {
if (callback != null)
callback(data);
});
}
@override
Future<ByteData?> send(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
// TODO(ianh): Use ServicesBinding.instance once we have better diagnostics
// on that getter.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
@override
void setMessageHandler(String channel, MessageHandler? handler) {
if (handler == null) {
ui.channelBuffers.clearListener(channel);
} else {
ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
ByteData? response;
try {
response = await handler(data);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
callback(response);
}
});
}
}
}
这个也是实现了BinaryMessenger的方法,那我们的思路就是在默认方法中拦截并调用自己的方法
final Map<String, MessageHandler> _outboundHandlers = <String, MessageHandler>{};
@override
void setMessageHandler(String channel, MessageHandler? handler) {
if (handler != null && handler.toString().contains("_textInputHanlde")) {
_outboundHandlers[channel] = handler;
return;
}...
当含有_textInputHanlde的时候,将处理方法改成自己的,然后再send的时候调用。
@override
Future<ByteData?> send(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
final Future<ByteData?>? resultFuture;
final MessageHandler? handler = _outboundHandlers[channel];
if (handler != null) {
resultFuture = handler(message);
return resultFuture!;
}...
这样就实现了拦截。
二。使用
在Main函数中,runApp()之前调用以下方法
MyWidgetsFlutterBinding.ensureInitialized();
附属源码:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
class MyWidgetsFlutterBinding extends WidgetsFlutterBinding with MyServicesBinding {
static WidgetsBinding? ensureInitialized() {
MyWidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
mixin MyServicesBinding on BindingBase, ServicesBinding {
@override
BinaryMessenger createBinaryMessenger() {
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
}
}
class TestDefaultBinaryMessenger extends BinaryMessenger {
final BinaryMessenger origin;
TestDefaultBinaryMessenger(this.origin);
final Map<String, MessageHandler> _outboundHandlers = <String, MessageHandler>{};
@override
Future<ByteData?> send(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
final Future<ByteData?>? resultFuture;
final MessageHandler? handler = _outboundHandlers[channel];
if (handler != null) {
resultFuture = handler(message);
return resultFuture!;
}
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
@override
void setMessageHandler(String channel, MessageHandler? handler) {
if (handler != null && handler.toString().contains("_textInputHanlde")) {
_outboundHandlers[channel] = handler;
return;
}
if (handler == null) {
ui.channelBuffers.clearListener(channel);
} else {
ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
ByteData? response;
try {
response = await handler(data);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
callback(response);
}
});
}
}
@override
Future<void> handlePlatformMessage(
String channel,
ByteData? message,
ui.PlatformMessageResponseCallback? callback,
) async {
ui.channelBuffers.push(channel, message, (ByteData? data) {
if (callback != null) callback(data);
});
}
}