Flutter学习-基础Widget

Flutter学习-基础Widget

  • 1. Flutter编程范式
    • 1.1 编程范式的理解
    • 1.2 flutter的编程范式
  • 2. Text Widget
    • 2.1 普通文本展示
    • 2.2 富文本
    • 2.3 扩展小知识点
    • 2.4 android studio 开发flutter一些快捷键
  • 3 Button Widget
    • 3.1 基础Button
    • 3.2 Button的间距问题
    • 3.3 Button的大小修改和内边距的修改
  • 4 Image Widget
    • 4.1 加载网络图片
    • 4.2 加载本地图片
    • 4.3 实现图片的圆角
      • 4.3.1 CircleAvatar
      • 4.3.2 ClipOval
      • 4.3.3 ClipRRect
    • 4.4 FadeImage(可以设置占位图)
    • 4.5 图片的缓存问题
    • 4.6 Icon
  • 5 TextFiled
    • 5.1 TextFile的基础使用
    • TextField的controller
  • 6 Form表单
    • 6.1 Form表单的基本使用
    • 6.2 保存和获取表单数据
    • 6.3 验证填写的表单数据

1. Flutter编程范式

这个章节又讲解一些理论的东西,可能并不会直接讲授Flutter的知识,但是会对你以后写任何的代码,都具备一些简单的知道思想;
建议大家遇到一些问题的时候,可以先查看文档和源码,因为很多东西文档中都有说明

1.1 编程范式的理解

编程范式对于初学编程的人来说是一个虚无缥缈的东西,但是却是我们日常开发中都在默认遵循的一些模式和方法;

比如我们最为熟悉的 面向对象编程就是一种编程范式,与之对应或者结合开发的包括:面向过程编程、函数式编程、面向协议编程;

另外还有两个对应的编程范式:命令式编程 和 声明式编程

  • 命令式编程: 命令式编程非常好理解,就是一步步给计算机命令,告诉它我们想做什么事情;
  • 声明式编程: 声明式编程通常是描述目标的性质,你应该是什么样的,依赖哪些状态,并且当依赖的状态发生改变时,我们通过某些方式通知目标作出相应

1.2 flutter的编程范式

从2009年开始(数据来自维基百科),声明式编程就开始流行起来,并且目前在Vue、React、包括iOS中的SwiftUI中以及Flutter目前都采用了声明式编程。

现在我们来开发一个需求:显示一个Hello World,之后又修改成了Hello Flutter

如果是传统的命令式编程,我们开发Flutter的模式很可能是这样的:(注意是想象中的伪代码)

  • 整个过程,我们需要一步步告诉Flutter它需要做什么;
final text = new Text();
var title = "Hello World";
text.setContent(title);

// 修改数据
title = "Hello Flutter";
text.setContent(title);

如果是声明式编程,我们通常会维护一套数据集:

  • 这个数据集可能来自己父类、来自自身State管理、来自InheritedWidget、来自统一的状态管理的地方;
  • 总之,我们知道有这么一个数据集,并且告诉Flutter这些数据集在哪里使用;
var title = "Hello World";

Text(title); // 告诉Text内部显示的是title

// 数据改变
title = "Hello Flutter";
setState(() => null); // 通知重新build Widget即可

上面的代码过于简单,可能不能体现出Flutter声明式编程的优势所在,但是在以后的开发中,我们都是按照这种模式在进行开始,我们一起来慢慢体会;

2. Text Widget

在Android中,我们使用TextView,iOS中我们使用UILabel来显示文本;
Flutter中,我们使用Text组件控制文本如何展示;

2.1 普通文本展示

在Flutter中,我们可以将文本的控制显示分成两类

  • 控制文本布局的参数: 如文本对齐方式 textAlign、文本排版方向 textDirection,文本显示最大行数 maxLines、文本截断规则 overflow 等等,这些都是构造函数中的参数
    文本默认情况下就是包裹内容的大小,也就是说Text()组件的大小取决于其内容的大小

  • 控制文本样式的参数: 如字体名称 fontFamily、字体大小 fontSize、文本颜色 color、文本阴影 shadows 等等,这些参数被统一封装到了构造函数中的参数 style 中

  • 示例属性的使用

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Text(
      "《定风波》 苏轼 莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。",
      textAlign: TextAlign.left,//设置文本左对齐
      maxLines: 2,//设置最大行数2行
      overflow: TextOverflow.ellipsis,//设置超出部分...
      style: TextStyle(
        fontSize: 30,//设置字体
        color: Colors.orange//设置字体颜色
      ),
    );
  }
}

