大家好,我是“追梦蜗牛”,大家可以在公众号后台回复 “Java资料”获得技能提升的资料,绝对是干货。

 

 

上篇文章为大家讲述了flutter的主要框架组件;本篇文章接着上篇内容继续为大家介绍flutter的组件使用,并且通过组件实现登录界面,本文针对功能点做特殊实例讲解,特别详细的整体使用我们会在其它的文章中来展开说明。每篇文章只要有代码说明 就会有demo提供。

 

前言

一、Widget介绍

二、布局控制 Row、Column控件 布局行为以及使用场景

三、Text组件使用和继承关系

四、Image组件使用和分辨率的适配

五、TextField 输入框的使用和数据控制器

六、 符合组件Container的使用实现(点击按钮)和类引用

七、提示信息的添加

八、总结

 

一、Widget介绍

 

Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。

 

例如:就以官网给出的示例,一个最简单的Flutter应用程序,只需一个widget即可!如下面示例:将一个widget传给runApp函数即可:

 

  •  
 import 'package:flutter/material.dart'; void main() {  runApp(    new Center(      child: new Text(        'Hello, world!',        textDirection: TextDirection.ltr,      ),    ),  );}

 

该runApp函数接受给定的Widget并使其成为widget树的根。在此示例中,widget树由两个widget:Center(及其子widget)和Text组成。框架强制根widget覆盖整个屏幕,这意味着文本“Hello, world”会居中显示在屏幕上。文本显示的方向需要在Text实例中指定,当使用MaterialApp时,文本的方向将自动设定。

示例如下:

 

  •  
class LoginPage extends StatefulWidget{  final String title;  LoginPage({Key key,this.title});   @override  createState() => new LoginPageState(title: this.title);//传递数据使用} class LoginPageState extends State<LoginPage>{  String title;  LoginPageState({Key key,this.title});   @override  Widget build(BuildContext context){    //界面布局    return Scaffold(        appBar: AppBar(//设置标题            title: Text("登录",                style: TextStyle(color: Colors.white)),            iconTheme: IconThemeData(color: Colors.white)        ),        body: Form(//页面内容            child: ListView(              key: _formKey,              padding: const EdgeInsets.all(10.0),              children: <Widget>[                SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度                pageTitle(),//登录标题,外部引用函数显示                            ],            )        )    );  } }

 

该实例中 重写Widget函数实现界面显示

 

 

  •  
@override  Widget build(BuildContext context){   }

 

二、布局控制 Row、Column控件 布局行为以及使用场景

 

上面的实例中显示布局用到了多种布局控件,用来控制布局的显示

 

1. 实例中 用到了表单 组件 Form,Flutter中的Form组件和html中的<form></form>的作用类似,都是起到了一个容器的作用

2. ListView 是一个线性布局的widgets 列表,ListView是最常用的滑动组件。它在滚动方向上一个接一个地显示它的孩子。在交叉轴中,需要孩子填充ListView。

3. 布局的方向除了From和Listview还有其他方式设定,通过 布局分为 上下布局 和 左右布局,Flutter通过这两种布局给出了 Row、Column控件

 

Row   

在Flutter中非常常见的一个多子节点控件,将children排列成一行。估计是借鉴了Web中Flex布局,如android的线性布局设置了属性:android:orientation 值为horizontal,所以很多属性和表现,都跟其相似。但是注意一点,自身不带滚动属性,如果超出了一行,在debug下面则会显示溢出的提示。

Row的布局有六个步骤,这种布局表现来自Flex(Row和Column的父类):

1.首先按照不受限制的主轴(main axis)约束条件,对flex为null或者为0的child进行布局,然后按照交叉轴( cross axis)的约束,对child进行调整;

2.按照不为空的flex值,将主轴方向上剩余的空间分成相应的几等分;

3.对上述步骤flex值不为空的child,在交叉轴方向进行调整,在主轴方向使用最大约束条件,让其占满步骤2所分得的空间;

4.Flex交叉轴的范围取自子节点的最大交叉轴;

5.主轴Flex的值是由mainAxisSize属性决定的,其中MainAxisSize可以取max、min以及具体的value值;

6.每一个child的位置是由mainAxisAlignment以及crossAxisAlignment所决定。

 

继承关系

