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

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

上篇文章为大家讲述了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(),//登录标题,外部引用函数显示
              
              ],
            )
        )
    );
  1. 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) 设置内边距
  2. 设置文本的位置,居中,居左,居右等
    textAlign: TextAlign.center,
  3. 设置文本的样式
    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 的图片风格,分为 1x2x3x,具体做法是在项目的根目录下创建两个文件夹,如下图所示:

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

五、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>[中实现,

  1. Image.asset("act_login_et_username.PNG"),  显示一个图标放置在左边
  2. Expanded(child: TextField 添加输入框
  3. 添加控制器,用来控制数据  controller: usernameCtrl,
  4. 添加 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


                                                                                      -END-

                                                                                    程序职场

                                                                           一个执着的职场程序员

                                                                

发布了55 篇原创文章 · 获赞 101 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/jianpengxuexikaifa/article/details/95774710
今日推荐