上述代码只是展示了一些常用的属性,更多的属性用法我们需要去看源码, 还有哪些属性,大家可以多尝试下。

  • 注意:Text组件是不可以直接设置一个宽度的,而是包裹内容的

2.2 富文本

代码示例:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text.rich(
      TextSpan(
        children: [
          TextSpan(text: "《定风波》", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Colors.black)),
          TextSpan(text: "苏轼", style: TextStyle(fontSize: 18, color: Colors.redAccent)),
          TextSpan(text: "\n莫听穿林打叶声,何妨吟啸且徐行。\n竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。")
        ],
      ),
      style: TextStyle(fontSize: 20, color: Colors.purple),
      textAlign: TextAlign.center,
    );
  }
}

效果图:
Flutter学习-基础Widget_第1张图片

2.3 扩展小知识点

上述我们一直在写的Text()组件,其实不是flutter最终渲染的Widegt。

void main() => runApp(MyApp())

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text("基础widget"),
            ),
            body: GYHomeContent()
        )
    );
  }
}

上述代码中main函数选择的MyApp对象吗? 其实不是,实际渲染的MyApp中build中返回的widget,所以我们我们要想知道使用Text小组件,flutter最终渲染的widget是什么,我们查看Text组件的build方法

Flutter学习-基础Widget_第2张图片

Flutter学习-基础Widget_第3张图片

上述源码中我们可以看出我们最终渲染的是RichText的一个组件,RichText是可以直接使用的

2.4 android studio 开发flutter一些快捷键

  • Option + command + b : 查看抽象类的实现类
  • Option + command + w :将build出的widget抽取到一个单独的widget中
  • Option + enter: 将Statelesswidget转换成Statefulwidget
  • stl :代表StatelessWidget
  • stf :StatefulWidget
  • Option + Enter :在widget包裹一个新的widget。
  • Option + Up :选择这个widget。
  • Option + Command + M :方法抽离或重构。
  • Command + Shift + Enter :代码快速补全。
  • Option + Command + V : 抽离局部变量。
  • Option + Command + F :抽离成员变量。
  • Command + Option + L : 代码格式化。
  • Command + F12 :快速查看当前文件所有方法。
  • Command + shift + F :全局搜索。
  • Command + shift + R :全局替换。
  • Command + F :当前文件搜索。
  • Command + R :当前文件替换。

3 Button Widget

3.1 基础Button

Material widget库中提供了多种按钮Widget如FloatingActionButton、RaisedButton、FlatButton、OutlineButton等

  • 案例示例:
class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FloatingActionButton(onPressed: (){print("FloatingActionButton click");}, child: Text("FloatingActionButton")),

        /**
         * @Deprecated(
            'Use ElevatedButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
            'This feature was deprecated after v1.26.0-18.0.pre.',
            )
            该按钮在v1.26.0-18.0.pre版本后被弃用了, 使用ElevatedButton 来替代RaisedButton
         */
        RaisedButton(onPressed: (){print("RaisedButton click");}, child: Text("RaisedButton")),
        ElevatedButton(onPressed: (){print("ElevatedButton click");}, child: Text("ElevatedButton")),

        /**
         * @Deprecated(
            'Use TextButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
            'This feature was deprecated after v1.26.0-18.0.pre.',
            该按钮在v1.26.0-18.0.pre版本后被弃用了, 使用TextButton 来替代 FlatButton
         */
        FlatButton(onPressed: (){print("FlatButton click");}, child: Text("FlatButton")),
        TextButton(onPressed: (){print("TextButton click");}, child: Text("TextButton")),
        
        /**
         * @Deprecated(
            'Use OutlinedButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
            'This feature was deprecated after v1.26.0-18.0.pre.',
            )
            该按钮在v1.26.0-18.0.pre版本后被弃用了, 使用OutlinedButton 来替代 OutlineButton
         */
        OutlineButton(onPressed: (){print("OutlineButton click");}, child: Text("OutlineButton")),
        OutlinedButton(onPressed: (){print("OutlinedButton click");}, child: Text("OutlinedButton"))
      ],
    );
  }
}

如果想要自己定制一些按钮, 我们完全可以自己通过按钮中的一些属性来控制,达到自己想要的按钮样式,大家可以自行演示下

运行结果:
Flutter学习-基础Widget_第4张图片

