Flutter 开发一个常用的登录界面

简介: 登录页面在 App 开发中非常常见,本篇借登录页面的开发介绍了文本框 TextField组件的使用,同时使用文本框的装饰属性实现了个性化文本框设置。
登录页面在 App 开发中非常常见,本篇借登录页面的开发介绍了文本框 TextField组件的使用,同时使用文本框的装饰属性实现了个性化文本框设置。

业务逻辑

为了演示登录跳转,在分类浏览先做了一个简单的按钮,点击跳转到登录页面。实际的 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()),
  );
},
//...

总结

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


欢迎关注个人公众号:岛上码农

相关文章
|
27天前
|
缓存 监控 前端开发
优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面
本文探讨了优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面,并通过案例分析展示了具体措施和效果,强调了持续优化的重要性及未来优化方向。
54 10
|
26天前
|
开发框架 Dart 前端开发
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。本文从 Flutter 简介、特点、开发环境搭建、应用架构、组件详解、路由管理、状态管理、与原生代码交互、性能优化、应用发布与部署及未来趋势等方面,全面解析 Flutter 技术,助你掌握这一前沿开发工具。
56 8
|
26天前
|
存储 JavaScript 前端开发
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战。本文介绍了几种常用的状态管理框架,如Provider和Redux,分析了它们的基本原理、优缺点及适用场景,并提供了选择框架的建议和使用实例,旨在帮助开发者提高开发效率和应用性能。
34 4
|
26天前
|
传感器 前端开发 Android开发
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求。本文深入探讨了插件开发的基本概念、流程、集成方法、常见类型及开发实例,如相机插件的开发步骤,同时强调了版本兼容性、性能优化等注意事项,并展望了插件开发的未来趋势。
39 2
|
1月前
|
存储 缓存 Dart
Flutter&鸿蒙next 封装 Dio 网络请求详解:登录身份验证与免登录缓存
本文详细介绍了如何在 Flutter 中使用 Dio 封装网络请求,实现用户登录身份验证及免登录缓存功能。首先在 `pubspec.yaml` 中添加 Dio 和 `shared_preferences` 依赖,然后创建 `NetworkService` 类封装 Dio 的功能,包括请求拦截、响应拦截、Token 存储和登录请求。最后,通过一个登录界面示例展示了如何在实际应用中使用 `NetworkService` 进行身份验证。希望本文能帮助你在 Flutter 中更好地处理网络请求和用户认证。
167 1
|
2月前
|
开发者
鸿蒙Flutter实战:07-混合开发
鸿蒙Flutter混合开发支持两种模式:1) 基于har包,便于主项目开发者无需关心Flutter细节,但不支持热重载;2) 基于源码依赖,利于代码维护与热重载,需配置Flutter环境。项目结构包括AppScope、flutter_module等目录,适用于不同开发需求。
103 3
|
1月前
|
传感器 开发框架 物联网
鸿蒙next选择 Flutter 开发跨平台应用的原因
鸿蒙(HarmonyOS)是华为推出的一款旨在实现多设备无缝连接的操作系统。为了实现这一目标,鸿蒙选择了 Flutter 作为主要的跨平台应用开发框架。Flutter 的跨平台能力、高性能、丰富的生态支持和与鸿蒙系统的良好兼容性,使其成为理想的选择。通过 Flutter,开发者可以高效地构建和部署多平台应用,推动鸿蒙生态的快速发展。
228 0
|
1月前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
79 0
|
2月前
|
编解码 Dart API
鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
本文介绍了如何开发一个 Flutter 鸿蒙插件,实现 Flutter 与鸿蒙的混合开发及双端消息通信。通过定义 `MethodChannel` 实现 Flutter 侧的 token 存取方法,并在鸿蒙侧编写 `EntryAbility` 和 `ForestPlugin`,使用鸿蒙的首选项 API 完成数据的读写操作。文章还提供了注意事项和参考资料,帮助开发者更好地理解和实现这一过程。
112 0
|
2月前
|
Dart Android开发
鸿蒙Flutter实战:03-鸿蒙Flutter开发中集成Webview
本文介绍了在OpenHarmony平台上集成WebView的两种方法:一是使用第三方库`flutter_inappwebview`,通过配置pubspec.lock文件实现;二是编写原生ArkTS代码,自定义PlatformView,涉及创建入口能力、注册视图工厂、处理方法调用及页面构建等步骤。
68 0
下一篇
DataWorks