业务逻辑

为了演示登录跳转,在分类浏览先做了一个简单的按钮,点击跳转到登录页面。实际的 app 中,通常会是触发某些需要登录才能查看的操作后再跳转到登录界面。

布局分析

界面如上图所示,从界面上看,整体内容区域是居中的,内容的布局是一个简单的列式布局,包括了顶部的一个 logo(通常是 app图标),再往下是两个文本输入框,最后是登录按钮。整体布局比较简单,使用 center 下嵌一个column 进行列布局即可。

图片圆形裁剪

在 flutter 中实行图片圆形裁剪有两个方式,一是使用外层的容器,通过将正方形的按圆形裁剪即可;二是使用内置的 circleavatar。不过从名字上看 circleavatar 用于头像的,因此这里使用容器的来实现圆形裁剪。封装一个获取圆形图片的方法_getroundimage,传入图片资源名称和正方形边长,代码如下所示:

widget _getroundimage(string imagename, double size) {
    return container(
      width: size,
      height: size,
      clipbehavior: clip.antialias,
      decoration: boxdecoration(
        borderradius: borderradius.all(radius.circular(size / 2)),
      ),
      child: image.asset(
        imagename,
        fit: boxfit.fitwidth,
      ),
    );
  }

这里使用了 boxdecoration 将边框设置为圆形的边框,半径为边长的一半,这样就达到边框是圆形的效果了。但是,需要额外设置一个属性就是 clipbehavior,这是边缘裁剪类型,默认是不裁剪的。这里使用了 clip.antialias(抗锯齿)的方式进行裁剪,这种方式的裁剪效果最好,但是更耗资源,其他的裁剪方式如下:

  • clip.hardedge:从名字就知道,这种方式很粗糙,但是裁剪的效率最快;
  • clip.antialiassavelayer:最为精细的裁剪,但是非常慢,不建议使用;
  • clip.none:默认值,如果内容区没有超出容器边界的话,不会做任何裁剪。内容超出边界的话需要使用别的裁剪方式防止内容溢出。

圆形扁平按钮

这里需要提一下, flutter 2.0以前的扁平按钮是flatbutton,使用起来很简单,但是很多场合不太满足,因此2.0以后引入了 textbutton 替代。textbutton 多了一个 style来装饰按钮样式。具体可以看官方的文档。这里我们的按钮需要设置背景色为主题色,然后按钮文字颜色为白色,同时需要切成圆角,因此还是使用 container 的边界圆弧来实现。需要注意的是,默认按钮的宽度是根据内容来的,因此为了让按钮撑满屏幕,我们设置了 container 的宽度为 double.infinity。代码如下所示:

widget _getloginbutton() {
    return container(
      height: 50,
      width: double.infinity,
      margin: edgeinsets.all(10),
      decoration: boxdecoration(
        color: theme.of(context).primarycolor,
        borderradius: borderradius.circular(4.0),
      ),
      child: textbutton(
        style: buttonstyle(
          foregroundcolor: materialstateproperty.all<color>(colors.white),
          backgroundcolor:
              materialstateproperty.all<color>(theme.of(context).primarycolor),
        ),
        child: text(
          '登录',
        ),
        onpressed: () {
          print(
              'login: username=${_username.trim()}, password=${_password.trim()}');
        },
      ),
    );
  }

按钮点击回调事件为 onpressed,这里只是简单地打印了表单的内容。

textfield 文本框

textfield 是 flutter 提供的文本输入框,textfield 的属性非常多,常用的属性如下:

  • keyboardtype:键盘类型,可以指定是数字、字母、电话号码、邮箱、日期等多种方式,通过与表单内容匹配的键盘类型可以提供输入效率,进而改善用户体验。
  • controller:texteditingcontroller 对象,texteditingcontroller 主要用于控制文本框的初始值,清除内容的操作。
  • obscuretext:是否需要隐藏输入内容,如果为 true,则输入内容会使用圆点显示,通常用与密码。
  • decoration:文本框的装饰,属性也很多,可以指定前置图标,边框类型、后置组件等多种属性,因此可以通过 decoration 获得想要的文本框样式。
  • focusnode:聚焦点,可以通过这个来控制文本框是否获取焦点,从而实现类似上一个下一个的输入控制。
  • onchanged:输入值改变事件回调,通常用这个方法实现双向绑定。

