Flutter幾行代碼處理點(diǎn)擊空白關(guān)閉鍵盤及添加鍵盤ToolBar

上一篇:Flutter解決有輸入框的頁(yè)面豪治,點(diǎn)擊非輸入框部分自動(dòng)關(guān)閉鍵盤及鍵盤相關(guān)筆記

前言

在上一篇里接受了如何添加點(diǎn)擊空白頁(yè)面關(guān)閉鍵盤及展示鍵盤的ToolBar,現(xiàn)在對(duì)這兩個(gè)功能封裝成一個(gè)工具罢浇,讓需要有這兩個(gè)功能的頁(yè)面幾行代碼就能集成相關(guān)功能

使用步驟:

Step1: 準(zhǔn)備工作
BlankToolBarModel blankToolBarModel = BlankToolBarModel();

void initState() {
   // Step1.1: 焦點(diǎn)變化時(shí)的響應(yīng)
   blankToolBarModel.outSideCallback = focusNodeChange;
   super.initState();
 }
 // Step1.2: 焦點(diǎn)變化時(shí)的響應(yīng)操作
 void focusNodeChange(){
   setState(() {});
 }

@override
 void dispose() {
   // Step1.3: 在銷毀頁(yè)面時(shí)取消監(jiān)聽(tīng)
   blankToolBarModel.removeFocusListeners();
   super.dispose();
 }
Step2: 由tool提供FocusNode創(chuàng)建TextField
// 創(chuàng)建輸入行
 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創(chuàng)建body
@override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: Text('登錄'),),
     // Step4 用tool創(chuàng)建body
     body: BlankToolBarTool.blankToolBarWidget(
           context,
           model:blankToolBarModel,
           body:xxx這里真正創(chuàng)建的body展示內(nèi)容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: 響應(yīng)空白處的焦點(diǎn)的Node
  BlankToolBarModel blankToolBarModel = BlankToolBarModel();
  @override
  void initState() {
    // Step2.1: 焦點(diǎn)變化時(shí)的響應(yīng)
    blankToolBarModel.outSideCallback = focusNodeChange;
    super.initState();
  }
  // Step2.2: 焦點(diǎn)變化時(shí)的響應(yīng)操作
  void focusNodeChange(){
    setState(() {});
  }

  @override
  void dispose() {
    // Step3: 在銷毀頁(yè)面時(shí)取消監(jiān)聽(tīng)
    blankToolBarModel.removeFocusListeners();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('登錄'),),
      // Step4 用tool創(chuàng)建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: '請(qǐng)輸入用戶名',icon: Icons.people),
        SizedBox(height: 30),
        createInputText(pwdController,hint: '請(qǐng)輸入密碼',icon: Icons.power,obscureText:true),
        SizedBox(height: 30),
        createInputText(codeController,hint: '請(qǐng)輸驗(yàn)證碼',icon: Icons.nature,obscureText:true),
        SizedBox(height: 30),
        FlatButton(color: Colors.blue,child: Text('登錄'),onPressed: checkLogin,)
      ],
    );
  }

  // 創(chuàng)建輸入行
  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 設(shè)置FocusNode
            focusNode: focusNode,
          );

    List<Widget> rowList = [];
    // 輸入框前的提示圖標(biāo)
    rowList.add(SizedBox(width: 10));
    rowList.add(Icon(icon));
    // 輸入框
    rowList.add(Expanded(child: textField));
    
              
    return Row(children: rowList);
  }

  // 點(diǎn)擊登錄處理
  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 {
  // 點(diǎn)擊空白部分用于響應(yīng)的FocusNode
  FocusNode blankModel=FocusNode();
  // 保存頁(yè)面中所有InputText綁定的FocusNode
  Map<String,ToolBarModel> focusNodeMap={};

  FocusNode _currentEditingNode;
  // 用于外側(cè)的回調(diào)
  VoidCallback outSideCallback;
  BlankToolBarModel({this.outSideCallback});

  /**
   * 通過(guò)一個(gè)key獲取node,一般是通過(guò)TextEditingController對(duì)象的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;
  }
  /**
   * 通過(guò)controller獲取focusNode
   */
  FocusNode getFocusNodeByController(TextEditingController controller){
    String key = controller.hashCode.toString();
    return getFocusNode(key);
  }
  /**
   * 找到正處于編輯狀態(tài)的FocusNode
   */
  FocusNode findEditingNode(){
    for(ToolBarModel barModel in focusNodeMap.values){
      if(barModel.focusNode.hasFocus){
        return barModel.focusNode;
      }
    }
    return null;
  }
  // 監(jiān)聽(tīng)FocusNode變化
  Future<Null> focusNodeListener() async {
      FocusNode editingNode = findEditingNode();
      if(_currentEditingNode != editingNode){
        _currentEditingNode = editingNode;
        print('>>>>>>>>+++++++++++');
        if(outSideCallback != null){
          outSideCallback();
        }
      }else{
        print('>>>>>>>>----------');
      }
      
  }
  /// 移除所有監(jiān)聽(tīng)
  void removeFocusListeners(){
    for(ToolBarModel barModel in focusNodeMap.values){
        barModel.focusNode.removeListener(focusNodeListener); 
      }
  }
  /// 關(guān)閉鍵盤
  void closeKeyboard(BuildContext context){
    FocusScope.of(context).requestFocus(blankModel);
  }
}
/**
 * 增加
 * 1沐祷、自動(dòng)處理點(diǎn)擊空白頁(yè)面關(guān)閉鍵盤嚷闭,
 * 2、鍵盤上方增加一個(gè)toolbar
 */
