Flutter 开发一个登录页面

业务逻辑

为了演示登录跳转,在分类浏览先做了一个简单的按钮,点击跳转到登录页面。实际的 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 开发登录页面的资料请关注我们其它相关文章!

时间: 2021-06-03

Flutter实现用视频背景的登录页的示例代码

最终效果 项目地址 https://github.com/Tecode/flutter_widget 实现方法 安装插件 安装video_player,我安装的是最新的版本,请根据你自己的flutter版本去安装对应的版本,安卓可以直接使用虚拟机,IOS需要真机才可以播放. dev_dependencies: flutter_test: sdk: flutter video_player: ^0.10.1+6 我的Flutter版本 Flutter 1.7.8+hotfix.4 • channe

Flutter页面传值的几种方式

今天来聊聊Flutter页面传值的几种方式: InheritWidget Notification Eventbus (当前Flutter版本:2.0.4) InheritWidget 如果看过Provider的源码的同学都知道,Provider跨组件传值的原理就是根据系统提供的InheritWidget实现的,让我们来看一下这个组件. InheritWidget是一个抽象类,我们写一个保存用户信息的类UserInfoInheritWidget继承于InheritWidget: class Us

Flutter 构建一个常用的页面框架

最终实现的结果如上图所示,顶部共用一个导航栏,底部有四个图标导航,点击对应的图标跳转到对应的页面. 图标准备 本次例程需要4个图标,2种颜色,可以从 iconfont 中找到自己需要的图标下载不同的颜色使用.然后在 pubspec.yaml 中的 assets 指定素材所在目录.需要注意的是如果是 png 文件直接指定整个目录即可,但如果是 jpg 格式,则需要同时指定文件名及后缀. BottomNavigationBar 简介 BottomNavigationBar的构造函数如下: Botto

Flutter 使用Navigator进行局部跳转页面的方法

Navigator组件使用的频率不是很高,但在一些场景下非常适用,比如局部表单多页填写.底部导航一直存在,每个tab各自导航场景. Navigator 是管理路由的控件,通常情况下直接使用Navigator.of(context)的方法来跳转页面,之所以可以直接使用Navigator.of(context)是因为在WidgetsApp中使用了此控件,应用程序的根控件通常是MaterialApp,MaterialApp包含WidgetsApp,所以可以直接使用Navigator的相关属性. Nav

flutter编写精美的登录页面

本文实例为大家分享了flutter编写精美的登录页面的具体代码,供大家参考,具体内容如下 先看效果图; 源代码已上传到github 我们先看一下页面 , 首先这个页面,我们并没有用到AppBar,当然也就没有自带返回功能. 然后下面有个Login的文字以及一条横线. 屏幕中上方是填写帐号以及密码的2个输入框,密码输入框有隐藏和显示密码的按钮. 下方是登录按钮 以及其他登录方式. 看一下主体布局: return Scaffold( body: Form( key: _formKey, child:

Flutter实现页面切换后保持原页面状态的3种方法

前言: 在Flutter应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,如果在initState中打印日志,会发现每次进入时都会输出,显然这样增加了额外的开销,并且带来了不好的用户体验. 在正文之前,先看一些常见的App导航,以喜马拉雅FM为例: 它拥有一个固定的底部导航以及首页的顶部导航,可以看到不管是点击底部导航切换页面还是在首页左右侧滑切换页面,之前的页面状态都是始终维持的,下面就具体介绍下如何在flutter中实现类似喜马拉雅的导航效果 第一步:实

vue页面跳转后返回原页面初始位置方法

vue页面跳转到新页面之后,再由新页面返回到原页面时候若想返回调出原页面的初始位置,怎么来解决这个问题呢?首先我们应该在跳出页面时候记录下跳出的scrollY,返回原页面的时候在设置返回位置为记录下的scrolly即可,scrolly我用的是vuex状态管理器来保存的.整个环境是基于vue-cli搭建的 一.main.js里面配置vuex //引用vuex import Vuex from 'vuex' Vue.use(Vuex) 二.main.js里面vuex状态管理 var store =

点击提交按钮后按钮变灰色不可用状态的三种方法

第一种方法:直接按钮中加入 当点击提交后,提交按钮变灰色不可用,这样可有效防止重复提交,本代码就是实现这样一个功能.从代码就可以看出,我们只需在提交按钮上加入这一句: onclick="javascript:{this.disabled=true;document.form1.submit();}",意思是当按钮点击后,将按钮的不可用属性设置为true,这样按钮就变灰了 表单提交后按钮变成灰色 [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行] 第二种方法:通过onSubmit

(asp.net c#)DropDownList绑定后显示对应的项的两种方法

方法一: 实现代码 复制代码 代码如下: DropDownList1.DataSource = listSort; DropDownList1.DataTextField = "LogSortName"; DropDownList1.DataValueField = "LogSortID"; DropDownList1.DataBind(); DropDownList1.SelectedIndex = ddl.Items.IndexOf(DropDownList1.

vue页面离开后执行函数的实例

如下所示: destroyed: function () { console.log("我已经离开了!"); this.doSomething(); }, 说明:destroyed是与methods.mounted同层级的 以上这篇vue页面离开后执行函数的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们. 您可能感兴趣的文章: vue页面跳转后返回原页面初始位置方法 vue中页面跳转拦截器的实现方法 Vue指令的钩子函数使用方法

Python获取当前页面内所有链接的四种方法对比分析

本文实例讲述了Python获取当前页面内所有链接的四种方法.分享给大家供大家参考,具体如下: ''' 得到当前页面所有连接 ''' import requests import re from bs4 import BeautifulSoup from lxml import etree from selenium import webdriver url = 'http://www.testweb.com' r = requests.get(url) r.encoding = 'gb2312'

vue2.0路由切换后页面滚动位置不变BUG的解决方法

最近项目中遇到这样一个问题,vue切换路由,页面到顶端的滚动距离仍会保持不变. <a href="javascript:;" rel="external nofollow" class="btn btn01" @click="useRightNow">立即试用</a> <router-link class="db" to="/user">个人中心<

解决django中form表单设置action后无法回到原页面的问题

django中form表单设置action后,点提交按钮是跳转到action页面的,比如设置action为login,网址为192.168.1.128,跳转后便会来到192.168.1.128/login,F5刷新也会是重新提交表单对话框,无法回到原页面. 因此就要在django服务器进行重定向,具体就是 from django.shortcuts import redirect #最后返回原页面 return redirect(url) 补充知识:Django + Ajax发送POST表单,并

Angular页面间切换及传值的4种方法

本文实例为大家分享了Angular JS页面间切换及传值 的方法,供大家参考,具体内容如下Angular JS页面间切换及传值 1. 基于ui-router的页面跳转传参 (1) 在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个producers,点击其中一个目标,页面跳转到对应的producer页,同时将producerId这个参数传过去. state('producers', { url: '/produ

Android 中使用 ViewPager实现屏幕页面切换和页面轮播效果

之前关于如何实现屏幕页面切换,写过一篇博文<Android中使用ViewFlipper实现屏幕切换>,相比ViewFlipper,ViewPager更适用复杂的视图切换,而且Viewpager有自己的adapter,这也让其适应复杂对象,实现数据的动态加载. ViewPager是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api.而viewpager就是其中之一,利用它,我们可以做很多事情,从最简单的导航,到页面菜单等等. 下面我们就展示下Vie