3.2 Button的间距问题

默认情况下Button上下会有一定的间距

  • 通过上面运行的结果我们可以发现,RaisedButtonElevateButton上面和下面好像都有些许的间距,主要是button有一个属性,MaterialTapTargetSize这个属性,这是一个枚举,默认值是padded,看源码描述是,如果按钮的大小不足48px大小,会默认扩展到48px的大小。如果我们想要按钮今包裹内容,那么我们可以设置MaterialTapTargetSize这个属性的值为shrinkWrap,这样按钮的大小就会包裹内容

Flutter学习-基础Widget_第5张图片

Flutter学习-基础Widget_第6张图片

3.3 Button的大小修改和内边距的修改

  • 当我们不给Button设置任何内容的时候,我们可以发现Button依旧会有一个默认的大小

Flutter学习-基础Widget_第7张图片

那么我们如果让Button的大小变小点了?这里以FlatButton为例

我们通过文档和源码,我们都可以看到,FlatButton最终是继承自MaterialButton,然后继承自StatelessWidget,我们知道,如果widget是继承自StatelessWidget的,那么flutter最终渲染的是build返回的组件

Flutter学习-基础Widget_第8张图片

文档描述:
在这里插入图片描述

通过文档我们可以看到,最终渲染的ButtonTheme有一个默认大小,然后在结合源码,我们可以看到ThemeButton有两个属性,minwidthminheight表示最小宽度和最小高度的

由源码可知我们的ThemeButton是通过上下文来获取,所以我们可以FlatButton的外层包裹一个ThemeButton然后设置minWidth、minHeight,然后通过这个两个属性我们就可以调整任意大小的Button

Flutter学习-基础Widget_第9张图片

虽然这里我们设置了最小宽度,但是如果我们的Button是有内容的,这个Button还是可以包裹内容的

Flutter学习-基础Widget_第10张图片

  • 但是我们发现Button还是有一点内边距,我们可以发现Button有一个属性padding,通过修改这个属性我们可以达到修改Button内边距的问题,这个属性和上面讲述的ButtonTheme的最小宽和最小高的属性可能会冲突

4 Image Widget

图片可以让我们的应用更加丰富多彩,Flutter中使用Image组件

Image组件有很多的构造函数,我们这里主要学习两个:

  • Image.assets:加载本地资源图片;
  • Image.network:加载网络中的图片

4.1 加载网络图片

相对来讲,Flutter中加载网络图片会更加简单,直接传入URL并不需要什么配置,所以我们先来看一下Flutter中如何加载网络图片

我们先来看看Image有哪些属性可以设置

const Image({
    required this.image, //必传参数 图片
    ...
    this.width, //图片的宽
    this.height, //图片的高
    this.color, // 图片的混合色值
    this.colorBlendMode, //混合模式
    this.fit, //缩放模式
    this.alignment = Alignment.center, //对齐方式
    this.repeat = ImageRepeat.noRepeat, //重复方式
    ...
  }) 
  • width、height:用于设置图片的宽、高,当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小,如果只设置width、height的其中一个,那么另一个属性默认会按比例缩放,但可以通过下面介绍的fit属性来指定适应规则。
  • fit:该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在BoxFit中定义,它是一个枚举类型,有如下值:
    • fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
    • cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。
    • contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。
    • fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。
  • colorcolorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而colorBlendMode指定混合模式;
  • repeat:当图片本身大小小于显示空间时,指定图片的重复规则。

示例代码

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.network(
            "http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
          alignment: Alignment.topCenter,//顶部居中
          repeat: ImageRepeat.repeatY, //Y轴上重复
          color: Colors.red,
          colorBlendMode: BlendMode.colorBurn,
        ),
        width: 300,
        height: 300,
      ),
    );
  }
}

Flutter学习-基础Widget_第11张图片

4.2 加载本地图片

加载本地图片这里比较麻烦一点,需要配置。

  • 首先需要把图片导入工程,例如在images文件加下放入一张图片
  • 然后再pubspec.yaml文件中配置图片资源
  • 代码中根据图片路径来加载图片
class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.asset("images/test.jpeg"),
        width: 300,
        height: 300,
      ),
    );
  }
}

Flutter学习-基础Widget_第12张图片

4.3 实现图片的圆角

4.3.1 CircleAvatar

CircleAvatar可以实现圆角头像,也可以添加一个子Widget:

