Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Flutter实战一Flutter聊天应用(十四)

$
0
0

优化输入体验

在进行下一步之前,我们先优化一下注册的体验:

  • 正在输入注册信息时,点击屏幕空白部分,清除当前文本输入框的焦点,同时收起键盘。
  • 正在输入注册信息时,直接收起键盘,再点击空白部分,清除当前文本输入框的焦点。
  • 不在用户输入时直接判断并显示错误提示信息,而是在用户输入完成以及点击加入按钮时判断并显示错误提示信息。
  • 在每一个输入框下方都显示帮助信息,提示用户输入什么内容。

首先我们把SignUpState类的_correctUsername_correctUsername变量改为bool类型。并把用户名和密码的判断逻辑合并到_checkInput方法中。

class SignUpState extends State<SignUp> {
  bool _correctPhone = true;
  bool _correctUsername = true;
  bool _correctPassword = true;
  //...
  void _checkInput() {
    if (_phoneController.text.isNotEmpty &&
        (_phoneController.text.trim().length < 7 ||
            _phoneController.text.trim().length > 12)) {
      _correctPhone = false;
    } else {
      _correctPhone = true;
    }
    if (_usernameController.text.isNotEmpty &&
        _usernameController.text.trim().length < 2) {
      _correctUsername = false;
    } else {
      _correctUsername = true;
    }
    if (_passwordController.text.isNotEmpty &&
        _passwordController.text.trim().length < 6) {
      _correctPassword = false;
    } else {
      _correctPassword = true;
    }
    setState(() {});
  }
  //...
}

因为我们将使用手机号作为用户的唯一ID存储在数据库中,所以在上面的代码中,我们增加了一个_correctPhone变量与判断手机号长度的逻辑。

SignUpState类的build里添加helperText属性,它能在文本框下方显示输入帮助信息。使用onSubmitted代替onChanged属性,它会在用户点击键盘上的完成按钮时触发事件,也就是上面合并修改的_checkInput方法。

