Flutter几行代码处理点击空白关闭键盘及添加键盘ToolBar

上一篇:Flutter解决有输入框的页面,点击非输入框部分自动关闭键盘及键盘相关笔记

前言

在上一篇里接受了如何添加点击空白页面关闭键盘及展示键盘的ToolBar,现在对这两个功能封装成一个工具,让需要有这两个功能的页面几行代码就能集成相关功能

使用步骤:

Step1: 准备工作
BlankToolBarModel blankToolBarModel = BlankToolBarModel();

void initState() {
   // Step1.1: 焦点变化时的响应
   blankToolBarModel.outSideCallback = focusNodeChange;
   super.initState();
 }
 // Step1.2: 焦点变化时的响应操作
 void focusNodeChange(){
   setState(() {});
 }

@override
 void dispose() {
   // Step1.3: 在销毁页面时取消监听
   blankToolBarModel.removeFocusListeners();
   super.dispose();
 }
Step2: 由tool提供FocusNode创建TextField
// 创建输入行
 Widget createInputText(TextEditingController controller){
   // Step5.1 由controller获得FocusNode
   FocusNode focusNode = blankToolBarModel.getFocusNodeByController(controller);
   // 输入框
   TextField textField = TextField(
           controller: controller,
           keyboardType: TextInputType.text,
           focusNode: focusNode,
         );
return textField;
}
Step3: 用tool创建body
@override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: Text('登录'),),
     // Step4 用tool创建body
     body: BlankToolBarTool.blankToolBarWidget(
           context,
           model:blankToolBarModel,
           body:xxx这里真正创建的body展示内容xxxx
       ),
   );
 }

完整例子:

import 'package:BlankToolBarTool.dart';
import 'package:flutter/material.dart';

class LoginPage5 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return LoginPage5State();
  }
}

class LoginPage5State extends State<LoginPage5>{
  TextEditingController nameController = TextEditingController();
  TextEditingController pwdController = TextEditingController();
  TextEditingController codeController = TextEditingController();
  // Step1: 响应空白处的焦点的Node
  BlankToolBarModel blankToolBarModel = BlankToolBarModel();
  @override
  void initState() {
    // Step2.1: 焦点变化时的响应
    blankToolBarModel.outSideCallback = focusNodeChange;
    super.initState();
  }
  // Step2.2: 焦点变化时的响应操作
  void focusNodeChange(){
    setState(() {});
  }

  @override
  void dispose() {
    // Step3: 在销毁页面时取消监听
    blankToolBarModel.removeFocusListeners();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('登录'),),
      // Step4 用tool创建body
      body: BlankToolBarTool.blankToolBarWidget(
            context,
            model:blankToolBarModel,
            body:createBody()
        ),
    );
  }

  Widget createBody(){
    return ListView(
      padding: EdgeInsets.only(left: 20,right: 20),
      children: <Widget>[
        SizedBox(height: 30),
        createInputText(nameController,hint: '请输入用户名',icon: Icons.people),
        SizedBox(height: 30),
        createInputText(pwdController,hint: '请输入密码',icon: Icons.power,obscureText:true),
        SizedBox(height: 30),
        createInputText(codeController,hint: '请输验证码',icon: Icons.nature,obscureText:true),
        SizedBox(height: 30),
        FlatButton(color: Colors.blue,child: Text('登录'),onPressed: checkLogin,)
      ],
    );
  }

  // 创建输入行
  Widget createInputText(TextEditingController controller,{obscureText: false,String hint,IconData icon}){
    // Step5.1 由controller获得FocusNode
    FocusNode focusNode = blankToolBarModel.getFocusNodeByController(controller);
    // 输入框
    TextField textField = TextField(
            controller: controller,
            keyboardType: TextInputType.text,
            decoration: InputDecoration(
              contentPadding: EdgeInsets.all(10.0),
              hintText: hint,
            ),
            obscureText: obscureText,
            // Step5.2 设置FocusNode
            focusNode: focusNode,
          );

    List<Widget> rowList = [];
    // 输入框前的提示图标
    rowList.add(SizedBox(width: 10));
    rowList.add(Icon(icon));
    // 输入框
    rowList.add(Expanded(child: textField));
    
              
    return Row(children: rowList);
  }

  // 点击登录处理
  void checkLogin(){
    print(nameController.text);
    print(pwdController.text);
    print(codeController.text);
  }

}
自定义toolbar.gif

