Flutter 给iOS键盘添加工具栏
标签:
编程学习
Flutter学习
老方法
先看一下我写的工具栏代码
class InputDoneView extends StatefulWidget {
final bool isShowPaste;
final VoidCallBack pasteCallBack;
final VoidCallBack doneCallBack;
InputDoneView({this.isShowPaste = false, this.pasteCallBack, this.doneCallBack});
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _InputDoneViewState();
}
}
class _InputDoneViewState extends State<InputDoneView> {
bool _isShowing = true;
_changeShowing() {
_isShowing = !_isShowing;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return AnimatedCrossFade(
duration: Duration(milliseconds: 180),
crossFadeState: _isShowing ? CrossFadeState.showFirst : CrossFadeState.showSecond,
firstChild: Container(
height: 44,
width: MediaQuery.of(context).size.width,
color: ColorConstant.COLOR_LIGHT_GRAY_COLOR,
child: Row(
children: <Widget>[
widget.isShowPaste ? Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(top: 1.0, bottom: 1.0),
child: CupertinoButton(
padding:
const EdgeInsets.only(left: 20.0, top: 2.0, bottom: 2.0),
onPressed: () {
_changeShowing();
///粘贴回调
if(widget.pasteCallBack != null) {
widget.pasteCallBack();
}
},
child: Text(
S.of(context).paste_string,
style: TextStyle(
color: ColorConstant.LIGHT_WHITE,
fontWeight: FontWeight.bold,
),
),
),
),
),
) : SizedBox(width: 0,),
Expanded(
child: Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(top: 1.0, bottom: 1.0),
child: CupertinoButton(
padding:
const EdgeInsets.only(right: 20.0, top: 2.0, bottom: 2.0),
onPressed: () {
_changeShowing();
if(widget.doneCallBack != null) {
widget.doneCallBack();
}
FocusScope.of(context).requestFocus(FocusNode());
},
child: Text(
S.of(context).done_string,
style: TextStyle(
color: ColorConstant.LIGHT_WHITE,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
],
),
),
secondChild: Container(),
);
}
}
我使用了一个轮子 keyboard_visibility 进行键盘的状态监听,当然也可以自己写个小插件,在这里来控制工具栏的展示和消失
KeyboardVisibilityNotification().addNewListener(
onChange: (bool visible) {
print('--==-=-==-=---==-=-=-=-=-$visible');
},
onShow: () {
print('--==-=-==-=---==-=-=-=-=-show');
if (_isShowPaste) {
_showOverlay(context);
}
},
onHide: () {
if (_overlayEntry != null) {
//让键盘下落
FocusScope.of(context).requestFocus(FocusNode());
}
_removeOverlay();
print('--==-=-==-=---==-=-=-=-=-hide');
},
);
///显示Done按钮
_showOverlay(BuildContext context) {
if (_overlayEntry != null) return;
OverlayState overlayState = Overlay.of(context);
_overlayEntry = OverlayEntry(builder: (context) {
return Positioned(
bottom: MediaQuery.of(context).viewInsets.bottom,
right: 0.0,
left: 0.0,
child: Material(
child: InputDoneView(),
),
);
});
overlayState.insert(_overlayEntry);
}
///移除Done按钮
_removeOverlay() {
if (_overlayEntry != null) {
_overlayEntry.remove();
_overlayEntry = null;
}
}
这样写可以将就用了但是有些地方需要手动去让工具栏消失,这里用事件总线来控制
eventBus.on(EventBusTitle.EVENT_BUS_HIDE, (arg) {
_removeOverlay();
});
这里使用的事件总线 event bus 代码(一定确定自己的项目是否需要事件总线,如果不需要尽量不要用,虽然使用简单,但是后期还是比较麻烦的)
class EventBusName {
///登录
static const String EVENT_BUS_LOGIN = 'event_bus_login';
}
///订阅者回调签名
typedef void EventCallback(dynamic arg);
class EventBus {
//私有构造函数
EventBus._internal();
//保存单例
static EventBus _singleton = EventBus._internal();
//工厂构造函数
factory EventBus() => _singleton;
//保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列
var _emap = Map<Object, List<EventCallback>>();
//粘性事件 key:事件名,value: arg参数
var _semap = Map<Object, Object>();
//添加订阅者
EventCallback on(eventName, EventCallback f) {
debugPrint('-=-=-=-=-=-=-=-=-=-=-=-=添加监听--------$eventName');
if (eventName == null || f == null) return null;
_emap[eventName] ??= new List<EventCallback>();
_emap[eventName].add(f);
if (_semap.containsKey(eventName)) {
f(_semap[eventName]);
}
return f;
}
eventCount(eventName) {
if(_emap.containsKey(eventName)) {
var list = _emap[eventName];
if(list != null) {
return list.length;
} else {
return 0;
}
}
return 0;
}
//移除订阅者
///这里有坑
void off(eventName, [EventCallback f]) {
debugPrint('-=-=-=-=-=-=-=-=-=-=-=-=移除监听--------$eventName');
var list = _emap[eventName];
if (eventName == null || list == null) return;
if (f == null) {
_emap[eventName] = null;
} else {
list.remove(f);
}
}
///移除所有
void removeAll() {
debugPrint('-=-=-=-=-=-=-=-=-=-=-=-=移除所有监听');
if (_emap == null || _emap.length == 0) return;
_emap = Map<Object, List<EventCallback>>();
}
//触发事件,事件触发后该事件所有订阅者会被调用
void emit(eventName, [arg]) {
debugPrint('-=-=-=-=-=-=-=-=-=-=-=-=发送监听--------$eventName');
var list = _emap[eventName];
if (list == null) return;
int len = list.length - 1;
//反向遍历,防止在订阅者在回调中移除自身带来的下标错位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
//发送粘性消息,使在其之后的订阅者也能收到
void emitSticky(eventName, [arg]) {
emit(eventName, arg);
if (eventName == null) return null;
_semap[eventName] = arg;
}
//移除粘性消息,避免继续传递
void removeSticky(eventName){
if(_semap.containsKey(eventName))
_semap.remove(eventName);
}
}
///定义一个top-level变量,页面引入该文件后可以直接使用bus
var eventBus = EventBus();
新方法
其实去年在使用老方法之前我就已经发现有别人的轮子 keyboard_actions 可以用了,但是当时项目已经做好,急于上线,而当时基于轮子的使用方法比较复杂,对于项目改动较大,就没有直接套用, 后来发现新版本的使用起来已经简单了很多
///键盘落下
keyboardDown(BuildContext context) {
FocusScope.of(context).requestFocus(FocusNode());
}
class KeyboardWidget extends StatelessWidget {
final KeyBoardController controller;
final Widget child;
final bool isDialog;
KeyboardWidget({this.controller, this.child, this.isDialog = false})
: assert(controller != null);
@override
Widget build(BuildContext context) {
if(Platform.isAndroid){
return child;
}
return KeyboardActions(
tapOutsideToDismiss: false,
isDialog: isDialog,
config: KeyboardActionsConfig(
// nextFocus: controller.isMultiply,
nextFocus: false,
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
actions: controller.nodes
.map((e) => KeyboardAction(
focusNode: e,
// displayArrows: controller.isMultiply,
displayArrows: false,
onTapAction: () {
controller.down();
}))
.toList(),
),
child: child,
);
}
}
class KeyBoardController {
final BuildContext context;
final List<FocusNode> nodes = List();
bool get isMultiply => nodes.length > 1;
KeyBoardController({this.context}) : assert(context != null);
addNode(FocusNode node) {
if (!nodes.contains(node)) {
nodes.add(node);
}
}
clearNodes() {
nodes.clear();
}
down() {
FocusScope.of(context).requestFocus(FocusNode());
}
}
extension FocusNodeExtension on FocusNode {
FocusNode attachController(KeyBoardController controller) {
controller.addNode(this);
return this;
}
}
使用的时候只需要将 KeyboardWidget 嵌套在将要使用的组件外围即可。