const CircleAvatar({
    Key? key,
    this.child, //子widget
    this.backgroundColor, // 背景颜色
    this.backgroundImage, // 背景图像
    this.foregroundImage, // 前景颜色
   	...
    this.foregroundColor,
    this.radius, //半径
    this.minRadius,//最小半径
    this.maxRadius,//最大半径
  }) 

我们需要实现一个圆形头像

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      child: Text("圆角图片", style: TextStyle(color: Colors.orange)),
      backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
      radius: 80,
    );
  }
}
  • CircleAvatar:本身就是一个圆角的widget,这里backgroundImage,属性需要我们传入一个ImageProvider?类型的小组件, 但是我们发现该组件是一个抽象类,然后我们可以查看该抽象类的实现类,所以这里我们传入的是NetworkImage小组件

Flutter学习-基础Widget_第13张图片

4.3.2 ClipOval

ClipOval也可以实现圆角头像,而且通常是在只有头像时使用

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Image.network(
        "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
        width: 200,
        height: 200,
      ),
    );
  }
}

//如果图片是不规则的, 我们可以调整图片的填充方式,来达到圆角充满的效果,但是这里的图片可能会根据你设置的不同模式的填充方式,有拉伸效果
ClipOval(
      child: Image.asset("images/test.jpeg",width: 200,height: 200,fit: BoxFit.fill,),

    );

4.3.3 ClipRRect

ClipRRect用于实现圆角效果,可以设置圆角的大小。

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ClipRRect(
        borderRadius: BorderRadius.circular(20),//随意调整圆角大小
        child: Image.network(
          "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
          width: 200,
          height: 200,
        ),
      ),
    );
  }
}

Flutter学习-基础Widget_第14张图片

4.4 FadeImage(可以设置占位图)

我们可以使用FadeImage组件来加载网络图片,然后设置占位图,该组件本身自带淡出效果,我们可以通过设置两个属性来达到控制动画效果的时间

@override
  Widget build(BuildContext context) {
    // 1.占位图的问题: FadeInImage
    return FadeInImage(
      fadeOutDuration: Duration(milliseconds: 1),//占位图消失时间
      fadeInDuration: Duration(milliseconds: 1),//网络图片展示时间
      placeholder: AssetImage("assets/images/juren.jpeg"),//占位图
      image: NetworkImage(imageURL),//真正显示的网络图片
    );
  }

4.5 图片的缓存问题

首先在flutter开发中,我们可能多个地方使用同一张网络图片, 我们可能想这个图片的缓存怎么处理(如果不错缓存每次都加载同一张图片是非常消耗性能的)?

  • 在flutter中我们并不要自己给图片来做缓存,flutter默认情况下是会给我们的图片做缓存的,fliutter在加载图片的时候,如果发现图片的地址和图片的缩放,就会直接加载原来的那张图片,所以flutter对图片默认情况下是有内存缓存的,默认最多缓存1000张图片,最大只能缓存100M的空间

Flutter学习-基础Widget_第15张图片

Flutter学习-基础Widget_第16张图片

根据文档我们发现可以去调整 缓存的大小(maximumSize),但是我们一般不去调整

4.6 Icon

通过源码我们看出Icon需要我们传入一个IconData的widget,其实flutter已经帮我们创建好了很多这种IconData,直接使用Icons这个widget就可以了

  • Icon图标其实是一种字体图标,那么Icon图标和我们的图片图标的区别在哪里?
    • 字体图标是矢量图,放大的时候不会失真
    • 字体图标,本质上也是一个字体,我们也可以设置颜色的
    • 字体图标很多时,相对来说占据的空间会更小
@override
  Widget build(BuildContext context) {
//    return Icon(Icons.pets, size: 300, color: Colors.orange,);
//    return Icon(IconData(0xe91d, fontFamily: 'MaterialIcons'), size: 300, color: Colors.orange,);
	
    return Text("\ue91d", style: TextStyle(fontSize: 100, color: Colors.orange, fontFamily: "MaterialIcons"),);
  }
  • 由于Icon本身是一个字体,那么我们是不是可以使用Text来加载,如果使用Text来加载必须满意一下条件:
    • 0xe91d -> unicode编码(需要把这个16进制的标记转换成unicode编码)
    • 2.设置对应的字体(需要告诉Text,你使用的是哪个字体)

5 TextFiled

5.1 TextFile的基础使用

TextField用于接收用户的文本输入,它提供了非常多的属性,我们来看一下源码:

  • 但是我们没必要一个个去学习,很多时候用到某个功能时去查看是否包含某个属性即可
