Flutter 入门指北(Part 5)之输入处理及实战

栏目: IOS · Android · 发布时间: 5年前

内容简介:(授权转载)微信码个蛋授权转载作者:这是我之前我预告要发的系列文章,昨天因为排版删了,没有原作者同意请勿二次转载。

(授权转载)微信码个蛋授权转载

Flutter 入门指北(Part 5)之输入处理及实战

作者:这是我之前我预告要发的系列文章,昨天因为排版删了,没有原作者同意请勿二次转载。

前面提到基础部件的时候,忘了提输入内容处理部件,这里补上,然后顺带撸个实际的界面吧

TextField

const TextField({
    Key key,    this.controller, // 定义一个 `TextEditingController` 实例,用来获取输入框内容等操作
    this.focusNode, // 定义一个 `FocusNode` 实例,判断当前输入框是否获取到焦点等操作
    this.decoration = const InputDecoration(), // 输入框样式,包括提醒字样,hint 等等
    TextInputType keyboardType, // 输入文本类型,例如 数字,email 等等
    this.textInputAction, // 键盘确认按钮的事件类型
    this.textCapitalization = TextCapitalization.none,    this.style, // 文字样式
    this.textAlign = TextAlign.start, // 对齐方式
    this.textDirection, // 文字方向
    this.autofocus = false, // 是否自动获取焦点
    this.obscureText = false, // 文字是否隐藏,多用于密码
    this.autocorrect = true, 
    this.maxLines = 1, //
    this.maxLength, // 最大长度
    this.maxLengthEnforced = true, // 设置最大长度后,输入内容超出后是否强制不给输入
    this.onChanged, // 输入内容发生变化时候的回调
    this.onEditingComplete, // 输入完毕的回调
    this.onSubmitted, // 提交内容的回调
    this.inputFormatters, // 
    this.enabled, // 是否可输入,false 不可输入
    this.cursorWidth = 2.0, // 游标宽度
    this.cursorRadius, // 游标半径
    this.cursorColor, // 游标颜色
    this.keyboardAppearance, // 该属性只在 iOS 设备有效
    this.scrollPadding = const EdgeInsets.all(20.0),    this.enableInteractiveSelection,    this.onTap, // 点击事件
  })

那么,简单的来个输入框示例吧,然后通过 Text 展示结果

class HomePage extends StatefulWidget {  @override
  _HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {  // 可以传入初始值
  TextEditingController _editController = TextEditingController();
  FocusNode _editNode = FocusNode();  // 保存按钮点击后的输入内容值
  String _content = '';  // 监听输入内容变化的内容值
  String _spyContent = '';  @override
  void initState() {    super.initState();    // 当输入框获取到焦点或者失去焦点的时候回调用
    _editNode.addListener(() {      print('edit has focus? => ${_editNode.hasFocus}');
    });
  }  @override
  void dispose() {    // 记得销毁,防止内存溢出
    _editController.dispose();
    _editNode.dispose();    super.dispose();
  }  @override
  Widget build(BuildContext context) {    return Scaffold(
      appBar: AppBar(
        title: Text('Input Content'),
      ),
      body: Container(
          padding: const EdgeInsets.symmetric(horizontal: 12.0),
          child: Column(
            children: <Widget>[
              TextField(
                controller: _editController,
                focusNode: _editNode,
                decoration: InputDecoration(
                    icon: Icon(Icons.phone_iphone, color: Theme.of(context).primaryColor),
                    labelText: '请输入手机号',
                    helperText: '手机号',
                    hintText: '手机号...在这儿输入呢'),
                keyboardType: TextInputType.number,                // 输入类型为数字类型
                textInputAction: TextInputAction.done,
                style: TextStyle(color: Colors.redAccent, fontSize: 18.0),
                textDirection: TextDirection.ltr,
                maxLength: 11, // 最大长度为 11
                maxLengthEnforced: true, // 超过长度的不显示
                onChanged: (v) { // 输入的内容发生改变会调用
                  setState(() => _spyContent = v);
                },
                onSubmitted: (s) { // 点击确定按钮时候会调用
                  setState(() => _spyContent = _editController.value.text);
                },
              ),
              Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: RaisedButton(
                      onPressed: () { 
                        // 获取输入的内容
                        setState(() => _content = _editController.value.text);                        // 清理输入内容
                        _editController.clear();
                        setState(() => _spyContent = '');
                      },
                      child: Text('获取输入内容'))),              // 展示输入的内容,点击按钮会显示
              Text(_content.isNotEmpty ? '获取到输入内容: $_content' : '还未获取到任何内容...'),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),                // 监听输入内容的变化,会跟随输入的内容进行改变
                child: Text('我是文字内容监听:$_spyContent'),
              )
            ],
          )),
    );
  }
}

