这样去看Flutter基础和布局就容易多了

3,440 阅读19分钟

目录

本人会针对Flutter从入门到相对熟练【具备开发水平】,在掘金上开辟一个专栏,专门讲述掌握Flutter的基本过程,每周会定期更新1-2篇博客!

前言

自从上一篇博客Flutter从这开始写项目,带着大家大致熟悉了StatelessWidgetStatefulwidget在项目中的简单使用,以及在项目结尾时讲述了两者的生命周期!

本着在Flutter的世界中,万物皆Widget的本质,本篇博客将继续讲述Flutter的基础Widget和布局Widget【内容是基础性质的,但是很重要很重要】

  • Flutter基础Widget【文本、按钮、图片、表单】
  • Flutter布局Widget【单子布局组件、多子布局组件】

希望大家可以跟着敲敲,通过一两个月的了解Flutter知识点,肯定可以具备开发水平的!【动手写!动手写!动手写】,本人会不断提供优质的内容博客给大家,内容目前涵盖Objective-C、Swift、Flutter、小程序的开发。欢迎点赞博客及关注本人,共同进步~~~

Flutter基础Widget

2.1 文本Widget

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

2.1.1 普通文本展示

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

  • 控制文本布局的参数: 如文本对齐方式 textAlign、文本排版方向 textDirection,文本显示最大行数 maxLines、文本截断规则 overflow 等等,这些都是构造函数中的参数; 

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

下面是一些普通文本的简单实用【可直接赋值粘贴

import 'package:flutter/material.dart';

main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:  Text("Flutter基础Widget"),
      ),
      body: ZXYHomeContent(),
    );
  }
}

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      "早发白帝城 \n[唐] 李白\n 朝辞白帝彩云间,千里江陵一日还。\n 两岸猿声啼不住,轻舟已过万重山",
      style: TextStyle(
        fontSize: 20,
        color: Colors.purple,
      ),

    );
  }
}

运行结果如下:

可以通过一些属性来改变Text的布局:

  • textAlign:文本对齐方式,比如TextAlign.center 

  • maxLines:最大显示行数,比如1 

  • overflow:超出部分显示方式,比如TextOverflow.ellipsis 

  • textScaleFactor:控制文本缩放,比如1.24

代码如下【只写出修改的代码,公用的代码都是上面那几个】

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      "早发白帝城 \n[唐] 李白\n 朝辞白帝彩云间,千里江陵一日还。\n 两岸猿声啼不住,轻舟已过万重山",
      textAlign: TextAlign.center,
      maxLines: 3,
      overflow: TextOverflow.ellipsis,
      style: TextStyle(
        fontSize: 20,
        color: Colors.purple,
      ),

    );
  }
}

运行结果如下:【发现少了一行,因为maxLines = 3,可以改成4即可显示

2.1.2 富文本展示

前面展示的文本,都应用了相同的样式,如果希望给他们不同的样式呢? 

  • 比如《早发白帝城》我希望字体更大一点,并且是黑色字体,并且有加粗效果;

  •  比如 李白 希望是红色字体; 

如果希望展示这种混合样式,那么可以利用分片来进行操作【在Android中,可以使用SpannableString,在iOS中,可以使用NSAttributedString完成,了解即可】

代码如下:

class ZXYHomeContent 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: "\n[唐] 李白", style: TextStyle(fontSize: 18, color: Colors.redAccent)),
          TextSpan(text: "\n 朝辞白帝彩云间,千里江陵一日还。\n 两岸猿声啼不住,轻舟已过万重山。")
        ]
      )
    );
  }
}

运行结果如下

2.2 按钮Widget

2.2.1 按钮的基础

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

  • FloatingActionButton:悬浮按钮

  • RaisedButton:Material Design中的button, 一个凸起的材质矩形按钮

  • FlatButton: Material Design中的button,一个没有阴影的材质设计按钮

  • OutlineButton:Material Design中的button,RaisedButton和FlatButton之间的交叉:一个带边框的背景透明的按钮,当按下按钮时,其高度增加,背景变得不透明。

下面直接展示,代码如下

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        FloatingActionButton(
          child: Text("FloatingActionButton"),
          onPressed: () {
            print("FloatingActionButton Click");
          },
        ),
        RaisedButton(
          child: Text("RaisedButton"),
          onPressed: () {
            print("RaisedButton Click");
          },
        ),
        FlatButton(
          child: Text("FlatButton"),
          onPressed: () {
            print("FlatButton Click");
          },
        ),
        OutlineButton(
          child: Text("OutlineButton"),
          onPressed: () {
            print("OutlineButton Click");
          },
        )
      ],
    );
  }
}