const TextField({
  Key key,
  this.controller,
  this.focusNode,
  this.decoration = const InputDecoration(),
  TextInputType keyboardType,
  this.textInputAction,
  this.textCapitalization = TextCapitalization.none,
  this.style,
  this.strutStyle,
  this.textAlign = TextAlign.start,
  this.textAlignVertical,
  this.textDirection,
  this.readOnly = false,
  ToolbarOptions toolbarOptions,
  this.showCursor,
  this.autofocus = false,
  this.obscureText = false,
  this.autocorrect = true,
  this.maxLines = 1,
  this.minLines,
  this.expands = false,
  this.maxLength,
  this.maxLengthEnforced = true,
  this.onChanged,
  this.onEditingComplete,
  this.onSubmitted,
  this.inputFormatters,
  this.enabled,
  this.cursorWidth = 2.0,
  this.cursorRadius,
  this.cursorColor,
  this.keyboardAppearance,
  this.scrollPadding = const EdgeInsets.all(20.0),
  this.dragStartBehavior = DragStartBehavior.start,
  this.enableInteractiveSelection = true,
  this.onTap,
  this.buildCounter,
  this.scrollController,
  this.scrollPhysics,
}) 

我们来学习几个常见的属性把

  • 一些属性比较简单:keyboardType键盘的类型,style设置样式,textAlign文本对齐方式,maxLength最大显示行数等等;
  • decoration:用于设置输入框相关的样式
    • icon:设置左边显示的图标
    • labelText:在输入框上面显示一个提示的文本
    • hintText:显示提示的占位文字
    • border:输入框的边框,默认底部有一个边框,可以通过InputBorder.none删除掉
    • filled:是否填充输入框,默认为false
    • fillColor:输入框填充的颜色
    • suffixIcon: 输入框右侧(尾部)显示widget
    • prefixIcon:输入框左侧(前面)显示widget
  • controller:关联的控制器
  • onChanged:监听输入框内容的改变,传入一个回调函数
  • onSubmitted:点击键盘中右下角的down时,会回调的一个函数
import 'package:flutter/material.dart';

class TextFieldDemo extends StatelessWidget {

