Flutter 实现登录页

版权声明:本文为博主原创文章,如需转载,请注明出处 https://blog.csdn.net/zhuangch/article/details/86239114

登录页几乎是每个联网app必备的界面,下面以我工作中开发的百卓优采云进销存app软件的登录页为例使用Flutter来实现,具体效果图如下:

在这里插入图片描述
界面看起来很简单,但麻雀虽小五脏俱全,使用到了实际开发中所需的大多数控件,下面让我们开启实现之旅,首先我们先实现上面的banner,实现之前我们先做好准备工作,把界面中需要的图片资源导入,具体步骤如下:

  1. lib同级目录下创建目录assets/login/,将所需图片资源放入该目录,最终目录结构为
    在这里插入图片描述
  2. 编辑pubspec.yaml , 将图片列表加入assets,编辑该文件时出现错误时,请检查前面缩进的空格在这里插入图片描述

准备工作做好后,我们来一步一步实现登录界面,先上骨架代码

void main() => runApp(AbizApp ());

class AbizApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Title',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:
      //AppFuncBrowse(),
      LoginPage(),
    );
  }
}
class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() {
    // TODO: implement createState
    return _LoginPageState();
  }
}
class _LoginPageState extends State<LoginPage> {
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            _buildTopBannerWidget(),
          ],
        ),
    );
  }
}

顶部banner实现很简单,就是显示一张图片, Image有fit属性,用来控制图片的显示方式,具体对应的值建议大家都亲自试一试来加深理解,我就不再赘述了

_buildTopBannerWidget() {
    return Container(
      child: Image.asset(
        "assets/login/login_banner.png",
        fit: BoxFit.cover,
      ),
    );
  }

中国制造网登录的账号提示,很简单,我就不再废话,直接上代码

  _buildAccountLoginTip() {
    return Padding(
      padding: EdgeInsets.all(15),
      child: Text(
        "百卓采购网/中国制造网会员登录",
        maxLines: 1,
        textAlign: TextAlign.start,
        style: TextStyle(fontSize: 16, color: Colors.black54),
      ),
    );
  }

下面实现关键部分,用户名和密码输入框,上代码

_buildEditWidget() {
    return Container(
      margin: EdgeInsets.only(left: 15, right: 15),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(4.0),
        border: Border.all(
            width: 1.0 / MediaQuery.of(context).devicePixelRatio,
            color: Colors.grey.withOpacity(0.5)),
      ),
    );
  }

flutter中控件度量的单位是逻辑像素,和iOS中点的概念一样,为了使边框是1px宽,我们必须获取1个逻辑像素代表几个物理像素(px),MediaQuery.of(context).devicePixelRatio就是我们需要的,MediaQuery.of(context)返回MediaQueryData,MediaQueryData里包含屏幕大小(逻辑大小),padding等信息。

下面我们来实现用户名的输入框

class _LoginPageState extends State<LoginPage> {
  TextEditingController _pwdEditController;
  TextEditingController _userNameEditController;

  final FocusNode _userNameFocusNode = FocusNode();
  final FocusNode _pwdFocusNode = FocusNode();

  @override
  void initState() {
    super.initState();

    _pwdEditController = TextEditingController();
    _userNameEditController = TextEditingController();
    _pwdEditController.addListener(() => setState(() => {}));
    _userNameEditController.addListener(() => setState(() => {}));
  }
  // 此处省略其他代码
}

_buildLoginNameTextField() {
    return TextField(
      controller: _userNameEditController,
      focusNode: _userNameFocusNode,
      decoration: InputDecoration(
          hintText: "登录名/邮箱/手机",
          border: InputBorder.none,
          prefixIcon: Image.asset(
            "assets/login/user_name.png",
            fit: BoxFit.none,
          ),
          suffixIcon: (_userNameEditController.text ?? "").isEmpty
              ? IconButton(
                  icon: Image.asset(
                    "assets/login/qrcode_login.png",
                    fit: BoxFit.cover,
                  ),
                  onPressed: () => {},
                )
              : IconButton(
                  icon: Icon(
                    Icons.cancel,
                    color: Colors.grey,
                  ),
                  onPressed: () {
                    _userNameEditController.clear();
                    _userNameFocusNode.unfocus();
                    setState(() {});
                  })
      ),
    );
  }