运行结果如下

2.2.2 自定义样式

前面的按钮使用的都是默认样式,我们可以通过一些属性来改变按钮的样式

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        RaisedButton(
          child: Text("同意协议", style: TextStyle(color: Colors.white)),
          color: Colors.orange,
          highlightColor: Colors.orange[700],
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
          onPressed: () {
            print("同意协议");
          },
        ),
      ],
    );
  }
}

运行结果如下:

2.3 图片Widget

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

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

  • Image.assets:加载本地资源图片; 

  • Image.network:加载网络中的图片;

2.3.1 加载网络图片

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

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

const Image({
  ...
  this.width, //图片的宽
  this.height, //图片高度
  this.color, //图片的混合色值
  this.colorBlendMode, //混合模式
  this.fit,//缩放模式
  this.alignment = Alignment.center, //对齐方式
  this.repeat = ImageRepeat.noRepeat, //重复方式
  ...
})
  • widthheight:用于设置图片的宽、高,当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小,如果只设置widthheight的其中一个,那么另一个属性默认会按比例缩放,但可以通过下面介绍的fit属性来指定适应规则。

  • fit:该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在BoxFit中定义,它是一个枚举类型,有如下值:

  • fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。

  • cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。

  • contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。

  • fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。

  • fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。

  • none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。

  • colorcolorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而colorBlendMode指定混合模式;

  • repeat:当图片本身大小小于显示空间时,指定图片的重复规则。

我们对其中某些属性做一个演练:

注意,这里用了一个Container,大家可以把它理解成一个UIView或者View,就是一个容器;后面我会专门讲到这个组件的使用;

代码如下

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.network(
            "https://pics0.baidu.com/feed/e1fe9925bc315c606acd1d9c18dcae15485477d8.jpeg?token=2a07fddc2d1d3fd8b357dddb28eeb6c9",
          alignment: Alignment.topCenter,
          repeat: ImageRepeat.repeatY,
          // color: Colors.red,
          colorBlendMode: BlendMode.colorDodge,
        ),
        width: 300,
        height: 300,
        // color: Colors.yellow,
      ),
    );
  }
}

运行结果如下

2.3.2 加载本地图片

加载本地图片稍微麻烦一点,需要将图片引入,并且进行配置:【2x,3x这些配置网络有很多讲解,本篇博客主要不是这个,参考下面也可以实现】

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 300,
        height: 300,
        // color: Colors.yellow,
        child: Image.asset("assets/images/home/home.png"),
      ),
    );
  }
}

运行结果如下:

2.3.3 实现圆角图像

方式一:CircleAvatar

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

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

来实现一个圆形头像:

  • 注意一:这里使用的是NetworkImage,因为backgroundImage要求我们传入一个ImageProvider;

  • ImageProvider是一个抽象类,事实上所有前面创建的Image对象都有包含image属性,该属性就是一个ImageProvider

  • 注意二:这里还在里面添加了一个文字,但是在文字外层包裹了一个Container;

  • 这里Container的作用是为了可以控制文字在其中的位置调整;

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CircleAvatar(
        radius: 100,
        backgroundImage: NetworkImage("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180809%2F738ee6c928994eab813bcd1cff90c361.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611813085&t=ee9e2201ec99388af35fb2c8fd0b9f12"),
        child: Container(
            alignment: Alignment(0, .5),
            width: 200,
            height: 200,
            child: Text("兵长利威尔")
        ),
      ),
    );
  }
}

运行结果如下:

方式二:ClipOval

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

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ClipOval(
        child: Image.network(
          "https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180809%2F738ee6c928994eab813bcd1cff90c361.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611813085&t=ee9e2201ec99388af35fb2c8fd0b9f12",
          width: 200,
          height: 200,
        ),
      ),
    );
  }
}

**方式三:**Container+BoxDecoration

这种方式放在下面讲解Container时来讲这种方式【看下面

2.4 表单Widget

和用户交互的其中一种就是输入框,比如注册、登录、搜索,收集用户输入的内容将其提交到服务器。

2.4.1 TextField的使用

TextField的介绍

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:输入框填充的颜色

  • controller

  • onChanged:监听输入框内容的改变,传入一个回调函数

  • onSubmitted:点击键盘中右下角的down时,会回调的一个函数

TextField的样式以及监听

来演示一下TextField的decoration属性以及监听:

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFieldDemo()
        ],
      ),
    );
  }
}