这边需要提下的是 setState 方法,该方法只有 StatefulWidget 才有,当需要修改某个值的内容的时候,通过该方法进行修改,最后的效果图如下,当输入框文字发生变化的时候,监听的 Text 内容会随之改变,获取内容的 Text 当点击按钮了才发生变化

Flutter 入门指北(Part 5)之输入处理及实战

textField 展示1.png

Flutter 入门指北(Part 5)之输入处理及实战

textField 展示2.png

该部分代码查看 text_field_main.dart 文件

那么如果有个需求,在点击按钮的时候需要对输入的内容的合理性进行检测,当然可以通过 TextEditingController 的结果进行检测,但是还有个更加方便的方法,可以直接使用部件 TextFormField 来实现,不过需要我们在外层加一个 Form 部件,接下来,就要准备通过 TextFormField 来撸一个登录界面,但是这之前,前面有个坑需要先解决下

导入自定义的图标

在这之前,涉及到 Icon 部件,都是使用的系统自带的图标,那么如何导入第三方自定义图标呢,马上为你揭晓答案,首先我们需要打开「阿里妈妈」也就是 iconfont ,不知道的小伙伴通过链接打开,然后需要注册个账户,也可以直接通过 Github 等三方登录,然后就可以搜索我们需要的图标了,接下来需要撸一个登录,那我们就找一个 用户密码 的图标吧,选择喜欢的图标,然后鼠标放到图标会出现三个按钮,直接点击 购物车 那个按钮,然后就可以通过顶部的 购物车 按钮查看添加的图标,点击下载代码,把资源文件下载到本地。

Flutter 入门指北(Part 5)之输入处理及实战

iconfont.png

解压后,需要用到的文件有两个,别的可以忽略

  1. demo_index.html 这边用来查看图标的 unicode

  2. iconfont.ttf 这边就是图标资源文件了

回到项目,创建一个文件夹 fonts ,和 images 同级,将 iconfont.ttf 文件放到该文件夹下,然后打开 pubspec.ymal 文件,注册下导入的资源,可以自己命名 iconfont.ttf 文件名,便于自己发现就行,例如我命名为 third_part_icon.ttf ,在注册图片下面继续添加

fonts:  - family: ThirdPartIcons    fonts:    - asset: fonts/third_part_icon.ttf

注册完了记得点击 Package get ,否则会找不到资源。接着新建个 third_icons.dart 文件

import 'package:flutter/material.dart';class ThirdIcons {  // codePoint 值通过打开 `demo_index.html` 获取
  // 会在相应 icon 下带有相应的 code,把 `&#` 替换成 `0`,然后去掉最后的 `;` 即可
  // 例如  对应我们需要的图标就是 0xe672
  static const IconData username = ThirdIconData(0xe672);  static const IconData password = ThirdIconData(0xe62f);
}class ThirdIconData extends IconData {  // fontFamily 就是我们在 `pubspec.yaml` 中注册的 family 值
  const ThirdIconData(int codePoint) : super(codePoint, fontFamily: 'ThirdPartIcons');
}

接下来就可以通过该类导入需要的第三方图标了。

导入第三方插件