工具类BlankToolBarTool.dart

import 'ToolBar.dart';
import 'package:flutter/material.dart';

/// 用于持有FocusNode的类
class BlankToolBarModel {
  // 点击空白部分用于响应的FocusNode
  FocusNode blankModel=FocusNode();
  // 保存页面中所有InputText绑定的FocusNode
  Map<String,ToolBarModel> focusNodeMap={};

  FocusNode _currentEditingNode;
  // 用于外侧的回调
  VoidCallback outSideCallback;
  BlankToolBarModel({this.outSideCallback});

  /**
   * 通过一个key获取node,一般是通过TextEditingController对象的hashCode
   * TextEditingController nickNameController = TextEditingController();
   * String key = nickNameController.hashCode.toString();
   * FocusNode focusNode = blankToolBarModel.getFocusNode(key);
   */
  FocusNode getFocusNode(String key){
    ToolBarModel barModel = focusNodeMap[key];
     if(barModel == null){
       barModel = ToolBarModel(index: focusNodeMap.length,focusNode: FocusNode());
       barModel.focusNode.addListener(focusNodeListener);
       focusNodeMap[key] = barModel;
     }
    return barModel.focusNode;
  }
  /**
   * 通过controller获取focusNode
   */
  FocusNode getFocusNodeByController(TextEditingController controller){
    String key = controller.hashCode.toString();
    return getFocusNode(key);
  }
  /**
   * 找到正处于编辑状态的FocusNode
   */
  FocusNode findEditingNode(){
    for(ToolBarModel barModel in focusNodeMap.values){
      if(barModel.focusNode.hasFocus){
        return barModel.focusNode;
      }
    }
    return null;
  }
  // 监听FocusNode变化
  Future<Null> focusNodeListener() async {
      FocusNode editingNode = findEditingNode();
      if(_currentEditingNode != editingNode){
        _currentEditingNode = editingNode;
        print('>>>>>>>>+++++++++++');
        if(outSideCallback != null){
          outSideCallback();
        }
      }else{
        print('>>>>>>>>----------');
      }
      
  }
  /// 移除所有监听
  void removeFocusListeners(){
    for(ToolBarModel barModel in focusNodeMap.values){
        barModel.focusNode.removeListener(focusNodeListener); 
      }
  }
  /// 关闭键盘
  void closeKeyboard(BuildContext context){
    FocusScope.of(context).requestFocus(blankModel);
  }
}
/**
 * 增加
 * 1、自动处理点击空白页面关闭键盘,
 * 2、键盘上方增加一个toolbar
 */
class BlankToolBarTool{
  static Widget blankToolBarWidget(
    // 上下文
    BuildContext context,
    {
      // 数据model
      BlankToolBarModel model,
      // 要展示的子内容
      Widget body,
      // 是否展示toolBar
      bool showToolBar = true,
      // 默认的toolBar的高度
      double toolBarHeight = 40,
      // toolBar的背景色
      Color toolBarColor = const Color(0xffeeeeee),
      // toolBar的可点击按钮的颜色
      Color toolBarTintColor = Colors.blue
    }
  ){
    if(!showToolBar){
      return GestureDetector(
              onTap: (){
                model.closeKeyboard(context);
              },
              child: body,
            );
    }
    return Stack(
         children: <Widget>[
           Positioned(top: 0,left: 00,bottom: 0,right: 0,child: 
            GestureDetector(
              onTap: (){
                model.closeKeyboard(context);
              },
              child: body,
            ),
           ),
           Positioned(top: 0,left: 0,bottom: 0,right: 0,child: 
            ToolBar(height: toolBarHeight,
                    color: toolBarColor,
                    tintColor: toolBarTintColor,
                    focusNodeMap: model.focusNodeMap,
                    doneCallback: (){
                      // 点击空白处的处理
                      model.closeKeyboard(context);
            },)
           ),
         ],
      );
  }
}