  final usernameTextEditController = TextEditingController();
  final passwordTextEditController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Theme(
      data: ThemeData(
          primaryColor: Colors.red //可以达到控制输入框边框的颜色
      ),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: <Widget>[
            TextField(
              controller: usernameTextEditController,
              decoration: InputDecoration(
                  labelText: "username",
                  icon: Icon(Icons.people),
                  hintText: "请输入用户名",
                  border: InputBorder.none,//去掉边框
                  filled: true,
                  fillColor: Colors.red[100]
              ),
              onChanged: (value) {
                print("onChange:$value");
              },
              onSubmitted: (value) {
                print("onSubmitted:$value");
              },
            ),
            SizedBox(height: 10,),
            TextField(
              controller: passwordTextEditController,
              decoration: InputDecoration(
                labelText: "password",
                icon: Icon(Icons.lock),
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 10,),
            Container(
              width: double.infinity,
              height: 40,
              child: FlatButton(
                child: Text("登 录", style: TextStyle(fontSize: 20, color: Colors.white),),
                color: Colors.blue,
                onPressed: () {
                  // 1.获取用户名和密码
                  final username = usernameTextEditController.text;
                  final password = passwordTextEditController.text;
                  print("账号:$username 密码:$password");
                  usernameTextEditController.text = "";
                  passwordTextEditController.text = "";
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

运行结果:

Flutter学习-基础Widget_第17张图片

TextField的controller

  • 我们可以给TextField添加一个控制器(Controller),可以使用它设置文本的初始值,也可以使用它来监听文本的改变;
  • 事实上,如果我们没有为TextField提供一个Controller,那么会Flutter会默认创建一个TextEditingController的,这个结论可以通过阅读源码得到:
  @override
  void initState() {
    super.initState();
    // ...其他代码
    if (widget.controller == null)
      _controller = TextEditingController();
  }

我们也可以自己来创建一个Controller控制一些内容:

class _TextFieldDemoState extends State<TextFieldDemo> {
  final textEditingController = TextEditingController();

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

    // 1.设置默认值
    textEditingController.text = "Hello World";

    // 2.监听文本框
    textEditingController.addListener(() {
      print("textEditingController:${textEditingController.text}");
    });
  }
	
  // ...省略build方法
}

6 Form表单

在我们开发注册、登录页面时,通常会有多个表单需要同时获取内容或者进行一些验证,如果对每一个TextField都分别进行验证,是一件比较麻烦的事情。
做过前端的开发知道,我们可以将多个input标签放在一个form里面,Flutter也借鉴了这样的思想:我们可以通过Form对输入框进行分组,统一进行一些操作。

6.1 Form表单的基本使用

Form表单也是一个Widget,可以在里面放入我们的输入框。
但是Form表单中输入框必须是FormField类型的

  • 我们查看刚刚学过的TextField是继承自StatefulWidget,并不是一个FormField类型;
  • 我们可以使用TextFormField,它的使用类似于TextField,并且是继承自FormField的;

我们通过Form的包裹,来实现一个注册的页面:

class FormDemo extends StatefulWidget {
  @override
  _FormDemoState createState() => _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  @override
  Widget build(BuildContext context) {
    return Form(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(
              icon: Icon(Icons.people),
              labelText: "用户名或手机号"
            ),
          ),
          TextFormField(
            obscureText: true,
            decoration: InputDecoration(
              icon: Icon(Icons.lock),
              labelText: "密码"
            ),
          ),
          SizedBox(height: 16,),
          Container(
            width: double.infinity,
            height: 44,
            child: RaisedButton(
              color: Colors.lightGreen,
              child: Text("注 册", style: TextStyle(fontSize: 20, color: Colors.white),),
              onPressed: () {
                print("点击了注册按钮");
              },
            ),
          )
        ],
      ),
    );
  }
}

Flutter学习-基础Widget_第18张图片

6.2 保存和获取表单数据

有了表单后,我们需要在点击注册时,可以同时获取和保存表单中的数据,怎么可以做到呢?

  1. 需要监听注册按钮的点击,在之前我们已经监听的onPressed传入的回调中来做即可。(当然,如果嵌套太多,我们待会儿可以将它抽取到一个单独的方法中)
  2. 监听到按钮点击时,同时获取用户名密码的表单信息。
  • 如何同时获取用户名密码的表单信息?
  • 如果我们调用FormState对象的save方法,就会调用Form中放入的TextFormFieldonSave回调:
TextFormField(
  decoration: InputDecoration(
    icon: Icon(Icons.people),
    labelText: "用户名或手机号"
  ),
  onSaved: (value) {
    print("用户名:$value");
  },
),
  • 但是,我们有没有办法可以在点击按钮时,拿到 Form对象 来调用它的save方法呢?
    • 知识点:在Flutter如何可以获取一个通过一个引用获取一个StatefulWidget的State对象呢?
    • 答案:通过绑定一个GlobalKey即可。

Flutter学习-基础Widget_第19张图片

案例代码演示:

class FormDemo extends StatefulWidget {
  @override
  _FormDemoState createState() => _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  final registerFormKey = GlobalKey<FormState>();
  String username, password;

  void registerForm() {
    registerFormKey.currentState.save();

    print("username:$username password:$password");
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: registerFormKey,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(
              icon: Icon(Icons.people),
              labelText: "用户名或手机号"
            ),
            onSaved: (value) {
              this.username = value;
            },
          ),
          TextFormField(
            obscureText: true,
            decoration: InputDecoration(
              icon: Icon(Icons.lock),
              labelText: "密码"
            ),
            onSaved: (value) {
              this.password = value;
            },
          ),
          SizedBox(height: 16,),
          Container(
            width: double.infinity,
            height: 44,
            child: RaisedButton(
              color: Colors.lightGreen,
              child: Text("注 册", style: TextStyle(fontSize: 20, color: Colors.white),),
              onPressed: registerForm,
            ),
          )
        ],
      ),
    );
  }
}

Flutter学习-基础Widget_第20张图片

6.3 验证填写的表单数据

在表单中,我们可以添加验证器,如果不符合某些特定的规则,那么给用户一定的提示信息
比如我们需要账号和密码有这样的规则:账号和密码都不能为空。
按照如下步骤就可以完成整个验证过程:

  1. 为TextFormField添加validator的回调函数;
  2. 调用Form的State对象的validate方法,就会回调validator传入的函数;

Flutter学习-基础Widget_第21张图片

也可以为TextFormField添加一个属性:autovalidate

Flutter学习-基础Widget_第22张图片

不需要调用validate方法,会自动验证是否符合要求;

你可能感兴趣的:(Flutter,flutter)