class TextFieldDemo extends StatefulWidget {
  @override
  _TextFieldDemoState createState() => _TextFieldDemoState();
}

class _TextFieldDemoState extends State<TextFieldDemo> {
  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: InputDecoration(
          icon: Icon(Icons.people),
          labelText: "username",
          hintText: "请输入用户名",
          border: InputBorder.none,
          filled: true,
          fillColor: Colors.lightGreen
      ),
      onChanged: (value) {
        print("onChanged:$value");
      },
      onSubmitted: (value) {
        print("onSubmitted:$value");
      },
    );
  }
}

运行结果:

当点击username时

可以给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方法
}

2.4.2 Form表单的使用

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

Form表单也是一个Widget,可以在里面放入我们的输入框。

但是Form表单中输入框必须是FormField类型的

  • 查看刚刚学过的TextField是继承自StatefulWidget,并不是一个FormField类型;

  • 可以使用TextFormField,它的使用类似于TextField,并且是继承自FormField的;

通过Form的包裹,来实现一个注册的页面:代码如下

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ZXYFormDemo()
        ],
      ),
    );
  }
}

class ZXYFormDemo extends StatefulWidget {
  @override
  _ZXYFormDemoState createState() => _ZXYFormDemoState();
}

class _ZXYFormDemoState extends State<ZXYFormDemo> {
  @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("点击了注册按钮");
              },
            ),
          )
        ],
      ),
    );
  }
}

运行结果:

保存和获取表单数据

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

  • 1、需要监听注册按钮的点击,在之前已经监听的onPressed传入的回调中来做即可。(当然,如果嵌套太多,待会儿可以将它抽取到一个单独的方法中)

  • 2、监听到按钮点击时,同时获取用户名密码的表单信息。

如何同时获取用户名密码的表单信息?

  • 如果调用Form的State对象的save方法,就会调用Form中放入的TextFormField的onSave回调:

代码如下:

TextFormField(
  decoration: InputDecoration(
    icon: Icon(Icons.people),
    labelText: "用户名或手机号"
  ),
  onSaved: (value) {
    print("用户名:$value");
  },
),
  • 但是,我们有没有办法可以在点击按钮时,拿到 Form对象 来调用它的save方法呢?

**知识点:在Flutter如何可以获取一个通过一个引用获取一个StatefulWidget的State对象呢?
**答案:通过绑定一个GlobalKey即可。

案例代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ZXYFormDemo()
        ],
      ),
    );
  }
}

class ZXYFormDemo extends StatefulWidget {
  @override
  _ZXYFormDemoState createState() => _ZXYFormDemoState();
}

class _ZXYFormDemoState extends State<ZXYFormDemo> {
  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

为了实现界面内组件的各种排布方式,需要进行布局,和其他端不同的是,Flutter中因为万物皆Widget,所以布局也是使用Widget来完成的。
Flutter中的布局组件非常多,有31个用于布局的组件,Flutter布局组件[1];
在学习的过程中,没必要一个个全部掌握,掌握最常用的,一些特殊的组件用到时去查文档即可;
Flutter将布局组件分成了 单子布局组件(Single-child layout widgets) 和 多子布局组件(Multi-child layout widgets)

3.1 单子布局组件

单子布局组件的含义是其只有一个子组件,可以通过设置一些属性设置该子组件所在的位置信息等。
比较常用的单子布局组件有:Align、Center、Padding、Container。

3.1.1 Align组件

Align 介绍

看到Align这个词,就知道与对齐方式有关。
在其他端的开发中(iOS、Android、前端)Align通常只是一个属性而已,但是Flutter中Align也是一个组件。

可以通过源码来看一下Align有哪些属性:

const Align({
  Key key,
  this.alignment: Alignment.center, // 对齐方式,默认居中对齐
  this.widthFactor, // 宽度因子,不设置的情况,会尽可能大
  this.heightFactor, // 高度因子,不设置的情况,会尽可能大
  Widget child // 要布局的子Widget
})

这里特别解释一下widthFactorheightFactor作用:

  • 因为子组件在父组件中的对齐方式必须有一个前提,就是父组件得知道自己的范围(宽度和高度);

  • 如果widthFactorheightFactor不设置,那么默认Align会尽可能的大(尽可能占据自己所在的父组件);