键盘toolbar管理类ToolBar.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;

class ToolBarModel {
  int index;
  FocusNode focusNode;
  ToolBarModel({this.index,this.focusNode});
}
class ToolBar extends StatefulWidget {
  Map <String,ToolBarModel> focusNodeMap;
  VoidCallback doneCallback;
  double height=40;
  Color color = Color(0xffeeeeee);
  Color tintColor = Colors.blue;

  ToolBar({this.focusNodeMap,this.doneCallback,this.height=40,this.color = const Color(0xffeeeeee),this.tintColor = Colors.blue});

  @override
  State<StatefulWidget> createState() {
    return ToolBarState(focusNodeMap: focusNodeMap,
        doneCallback: doneCallback,
        height: height,
        color: color,
        tintColor: tintColor);
  }
}
class ToolBarState extends State<ToolBar>{
  Map <String,ToolBarModel> focusNodeMap;
  VoidCallback doneCallback;
  double height=40;
  Color color = Color(0xffeeeeee);
  Color tintColor = Colors.blue;
  ToolBarState({this.focusNodeMap,this.doneCallback,this.height=40,this.color = const Color(0xffeeeeee),this.tintColor = Colors.blue});
  @override
  Widget build(BuildContext context) {
    ToolBarModel barModel = currentEditingFocusNode();
    if(barModel == null){
      // 没有任何输入框处于编辑状态,则返回的是0高度的容器
      return Column(children: <Widget>[
               Flexible(child: Container()),
               Container(height: 0)
           ],
      );
    }else{
      return Column(children: <Widget>[
               Flexible(child: Container()),
               createToolBar(barModel)
           ],
      );
    }
  }
  Widget createToolBar(ToolBarModel barModel){
    // 有输入框在编辑状态
    int currentIndex = barModel.index;
    bool isFirst = currentIndex==0;
    bool isLast = currentIndex==(focusNodeMap.length-1);
    // 前一个
    Widget preIcon = Icon(Icons.arrow_forward_ios, 
      color: isFirst?Colors.grey:tintColor,size: 20.0,);
    Widget preBtn = InkWell(
      child:Transform(
        transform: Matrix4.identity()..rotateZ(math.pi),// 旋转的角度
        origin: Offset(10,10),
        child: preIcon
      ),
      onTap: (){
        focusNodeAtIndex(currentIndex-1);
      },
    );
    // 下一个
    Widget nextBtn = InkWell(
      child:Icon(Icons.arrow_forward_ios,
          color:isLast?Colors.grey:tintColor,
          size: 20,),
      onTap:(){
        focusNodeAtIndex(currentIndex+1);
      },
      );

    // 关闭
    // Widget doneBtn = CupertinoButton(
    //   child: Container(height: 40,width: 200,child: Text('关闭')),
    //   onPressed: doneCallback
    // );
    Widget doneBtn = InkWell(
      child: Text('关闭',style: TextStyle(color: tintColor),),
      onTap: doneCallback
    );
    
    return Container(
      height: height,color: color,
      padding: EdgeInsets.only(left: 10,right: 10),
      child: Row(
        children: <Widget>[
          preBtn,
          SizedBox(width: 40,),
          nextBtn,
          Flexible(child: Container(),),
          doneBtn
        ],
      ),
    );
  }
  // 获取当前获得焦点的对象
  ToolBarModel currentEditingFocusNode(){
      for(ToolBarModel barModel in focusNodeMap.values){
        if(barModel.focusNode.hasFocus){
          return barModel;
        }
      }
      return null;
  }
  /// 让指定的某个node获得焦点
  void focusNodeAtIndex(int selectIndex){
    if(selectIndex<0||selectIndex>=focusNodeMap.length){
      return;
    }
    for(ToolBarModel barModel in focusNodeMap.values){
        if(selectIndex == barModel.index){
          barModel.focusNode.requestFocus();
          setState(() {
            
          });
          return;
        }
      }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容