  •  
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row Row(  children: <Widget>[    Expanded(      child: Container(        color: Colors.red,        padding: EdgeInsets.all(5.0),      ),      flex: 1,    ),    Expanded(      child: Container(        color: Colors.blue,        padding: EdgeInsets.all(5.0),      ),      flex: 1,    ),  ],)

 

例子说明,使用Expanded控件,将一行的宽度分成2个等分,每个child占1/2区域,由flex属性控制。

 

构造函数

  •  
Row({  Key key,  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,  MainAxisSize mainAxisSize = MainAxisSize.max,  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,  TextDirection textDirection,  VerticalDirection verticalDirection = VerticalDirection.down,  TextBaseline textBaseline,  List<Widget> children = const <Widget>[],})

 

Column

Row和Column都是Flex的子类,只是direction参数不同。Column各方面同Row。

三、Text组件使用和继承关系

 

Text显示文本添加在Widget中,作为一个函数引用,看起来模块化一些,不容易混乱

  •  
@override  Widget build(BuildContext context){    //界面布局    return Scaffold(        appBar: AppBar(//设置标题            title: Text("登录",                style: TextStyle(color: Colors.white)),            iconTheme: IconThemeData(color: Colors.white)        ),        body: Form(//页面内容            child: ListView(              key: _formKey,              padding: const EdgeInsets.all(10.0),              children: <Widget>[                SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度                pageTitle(),//登录标题,外部引用函数显示                            ],            )        )    );

 

Text添加通过 Padding来实现;

添加内边距 padding设置边距有两种 一种通过 all  设置全部边距,一种通过only设置每一个的边距

padding: EdgeInsets.only(left: 5.0, top: 15.0,bottom: 5.0,right: 5.0),// 1. EdgeInsets.all(8.0) 所有内边距  2. EdgeInsets.only(left: 12.0, top: 4.0) 设置内边距

设置文本的位置,居中,居左,居右等

textAlign: TextAlign.center,

设置文本的样式

style: TextStyle(inherit: false,fontSize: 22.0,color: Color(0xFF000000),/*fontStyle: FontStyle.italic,*/letterSpacing: 2.0,/*decoration: TextDecoration.overline,

            decorationStyle: TextDecorationStyle.wavy,*/fontWeight: FontWeight.w700),

 

//字体是否隐藏 inherit: true,

        //字体样式 大小,颜色 color: Color(0xFFffffff) ,color: Color.fromARGB(255, 150, 150, 150),

        //字体斜体  fontStyle: FontStyle.italic

        //字体间距  letterSpacing: 10.0,

        //上划线 decoration: TextDecoration.overline,(lineThrough 删除线)(underline 下划线)

        //波浪线 decorationStyle: TextDecorationStyle.wavy (dashed)(dotted)

        //字重 fontWeight: FontWeight.w700

具体的效果不在一一演示,大伙可以根据代码验证效果。

//顶部登录标题(文本介绍)

 

  •  
Padding pageTitle() {    return Padding(      padding: EdgeInsets.only(left: 5.0, top: 15.0,bottom: 5.0,right: 5.0),// 1. EdgeInsets.all(8.0) 所有内边距  2. EdgeInsets.only(left: 12.0, top: 4.0) 设置内边距      child: Text(        '欢迎进入登录界面',        textAlign: TextAlign.center,        //字体是否隐藏 inherit: true,        //字体样式 大小,颜色 color: Color(0xFFffffff) ,color: Color.fromARGB(255, 150, 150, 150),        //字体斜体  fontStyle: FontStyle.italic        //字体间距  letterSpacing: 10.0,        //上划线 decoration: TextDecoration.overline,(lineThrough 删除线)(underline 下划线)        //波浪线 decorationStyle: TextDecorationStyle.wavy (dashed)(dotted)        //字重 fontWeight: FontWeight.w700        style: TextStyle(inherit: false,fontSize: 22.0,color: Color(0xFF000000),/*fontStyle: FontStyle.italic,*/letterSpacing: 2.0,/*decoration: TextDecoration.overline,            decorationStyle: TextDecorationStyle.wavy,*/fontWeight: FontWeight.w700),      ),    );  }

 

继承关系

 

  •  
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Text

 

 

 

四、Image组件使用和分辨率的适配

 

在 Flutter 中也有多种图片显示方式,用来加载不同形式的图片:

 

Image:通过ImageProvider来加载图片

Image.asset:用来加载本地资源图片

Image.file:用来加载本地(File文件)图片

Image.network:用来加载网络图片

Image.memory:用来加载Uint8List资源(byte数组)图片

实例:通过 new Image.asset("images/user_photo.png",

                  alignment: Alignment.topCenter,height: 100,width: 100,), //设置图片logo

设置读取本地图片资源显示,并且设置图片的位置和大小。

 

  •  
@override  Widget build(BuildContext context){    //界面布局    return Scaffold(        appBar: AppBar(//设置标题            title: Text("登录",                style: TextStyle(color: Colors.white)),            iconTheme: IconThemeData(color: Colors.white)        ),        body: Form(//页面内容            child: ListView(              key: _formKey,              padding: const EdgeInsets.all(10.0),              children: <Widget>[                SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度                new Image.asset("images/user_photo.png",                  alignment: Alignment.topCenter,height: 100,width: 100,), //设置图片logo                            ],            )        )    );

 

在本地图片加载显示的时候 为了区分分辨率的大小,需要有多种分辨率

Image.asset

 

加载一个本地资源图片,和 Android 一样,有多种分辨率的图片可供选择,但是沿袭的是 iOS 的图片风格,分为 1x,2x,3x,具体做法是在项目的根目录下创建两个文件夹,如下图所示:

 

 

 

具体生命图片的操作 请查看之前章节的文字说明。

 

五、TextField 输入框的使用和数据控制器

 

文本输入框 TextField,类似于iOS中的UITextField和Android中的EditText和Web中的TextInput。主要是为用户提供输入文本提供方便

 

构造方法

 

 

  •  
const TextField({    Key key,    this.controller,            //控制器,控制TextField文字    this.focusNode,    this.decoration: const InputDecoration(),      //输入器装饰    TextInputType keyboardType: TextInputType.text, //输入的类型    this.style,    this.textAlign: TextAlign.start,    this.autofocus: false,    this.obscureText: false,  //是否隐藏输入    this.autocorrect: true,    this.maxLines: 1,    this.maxLength,    this.maxLengthEnforced: true,    this.onChanged,            //文字改变触发    this.onSubmitted,          //文字提交触发(键盘按键)    this.onEditingComplete,  //当用户提交可编辑内容时调用    this.inputFormatters,    this.enabled,    this.cursorWidth = 2.0,    this.cursorRadius,    this.cursorColor,    this.keyboardAppearance,  })

 

只是知道构造函数还是不够的,具体使用是怎么样的呢,需要实现了看效果,下面我们通过一个实例来了解一下 输入框的显示和控制器获取数据

 

 

  •  
@override  Widget build(BuildContext context){    //界面布局    return Scaffold(        body: Form(//页面内容            child: ListView(              key: _formKey,              padding: const EdgeInsets.all(10.0),              children: <Widget>[                SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度                //----用户名                Row(//水平布局控件                  crossAxisAlignment: CrossAxisAlignment.center,                  children: <Widget>[                    Text("用户名:"),//                    Image.asset("act_login_et_username.PNG"),                    Expanded(child: TextField(                      controller: usernameCtrl,                      decoration: InputDecoration(                          prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中                          hintText: "注册邮箱",//可以设置用户名在左边                          hintStyle: TextStyle( color: const Color(0xFF000000)                          ),                          border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线                            borderRadius: const BorderRadius.all(const Radius.circular(6.0)),//                              borderSide: BorderSide(color: kYellow),//边框颜色变化                            borderSide: BorderSide(color: Color(0x00000000)),                          ),                          contentPadding: const EdgeInsets.all(10.0)                      ),                    ))                  ],                ),              ],            )        )    );  }

 

输入框放置在一个 Row水平布局组件中,因为输入框需要显示主题,输入框,主题图标,所以需要放置在 children: <Widget>[中实现,

 

Image.asset("act_login_et_username.PNG"),  显示一个图标放置在左边

Expanded(child: TextField 添加输入框

添加控制器,用来控制数据  controller: usernameCtrl,

添加 decoration属性

 

  •  
decoration: InputDecoration(                          prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中                          hintText: "注册邮箱",//可以设置用户名在左边                          hintStyle: TextStyle( color: const Color(0xFF000000)),//设置输入框默认信息的样式                          border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线                            borderRadius: const BorderRadius.all(const Radius.circular(6.0)),//                              borderSide: BorderSide(color: kYellow),//边框颜色变化                            borderSide: BorderSide(color: Color(0x00000000)),                          ),                          contentPadding: const EdgeInsets.all(10.0)                      ),数据控制器添加

 

 

在TextField 中添加 属性controller 

 

  •  
Expanded(child: TextField(                      controller: usernameCtrl,                      decoration: InputDecoration(                          prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中                          hintText: "注册邮箱",//可以设置用户名在左边                          hintStyle: TextStyle( color: const Color(0xFF000000)                          ),                          border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线                            borderRadius: const BorderRadius.all(const Radius.circular(6.0)),//                              borderSide: BorderSide(color: kYellow),//边框颜色变化                            borderSide: BorderSide(color: Color(0x00000000)),                          ),                          contentPadding: const EdgeInsets.all(10.0)                      ),                    ))控制器获取数据 var loginBtn = Builder(builder: (ctx) {  return CommonButton(text: "登录", onTap: () {    // 拿到用户输入的账号密码    String username = usernameCtrl.text.trim();    if (username.isEmpty) {      Scaffold.of(ctx).showSnackBar(SnackBar(        content: Text("账号和密码不能为空!"),      ));      return;    }    // 关闭键盘    FocusScope.of(context).requestFocus(FocusNode());    // 发送给webview,让webview登录后再取回token    loginPush(username, password);  });});

 

 

 

六、 符合组件Container的使用实现(点击按钮)和类引用

 

提交登录或者提交某一个操作的时候,需要一个按钮效果并支持点击后操作

如下通过引用外部的封装的类实现 点击效果

 

  •  
@override  Widget build(BuildContext context){    //登录    var loginBtn = Builder(builder: (ctx) {      return CommonButton(text: "登录", onTap: () {        // 拿到用户输入的账号密码        String username = usernameCtrl.text.trim();        String password = passwordCtrl.text.trim();        if (username.isEmpty || password.isEmpty) {          Scaffold.of(ctx).showSnackBar(SnackBar(            content: Text("账号和密码不能为空!"),          ));          return;        }        // 关闭键盘        FocusScope.of(context).requestFocus(FocusNode());        // 发送给webview,让webview登录后再取回token        loginPush(username, password);      });    });    //界面布局    return Scaffold(        body: Form(//页面内容            child: ListView(              key: _formKey,              padding: const EdgeInsets.all(10.0),              children: <Widget>[                SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度                //----登录按钮                loginBtn,               ],            )        )    );  }

 

引用类中通过  Container实现点击效果,

 

Container在Flutter中非常常见。官方给出的简介,是一个结合了绘制(painting)、定位(positioning)以及尺寸(sizing)widget的widget。

 

它是一个组合的widget,内部有绘制widget、定位widget、尺寸widget。后续看到的不少widget,都是通过一些更基础的widget组合而成。

 

Container会遵循如下顺序去尝试布局:

 

对齐(alignment);

调节自身尺寸适合子节点;

采用width、height以及constraints布局;

扩展自身去适应父节点;

调节自身到足够小。

  •  
class CommonButton extends StatefulWidget {  final String text;  final GestureTapCallback onTap;   CommonButton({@required this.text, @required this.onTap});   @override  State<StatefulWidget> createState() => CommonButtonState();} class CommonButtonState extends State<CommonButton> {   Color color = ThemeColorUtils.currentColorTheme;  TextStyle textStyle = TextStyle(color: Colors.white, fontSize: 17);   @override  Widget build(BuildContext context) {    return InkWell(      onTap: () {        this.widget.onTap();      },      child: Container(        height: 45,        decoration: BoxDecoration(            color: color,            border: Border.all(color: const Color(0xffcccccc)),            borderRadius: BorderRadius.all(Radius.circular(30))        ),        child: Center(          child: Text(this.widget.text, style: textStyle,),        ),      ),    );  } }

 

上面的简单实例,就是以 复合组件 Container  类设置 高度,圆角,颜色,居中的文字的个组合成了一个按钮效果 。

 

七、提示信息的添加

 

提示框是很常用的一种效果,比如:添加操作成功提示,获取数据成功结果提示,等等

这种使用率分成高的,效果需要单独分离出来,实现自定义效果,需要用到 OverlayState组件,

 

什么是OverlayState?

 

Flutter事实上有一个Overlay的Widge,它是一个StatefullWidget,它的createState方法获取的就是OverlayState对象。

Overlay可以认为是一个UI上面的蒙版/浮空层,使用起来类似Stack,如何使用:

通过Overlay.of获得OverlayState对象,调用OverlayState.insert添加OverlayEntry,当不需要的时候,通过OverlayEntry.remove移除OverlayEntry。

 

 

  •  
import 'package:flutter/material.dart'; class Toast {  static OverlayEntry _overlayEntry; //toast靠它加到屏幕上  static bool _showing = false; //toast是否正在showing  static DateTime _startedTime; //开启一个新toast的当前时间,用于对比是否已经展示了足够时间  static String _msg;   static void show(      BuildContext context,      String msg,      ) async {    assert(msg != null);    _msg = msg;    _startedTime = DateTime.now();    //获取OverlayState    OverlayState overlayState = Overlay.of(context);    _showing = true;    if (_overlayEntry == null) {      _overlayEntry = OverlayEntry(          builder: (BuildContext context) => Positioned(            //top值,可以改变这个值来改变toast在屏幕中的位置            top: MediaQuery.of(context).size.height * 2 / 3,            child: Container(                alignment: Alignment.center,                width: MediaQuery.of(context).size.width,                child: Padding(                  padding: EdgeInsets.symmetric(horizontal: 80.0),                  child: AnimatedOpacity(                    opacity: _showing ? 1.0 : 0.0, //目标透明度                    duration: _showing                        ? Duration(milliseconds: 100)                        : Duration(milliseconds: 400),                    child: _buildToastWidget(),                  ),                )),          ));      overlayState.insert(_overlayEntry);    } else {      //重新绘制UI,类似setState      _overlayEntry.markNeedsBuild();    }    await Future.delayed(Duration(milliseconds: 2000)); //等待两秒     //2秒后 到底消失不消失    if (DateTime.now().difference(_startedTime).inMilliseconds >= 2000) {      _showing = false;      _overlayEntry.markNeedsBuild();    }  }   //toast绘制  static _buildToastWidget() {    return Center(      child: Card(        color: Colors.black87,        child: Padding(          padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),          child: Text(            _msg,            style: TextStyle(              fontSize: 16.0,              color: Colors.white,            ),          ),        ),      ),    );  }}

 

八、总结

 

本章主要对组件的使用做详细介绍和使用说明

 

需要掌握几种知识点:

1. From 组件

2.ListView组件

3.Row 和Clounm 水平和竖直组件

4.符合组件的使用 Container

5.图片分辨率的设置

6.输入框 ,控制器的使用

7.自定义提示框的添加

 

 

完整代码地址:https://github.com/chenjianpeng/flutter/tree/master/flutter_demo002

 

flutter(五)组件的使用,登录界面_输入框

 

- End -

 

flutter(五)组件的使用,登录界面_ico_02

长按二维码关注

期待您的加入

 

flutter(五)组件的使用,登录界面_输入框_03