class BlankToolBarTool{
  static Widget blankToolBarWidget(
    // 上下文
    BuildContext context,
    {
      // 數(shù)據(jù)model
      BlankToolBarModel model,
      // 要展示的子內(nèi)容
      Widget body,
      // 是否展示toolBar
      bool showToolBar = true,
      // 默認(rèn)的toolBar的高度
      double toolBarHeight = 40,
      // toolBar的背景色
      Color toolBarColor = const Color(0xffeeeeee),
      // toolBar的可點(diǎn)擊按鈕的顏色
      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: (){
                      // 點(diǎn)擊空白處的處理
                      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){
      // 沒(méi)有任何輸入框處于編輯狀態(tài)赖临,則返回的是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){
    // 有輸入框在編輯狀態(tài)
    int currentIndex = barModel.index;
    bool isFirst = currentIndex==0;
    bool isLast = currentIndex==(focusNodeMap.length-1);
    // 前一個(gè)
    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),// 旋轉(zhuǎn)的角度
        origin: Offset(10,10),
        child: preIcon
      ),
      onTap: (){
        focusNodeAtIndex(currentIndex-1);
      },
    );
    // 下一個(gè)
    Widget nextBtn = InkWell(
      child:Icon(Icons.arrow_forward_ios,
          color:isLast?Colors.grey:tintColor,
          size: 20,),
      onTap:(){
        focusNodeAtIndex(currentIndex+1);
      },
      );

    // 關(guān)閉
    // Widget doneBtn = CupertinoButton(
    //   child: Container(height: 40,width: 200,child: Text('關(guān)閉')),
    //   onPressed: doneCallback
    // );
    Widget doneBtn = InkWell(
      child: Text('關(guān)閉',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
        ],
      ),
    );
  }
  // 獲取當(dāng)前獲得焦點(diǎn)的對(duì)象
  ToolBarModel currentEditingFocusNode(){
      for(ToolBarModel barModel in focusNodeMap.values){
        if(barModel.focusNode.hasFocus){
          return barModel;
        }
      }
      return null;
  }
  /// 讓指定的某個(gè)node獲得焦點(diǎn)
  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;
        }
      }
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胞锰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兢榨,更是在濱河造成了極大的恐慌嗅榕,老刑警劉巖顺饮,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凌那,居然都是意外死亡兼雄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門案怯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)君旦,“玉大人澎办,你說(shuō)我怎么就攤上這事嘲碱。” “怎么了局蚀?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵麦锯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我琅绅,道長(zhǎng)扶欣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任千扶,我火速辦了婚禮料祠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澎羞。我一直安慰自己髓绽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布妆绞。 她就那樣靜靜地躺著顺呕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪括饶。 梳的紋絲不亂的頭發(fā)上株茶,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音图焰,去河邊找鬼启盛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛技羔,可吹牛的內(nèi)容都是我干的驰徊。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼堕阔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棍厂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起超陆,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牺弹,失蹤者是張志新(化名)和其女友劉穎浦马,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體张漂,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晶默,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了航攒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磺陡。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漠畜,靈堂內(nèi)的尸體忽然破棺而出币他,到底是詐尸還是另有隱情,我是刑警寧澤憔狞,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布蝴悉,位于F島的核電站,受9級(jí)特大地震影響瘾敢,放射性物質(zhì)發(fā)生泄漏拍冠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一簇抵、第九天 我趴在偏房一處隱蔽的房頂上張望庆杜。 院中可真熱鬧,春花似錦碟摆、人聲如沸晃财。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拓劝。三九已至,卻和暖如春嘉裤,著一層夾襖步出監(jiān)牢的瞬間郑临,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工屑宠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厢洞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓典奉,卻偏偏與公主長(zhǎng)得像躺翻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卫玖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容