其实 Flutter 中缺少很多功能,需要通过导入第三方插件来实现功能,插件就是 Flutter 和原生交互的桥梁,也就是说,要写 Flutter 的插件,需要写 AndroidiOS 两端代码才可,否则只有在其中一个端能够实现功能。好在有很多现成的插件已经开源,可以通过 FlutterPackage 搜索到,例如等会我们会需要用到 FlutterToast 这个插件,用来做提醒用,在 FlutterPackage 中搜索到插件后,打开项目中的 pubspec.ymal 文件,在 dependencies 类目下将 fluttertoast 插件引入,如图:

Flutter 入门指北(Part 5)之输入处理及实战

插件导入展示.png

然后点击 Package get 让其导入即可,别的插件也是这样导入。做好准备工作,我们就可以撸一个登录界面了~

撸一个登录界面

在开撸之前,我们先看下最终的效果图吧,虽然是比较常用的界面(因为图都是自己搞的,比较丑请不要介意)

Flutter 入门指北(Part 5)之输入处理及实战

登录展示.png

Flutter 入门指北(Part 5)之输入处理及实战

注册展示.png

因为两个界面比较相似,所以这边只贴外层的代码和登录的代码,具体的代码,可以查看源码,已经推到 Github

void main() {
  runApp(LoginApp());  if (Platform.isAndroid) {    var style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
    SystemChrome.setSystemUIOverlayStyle(style);
  }
}/// 外层界面,包裹登录界面和注册界面,使用的都是前面讲过的,忘记可以查看之前的章节class LoginApp extends StatelessWidget {  @override
  Widget build(BuildContext context) {    return MaterialApp(
      title: 'Login Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.lightBlue),
      home: LoginHomePage(),
    );
  }
}class LoginHomePage extends StatefulWidget {  @override
  _LoginHomePageState createState() => _LoginHomePageState();
}class _LoginHomePageState extends State<LoginHomePage> with SingleTickerProviderStateMixin {
  TabController _tabController;  List<String> _pageIndicators = ['登录', '注册'];  List<Widget> _pages = [];  int _position = 0;  @override
  void initState() {    super.initState();
    _tabController = TabController(length: _pageIndicators.length, vsync: this);    // 将登录界面和注册界面添加到列表,用于放到 IndexStack 的 children 属性
    _pages..add(LoginPage())..add(RegisterPage());

    _tabController.addListener(() {      // 当 tab 切换的时候,联动 IndexStack 的 child 页面也进行修改,通过 setState 来修改值
      if (_tabController.indexIsChanging) setState(() => _position = _tabController.index);
    });
  }  @override
  void dispose() {    super.dispose();
  }  @override
  Widget build(BuildContext context) {    // 先忽略...
    return Theme(
        data: ThemeData(primarySwatch: Colors.pink, iconTheme: IconThemeData(color: Colors.pink)),
        child: Scaffold(
          body: Container(
            padding: const EdgeInsets.all(20.0),
            alignment: Alignment.center,
            decoration:
                BoxDecoration(image: DecorationImage(image: AssetImage('images/login_bg.png'), fit: BoxFit.cover)),            
            // 先忽略...下面会讲,主要是解决软键盘弹出的时候,界面内容会溢出的问题
            child: SingleChildScrollView(
              child: SafeArea(
                  child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[                // 顶部页面切换指示器,代码可以参考 `app_bar_main.dart` 文件
                TabBar(
                    indicatorSize: TabBarIndicatorSize.label,
                    controller: _tabController,
                    indicatorWeight: 4.0,
                    indicatorColor: Colors.white,                    // 返回 tab 列表
                    tabs: _pageIndicators
                        .map((v) => Text(v, style: TextStyle(color: Colors.white, fontSize: 24.0)))
                        .toList()),
                Padding(
                    padding: const EdgeInsets.only(top: 30.0),
                    child: SizedBox(                        // 切换界面列表
                        child: IndexedStack(children: _pages, index: _position),                        // 指定高度
                        height: MediaQuery.of(context).size.height / 2))
              ])),
            ),
          ),
        ));
  }
}
/// 登录界面class LoginPage extends StatefulWidget {  @override
  _LoginPageState createState() => _LoginPageState();
}class _LoginPageState extends State<LoginPage> {  // 用于后面判断表单内容是否有效
  GlobalKey<FormState> _formKey = GlobalKey();  // 用于获取输入框的内容
  TextEditingController _usernameController = TextEditingController();
  TextEditingController _passwordController = TextEditingController();  @override
  void initState() {    super.initState();
  }  @override
  void dispose() {    // 防止内存溢出,记得销毁..销毁..销毁
    _usernameController.dispose();
    _passwordController.dispose();    super.dispose();
  }

  _login() {    // 取消焦点
    FocusScope.of(context).requestFocus(FocusNode());    // 判断表单是否有效
    if (_formKey.currentState.validate()) {      // 获取输入框内容
      var username = _usernameController.value.text;      var password = _passwordController.value.text;      // 判断登录条件
      if (username == 'kuky' && password == '123456')        // 引入的三方插件方法,`Flutter` 没有自带的 `Taost`
        Fluttertoast.showToast(msg: '登录成功');      else
        Fluttertoast.showToast(msg: '登录失败');
    }
  }  @override
  Widget build(BuildContext context) {    return Form(        // 将 key 设置给表单,用于判断表单是否有效
        key: _formKey,
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 4.0),              // 表单输入框,参数同 TextField 基本类似
              child: TextFormField(
                controller: _usernameController,
                style: TextStyle(color: Colors.white, fontSize: 16.0),
                decoration: InputDecoration(
                    icon: Icon(ThirdIcons.username, size: 24.0, color: Colors.white),
                    labelText: '请输入用户名',
                    labelStyle: TextStyle(color: Colors.white),
                    helperStyle: TextStyle(color: Colors.white)),                // 有效条件(为空不通过,返回提示语,通过返回 null)
                validator: (value) => value.trim().isEmpty ? '用户名不能为空' : null,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 4.0),
              child: TextFormField(
                obscureText: true,
                controller: _passwordController,
                style: TextStyle(color: Colors.white, fontSize: 16.0),
                decoration: InputDecoration(
                    icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white),
                    labelText: '请输入密码',
                    labelStyle: TextStyle(color: Colors.white),
                    helperStyle: TextStyle(color: Colors.white)),
                validator: (value) => value.trim().length < 6 ? '密码长度不能小于6位' : null,
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 20.0),
              child: SizedBox(                // 主要用于使 RaisedButton 和上层容器同宽
                width: MediaQuery.of(context).size.width,
                child: RaisedButton(
                    color: Colors.pink,
                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
                    onPressed: _login,
                    child: Text(                      '登录',
                      style: TextStyle(color: Colors.white, fontSize: 20.0),
                    )),
              ),
            )
          ],
        ));
  }
}

撸完界面后,可以试下登录效果,如果输入框的内容,和 TextFormFieldvalidator 的条件不符合,则会显示错误文字的提示

Flutter 入门指北(Part 5)之输入处理及实战

登录错误展示.png

如果按照条件用户名为 kuky 密码为 123456 (条件可以根据自己进行修改)则会显示登录成功的逻辑

Flutter 入门指北(Part 5)之输入处理及实战

登录成功展示.png

以上代码查看 login_home_page.dart 文件

注册界面的逻辑和登录界面的逻辑几乎一样,算是第一次实战了,望小伙伴能够好好的写一遍

最后代码的地址还是要的:

  1. 文章中涉及的代码: demos


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Dreamweaver CS3 Bible

Dreamweaver CS3 Bible

Joseph W. Lowery / Wiley / May 21, 2007 / $49.99

Book Description Learn to create dynamic, data-driven Web sites using the exciting enhancements in the Dreamweaver CS3 version. You get a thorough understanding of the basics and then progress to l......一起来看看 《Dreamweaver CS3 Bible》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具