在这个案例中,我们使用了一个前置图标用来表示输入内容的类型,比如使用手机图标代表输入手机号,使用锁代表代表密码。同时使用了一个 offstage作为后置的组件,用于在输入内容后可以点击清除内容。offstage 组件是通过一个属性offstage来控制组件是否显示,这样我们可以在没有内容的时候隐藏它,有输入内容的时候再显示。

为了提高代码复用性,使用了一个方法获取通用的文本框,这里主要是使用了 container包裹以控制边距和文本框下的分隔线:

widget _getinputtextfield(
    textinputtype keyboardtype, {
    focusnode focusnode,
    controller: texteditingcontroller,
    onchanged: function,
    inputdecoration decoration,
    bool obscuretext = false,
    height = 50.0,
  }) {
    return container(
      height: height,
      margin: edgeinsets.all(10.0),
      child: column(
        children: [
          textfield(
            keyboardtype: keyboardtype,
            focusnode: focusnode,
            obscuretext: obscuretext,
            controller: controller,
            decoration: decoration,
            onchanged: onchanged,
          ),
          divider(
            height: 1.0,
            color: colors.grey[400],
          ),
        ],
      ),
    );
  }

完整代码

class _loginpagestate extends state<loginpage> {
  //texteditingcontroller可以使用 text 属性指定初始值
  texteditingcontroller _usernamecontroller = texteditingcontroller();
  texteditingcontroller _passwordcontroller = texteditingcontroller();
  string _username = '', _password = '';

  @override
  widget build(buildcontext context) {
    return scaffold(
      appbar: appbar(
        title: text('登录'),
        brightness: brightness.dark,
      ),
      body: center(
        child: column(
          crossaxisalignment: crossaxisalignment.center,
          mainaxisalignment: mainaxisalignment.center,
          children: <widget>[
            _getroundimage('images/logo.png', 100.0),
            sizedbox(
              height: 60,
            ),
            _getusernameinput(),
            _getpasswordinput(),
            sizedbox(
              height: 10,
            ),
            _getloginbutton(),
          ],
        ),
      ),
    );
  }

  widget _getusernameinput() {
    return _getinputtextfield(
      textinputtype.number,
      controller: _usernamecontroller,
      decoration: inputdecoration(
        hinttext: "输入手机号",
        icon: icon(
          icons.mobile_friendly_rounded,
          size: 20.0,
        ),
        border: inputborder.none,
        //使用 gesturedetector 实现手势识别
        suffixicon: gesturedetector(
          child: offstage(
            child: icon(icons.clear),
            offstage: _username == '',
          ),
          //点击清除文本框内容
          ontap: () {
            this.setstate(() {
              _username = '';
              _usernamecontroller.clear();
            });
          },
        ),
      ),
      //使用 onchanged 完成双向绑定
      onchanged: (value) {
        this.setstate(() {
          _username = value;
        });
      },
    );
  }

  widget _getpasswordinput() {
    return _getinputtextfield(
      textinputtype.text,
      obscuretext: true,
      controller: _passwordcontroller,
      decoration: inputdecoration(
        hinttext: "输入密码",
        icon: icon(
          icons.lock_open,
          size: 20.0,
        ),
        suffixicon: gesturedetector(
          child: offstage(
            child: icon(icons.clear),
            offstage: _password == '',
          ),
          ontap: () {
            this.setstate(() {
              _password = '';
              _passwordcontroller.clear();
            });
          },
        ),
        border: inputborder.none,
      ),
      onchanged: (value) {
        this.setstate(() {
          _password = value;
        });
      },
    );
  }

  //省略了上述列举的代码

}

页面跳转

在上层面的登录按钮上,我们增加了一个点击事件,点击后再跳到登录页,按钮的响应代码如下所示。这是页面跳转的最简单的方式,使用 navigator 导航器的 push方法实现页面跳转,后续会介绍如何通过路由实现页面跳转,那种方式更为优雅。

//...
onpressed: () {
  navigator.of(context).push(
    materialpageroute(builder: (context) => loginpage()),
  );
},
//...

总结

从代码上看,功能虽然实现了,但是构建用户名和密码的代码十分相似,有没有办法进一步提高代码复用率,构建一个更为通用的表单组件呢?下篇我们将介绍如何来封装。

以上就是flutter 开发一个登录页面的详细内容,更多关于flutter 开发登录页面的资料请关注www.887551.com其它相关文章!