TextField控件功能和原生iOS控件UITextField功能类似,不过大多数属性通过设置decoration来实现, placeholder和leftview,rightview对应hintText,prefixIcon,suffixIcon属性,为了实现输入内容不为空时输入框右边显示清空按钮,需要监听TextField的值,我们通过设置TextEditingController并监听它的值变化来实现,隐藏键盘我们通过FocusNode来设置,具体见代码,为了去除编辑框的下划线,设置 InputDecoration属性

      border: InputBorder.none,

登录名的介绍已经完了,密码框的实现和登录名几乎一样,唯一的不同就是设置属性 obscureText: true, 其他的就不再多说,上代码

_buildPwdTextField() {
   return TextField(
     controller: _pwdEditController,
     focusNode: _pwdFocusNode,
     obscureText: true,
     decoration: InputDecoration(
         hintText: "密码",
         border: InputBorder.none,
         prefixIcon: Image.asset(
           "assets/login/password.png",
           fit: BoxFit.none,
         ),
         suffixIcon: (_pwdEditController.text ?? "").isEmpty
             ? FlatButton(
                 child: Text("忘记密码"),
                 onPressed: () {
                   _pwdFocusNode.unfocus();
                   _userNameFocusNode.unfocus();
                 })
             : IconButton(
                 icon: Icon(
                   Icons.cancel,
                   color: Colors.grey,
                 ),
                 onPressed: () {
                   _pwdEditController.clear();
                   _pwdFocusNode.unfocus();
                   setState(() {});
                 }),
   ));
 }

两个输入框之间有一条1px分割线,分割线在Flutter中也有控件:Divider,最后编辑框组的实现如下

_buildEditWidget() {
    return Container(
      margin: EdgeInsets.only(left: 15, right: 15),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(4.0),
        border: Border.all(
            width: 1.0 / MediaQuery.of(context).devicePixelRatio,
            color: Colors.grey.withOpacity(0.5)),
      ),
      child: Column(
        children: <Widget>[
          _buildLoginNameTextField(),
          Divider(height: 1.0),
          _buildPwdTextField(),
        ],
      ),
    );
  }

现在只剩下最后的部件:登录按钮和注册按钮

  _buildLoginRegisterButton() {
    return Padding(
      padding: EdgeInsets.all(15),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Expanded(
            child: Container(
              height: 44,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(4.0),
                color: Colors.grey.withOpacity(0.3),
              ),
              child: FlatButton(
                  onPressed: null,
                  child: Text(
                    "登录",
                    style: TextStyle(color: Colors.white),
                  )),
            ),
          ),
          SizedBox(width: 15.0),
          Expanded(
              child: Container(
            height: 44,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(4.0),
              border: Border.all(width: 1.0, color: Colors.green),
            ),
            child: FlatButton(
                onPressed: null,
                child: Text(
                  "立即注册",
                  style: TextStyle(color: Colors.green),
                )),
          ))
        ],
      ),
    );
  }

为了使按钮等分剩余空间,需要用到Expanded控件包裹,Expanded控件从Flexible派生,Flexible通过属性flex设置剩余空间的分配,熟悉android的一眼就看出类似于layout_weight属性

各个部件都实现好了,现在就是组装的时候了,我们的采取从上到下的线性布局,Flutter可以用Column来实现

  
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        _buildTopBannerWidget(),
        _buildAccountLoginTip(),
        _buildEditWidget(),
        _buildLoginRegisterButton(),
      ],
    ),
  );
}

运行后一切正常,但是点击输入框问题来了
在这里插入图片描述
具体意思是布局重叠了,这时候SingleChildScrollView就派上用场了,它能在弹出键盘时将内容向上移动使输入框不被覆盖,优化后的代码如下

  
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          _buildTopBannerWidget(),
          _buildAccountLoginTip(),
          _buildEditWidget(),
          _buildLoginRegisterButton(),
        ],
      ),
    ),
  );
}

至此我们的登录页就实现完了,至于实际的登录联网就不再说了,有什么问题欢迎大家指正

没有更多推荐了,返回首页