  • 也可以对他们进行设置,比如widthFactor设置为3,那么相对于Align的宽度是子组件跨度的3倍;

**Center 组件
**

Center介绍

事实上Center组件继承自Align,只是将alignment设置为Alignment.center。源码分析:

class Center extends Align {
  const Center({
    Key key,
    double widthFactor,
    double heightFactor,
    Widget child
  }) : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}

center 演练

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Align(
      child: Icon(Icons.pets, size: 36, color: Colors.red),
      alignment: Alignment.bottomRight,
      widthFactor: 3,
      heightFactor: 3,
    );
  }
}

Padding组件

Padding介绍

Padding组件在其他端也是一个属性而已,但是在Flutter中是一个Widget,但是Flutter中没有Margin这样一个Widget,这是因为外边距也可以通过Padding来完成。
Padding通常用于设置子Widget到父Widget的边距(你可以称之为是父组件的内边距或子Widget的外边距)。

源码分析:

const Padding({
  Key key,
  @required this.padding, // EdgeInsetsGeometry类型(抽象类),使用EdgeInsets
  Widget child,
})

Padding演练

代码演练:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(20),
      child: Text(
        "莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。",
        style: TextStyle(
            color: Colors.redAccent,
            fontSize: 18
        ),
      ),
    );
  }
}

运行结果如下

3.1.2 Container组件

Container组件类似于其他Android中的View,iOS中的UIView。
如果你需要一个视图,有一个背景颜色、图像、有固定的尺寸、需要一个边框、圆角等效果,那么就可以使用Container组件。

Container介绍

Container在开发中被使用的频率是非常高的,特别是经常会将其作为容器组件。下面我们来看一下Container有哪些属性:

Container({
  this.alignment,
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
})

大多数属性在介绍其它容器时都已经介绍过了,不再赘述,但有两点需要说明:

  • 容器的大小可以通过widthheight属性来指定,也可以通过constraints来指定,如果同时存在时,widthheight优先。实际上Container内部会根据widthheight来生成一个constraints

  • colordecoration是互斥的,实际上,当指定color时,Container内会自动创建一个decoration;

  • decoration属性稍后详细学习;

Container 演练

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromRGBO(3, 3, 255, .5),
        width: 100,
        height: 100,
        child: Icon(Icons.pets, size: 32, color: Colors.white),
      ),
    );
  }
}

BoxDecoration

Container有一个非常重要的属性 decoration

  • 对应的类型是Decoration类型,但是它是一个抽象类。

  • 在开发中,经常使用它的实现类BoxDecoration来进行实例化。

BoxDecoration常见属性:

const BoxDecoration({
    this.color, // 颜色,会和Container中的color属性冲突
    this.image, // 背景图片
    this.border, // 边框,对应类型是Border类型,里面每一个边框使用BorderSide
    this.borderRadius, // 圆角效果
    this.boxShadow, // 阴影效果
    this.gradient, // 渐变效果
    this.backgroundBlendMode, // 背景混合
    this.shape = BoxShape.rectangle, // 形变
  })

部分效果演示:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
//        color: Color.fromRGBO(3, 3, 255, .5),
        width: 150,
        height: 150,
        child: Icon(Icons.pets, size: 32, color: Colors.white),
        decoration: BoxDecoration(
            color: Colors.amber, // 背景颜色
            border: Border.all(
                color: Colors.redAccent,
                width: 3,
                style: BorderStyle.solid
            ), // 这里也可以使用Border.all统一设置
//            top: BorderSide(
//              color: Colors.redAccent,
//              width: 3,
//              style: BorderStyle.solid
//            ),
            borderRadius: BorderRadius.circular(20), // 这里也可以使用.only分别设置
            boxShadow: [
              BoxShadow(
                  offset: Offset(5, 5),
                  color: Colors.purple,
                  blurRadius: 5
              )
            ],
//          shape: BoxShape.circle, // 会和borderRadius冲突
            gradient: LinearGradient(
                colors: [
                  Colors.green,
                  Colors.red
                ]
            )
        ),
      ),
    );
  }
}

运行结果如下:

实现圆角图像

上面提到可以通过 Container+BoxDecoration来实现圆角图像。

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20),
            image: DecorationImage(
              image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
            )
        ),
      ),
    );
  }
}

运行结果如下

3.2 多子布局组件

在开发中,经常需要将多个Widget放在一起进行布局,比如水平方向、垂直方向排列,甚至有时候需要他们进行层叠,比如图片上面放一段文字等;
这个时候需要使用多子布局组件(Multi-child layout widgets)。
比较常用的多子布局组件是Row、Column、Stack,来学习一下他们的使用。

3.2.1 Flex组件

事实上,学习的Row组件和Column组件都继承自Flex组件。

  • Flex组件和Row、Column属性主要的区别就是多一个direction。

  • 当direction的值为Axis.horizontal的时候,则是Row。

  • 当direction的值为Axis.vertical的时候,则是Column。

Row组件

Row组件用于将所有的子Widget排成一行,实际上这种布局应该是借鉴于Web的Flex布局。
如果熟悉Flex布局,会发现非常简单。

从源码中查看Row的属性:

Row({
  Key key,
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, // 主轴对齐方式
  MainAxisSize mainAxisSize = MainAxisSize.max, // 水平方向尽可能大
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, // 交叉处对齐方式
  TextDirection textDirection, // 水平方向子widget的布局顺序(默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左))
  VerticalDirection verticalDirection = VerticalDirection.down, // 表示Row纵轴(垂直)的对齐方向
  TextBaseline textBaseline, // 如果上面是baseline对齐方式,那么选择什么模式(有两种可选)
  List<Widget> children = const <Widget>[],
})

Row演练

来对部分属性进行简单的代码演练,其他一些属性大家自己学习一下

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.max,
      children: <Widget>[
        Container(color: Colors.red, width: 60, height: 60),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Container(color: Colors.orange, width: 100, height: 100),
      ],
    );
  }
}

运行结果如下:

Expanded

如果希望红色和黄色的Container Widget不要设置固定的宽度,而是占据剩余的部分,这个时候应该如何处理呢?

这个时候可以使用 Expanded 来包裹 Container Widget,并且将它的宽度不设置值;

flex属性,弹性系数,Row会根据两个Expanded的弹性系数来决定它们占据剩下空间的比例

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Container(color: Colors.red, height: 60),
        ),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Expanded(
          flex: 1,
          child: Container(color: Colors.orange, height: 100),
        )
      ],
    );
  }
}

运行结果如下

3.2.2 Column组件

Column组件用于将所有的子Widget排成一列,学会了前面的Row后,Column只是和row的方向不同而已。

Column介绍

直接看它的源码:我们发现和Row属性是一致的,不再解释

 Column({
    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的代码中Row改为Column,查看代码运行效果:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Container(color: Colors.red, width: 60),
        ),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Expanded(
          flex: 1,
          child: Container(color: Colors.orange, width: 100),
        )
      ],
    );
  }
}

运行结果:

3.2.3 Stack组件

在开发中,多个组件很有可能需要重叠显示,比如在一张图片上显示文字或者一个按钮等。
在Android中可以使用Frame来实现,在Web端可以使用绝对定位,在Flutter中我们需要使用层叠布局Stack

Stack介绍

通过源码来看一下Stack有哪些属性:

Stack({
  Key key,
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
  • alignment:此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子widget。所谓部分定位,在这里**特指没有在某一个轴上定位:**left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位。

  • textDirection:和Row、Wrap的textDirection功能一样,都用于决定alignment对齐的参考系即:textDirection的值为TextDirection.ltr,则alignment的start代表左,end代表右;textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左。

  • fit:此参数用于决定没有定位的子widget如何去适应Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示扩伸到Stack的大小。

  • overflow:此属性决定如何显示超出Stack显示空间的子widget,值为Overflow.clip时,超出部分会被剪裁(隐藏),值为Overflow.visible 时则不会。

stack演练

代码如下:

class ZXYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Container(
          color: Colors.purple,
          width: 300,
          height: 300,
        ),
        Positioned(
            left: 20,
            top: 20,
            child: Icon(Icons.favorite, size: 50, color: Colors.white)
        ),
        Positioned(
          bottom: 20,
          right: 20,
          child: Text("你好啊,程序员", style: TextStyle(fontSize: 20, color: Colors.white)),
        )
      ],
    );
  }
}

运行结果如下:

机会❤️❤️❤️🌹🌹🌹

如果想和我一起共建抖音,成为一名bytedancer,Come on。期待你的加入!!!

截屏2022-06-08 下午6.09.11.png

总结

今天这篇文章,详细介绍了Flutter的基本Widget和布局Widget,从而带领大家来熟悉Widget在项目中的使用。【读完本篇是不是有种体会到了Flutter的万物皆Widget的思想啦

通过上面内容的讲解,大家可以写出简单的登录界面以及UI的布局展示【该使用何种Widget】。大家可以手动的编写上面的Demo例子, 相信每个星期1-2篇相关博客,可以加深大家对Flutter项目的认知和感触【动手写!动手写!动手写!!!

感谢大家的点赞作品及关注本人,共同进步,共勉!!!