class SignUpState extends State<SignUp> {
          //...
                      new TextField(
                        controller: _phoneController,
                        keyboardType: TextInputType.phone,
                        decoration: new InputDecoration(
                          helperText: 'Your unique ID.',
                          hintText: 'Phone',
                          errorText: _correctPhone
                              ? null
                              : 'phone length is 7 to 12 bits.',
                          icon: new Icon(
                            Icons.phone,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
                      new TextField(
                        controller: _usernameController,
                        decoration: new InputDecoration(
                          helperText: "What's your name?",
                          hintText: 'Username',
                          errorText: _correctUsername
                              ? null
                              : 'Username length is less than 2 bits.',
                          icon: new Icon(
                            Icons.account_circle,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
                      new TextField(
                        controller: _passwordController,
                        obscureText: true,
                        keyboardType: TextInputType.number,
                        decoration: new InputDecoration(
                          helperText: 'Your landing password.',
                          hintText: 'Password',
                          errorText: _correctPassword
                              ? null
                              : 'Password length is less than 6 bits.',
                          icon: new Icon(
                            Icons.lock_outline,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
          //...
}

然后我们需要在把有背景图片的Container控件包装在GestureDetector控件中,再添加点击事件,清除文本输入框的焦点,然后判断输入内容。

class SignUpState extends State<SignUp> {
  //...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: new Stack(children: <Widget>[
      new Opacity(
          opacity: 0.3,
          child: new GestureDetector(
              onTap: () {
                FocusScope.of(context).requestFocus(new FocusNode());
                _checkInput();
              },
              child: new Container(
                decoration: new BoxDecoration(
                  image: new DecorationImage(
                    image: new ExactAssetImage('images/sign_up_background.jpg'),
                    fit: BoxFit.cover,
                  ),
                ),
              ))),
      //...
  }
  //...
}

最后我们在用户点击加入按钮提交注册信息之前,先清除文本输入框的焦点并判断输入内容。

class SignUpState extends State<SignUp> {
  //...
  Future _handleSubmitted() async {
    FocusScope.of(context).requestFocus(new FocusNode());
    _checkInput();
    //...
  }
  //...
}

添加等待动画

用户在提交注册请求后,需要等待服务端返回请求结果,这一等待时间会根据当前网络的因素或长或短。所以我们现在要增加一个等待动画,使用CircularProgressIndicator控件实现,其效果是一个不断转动的圆形。在等待期间我们不需要用户有其他操作,因此需要一个新屏幕。

class ShowAwait extends StatefulWidget {
  ShowAwait(this.requestCallback);
  final Future<int> requestCallback;

  @override
  _ShowAwaitState createState() => new _ShowAwaitState();
}

class _ShowAwaitState extends State<ShowAwait> {
  @override
  initState() {
    super.initState();
    new Timer(new Duration(seconds: 2), () {
      widget.requestCallback.then((int onValue) {
        Navigator.of(context).pop(onValue);
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new CircularProgressIndicator(),
    );
  }
}

上面代码中,在initState方法中有一个Timer类,它能设置定时任务,定时执行一次或者每隔一段时间执行一次,我们在这里设置的是定时2秒执行一次传入requestCallbac方法。同时requestCallbac方法需要返回一个int值,该值用于返回服务端的处理结果。执行完毕后使用Navigator.of(context).pop返回结果并关闭等待屏幕。

提交注册信息

然后回到sign_up.dart文件,我们要开始写服务端的代码,关于如何使用Firebase数据库,可以查看《Flutter进阶—Firebase数据库实例》,我们保存用户信息的数据名称是users,因此需要先连接上Firebase数据库。

class SignUpState extends State<SignUp> {
  //...
  final reference = FirebaseDatabase.instance.reference().child('users');
  //...
}

现在我们修改一下_userLogUp方法,在注册之前要先验证用户注册的手机号码是否已经存在,如果已经被注册,则返回0。确定当前手机号码未被注册之后,才提交用户注册信息到数据库,并返回1

class SignUpState extends State<SignUp> {
  //...
  Future<int> _userLogUp(String username, String password,
      {String email, String phone}) async {
    return await reference
        .child(_phoneController.text)
        .once()
        .then((DataSnapshot onValue) {
      if (onValue.value == null) {
        reference.child(phone).set({
          'name': username,
          'password': password,
          'email': email,
          'phone': phone,
        });
        return 1;
      } else {
        return 0;
      }
    });
  }
  //...
}

再修改一下_handleSubmitted方法,在用户点击注册按钮时弹出ShowAwait创建的等待屏幕,并把上面的_userLogUp方法传给ShowAwait实例。如果返回0,则提示用户,如果返回1,则返回并传递用户的手机号、密码到登陆屏幕。

class SignUpState extends State<SignUp> {
  //...
  void _handleSubmitted() {
    FocusScope.of(context).requestFocus(new FocusNode());
    _checkInput();
    if (_usernameController.text == '' || _passwordController.text == '') {
      showMessage(context, "The registration information is incomplete!");
      return;
    } else if (!_correctUsername || !_correctPassword || !_correctPhone) {
      showMessage(context, "The registration information is incomplete!");
      return;
    }
    showDialog<int>(
        context: context,
        barrierDismissible: false,
        child: new ShowAwait(_userLogUp(
            _usernameController.text, _passwordController.text,
            email: _emailController.text,
            phone: _phoneController.text))).then((int onValue) {
      if (onValue == 0) {
        showMessage(context, "This number has been registered!");
      } else if (onValue == 1) {
        Navigator
            .of(context)
            .pop([_phoneController.text, _passwordController.text]);
      }
    });
  }
  //...
}

这里写图片描述

作者:hekaiyou 发表于2017/7/22 23:53:11 原文链接
阅读:186 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles