Flutter学习之认知基础组件
一、前言
前一天,学习了Dart语法,对Dart的语法和特性有了更深一步的了解。今天,来学习Flutter的基础控件,身为Android开发者都知道,一开始入坑Android就要熟悉学习其控件,如:TextView,ImageView,Button,ListView,RecycleView等。为什么要学习呢?因为平时的开发都离不开这些控件,UI的呈现都是有这些控件组成的,因此,其重要性就不用说了。对于Flutter来讲,基础控件(widget)就更加重要了。Flutter和Android有所不一样,Android布局包含布局(RelativeLayout,LinearLayout,ConstrainLayou)和组件。Flutter的一切都是Widget,包括最顶层布局也是Widget,一个页面有很多很多的Widget组合而成,Widget也称为装饰品,窗口小部件。
二、Widget简介
在Flutter里,UI控件就是Widget,Widget根据不同的功能可以分为结构元素(如按钮或菜单),文本样式(字体或者颜色方案),布局属性(如填充,对齐,居中),可以这么理解,一个flutter的页面是有一棵树型的Widget组成,包括根节点,树枝和树叶,全都是Widget,只是Widget嵌套Widget,那就可以用下面这张图来表示:
在Flutter中,Widget是一切的基础,作为响应式渲染,属于MVVM的实现机制,通过修改数据,再用setState设置数据,Flutter会自动通过绑定的数据更新Widget,所以在平时开发中,开发者需要的就是实现Widget界面,和数据绑定起来。在平时,用的最多就是StatelessWidget和StatefulWidget这两种Widget,StatelessWidget表示无状态的,StatefulWidget表示有状态的。这里怎么理解呢?在Flutter中每个页面都是一帧,无状态就是保持在那一帧,总而言之就是不能跟用户交互,当有状态的Widget当数据更新时,其实是绘制了新的Widget,也就是UI发生了变化,只是State实现了跨帧数据同步保存。这里给大家说下,在Android Studio看源码的两个工具: 左边一栏Structure结构(看当前文件,win下的快捷键是(Alt+7))和右边Hierarchy继承关系(看当前类,win下快捷键是F4)都可以帮助你阅读源码。因为StatelessWidget和StatefulWidget用的最多,现在只需要用到这两个,就先学习这两个Widget。1.StatelessWidget
源码StatelessWidget只有三个方法:
- const StatelessWidget({Key key}):super(key:key):初始化子类的[key]。这个key类是Widget、Element、SemanticsNode的唯一标识符,是用来控制Widget数中替换Widget的时候使用的。
- StatelessElement createElement():创建一个[StatelessElement]来管理这个小部件在树中的位置,源码解释:子类重写此方法是不常见的,那这个方法也不用管,只需要知道这个方法用来管理自身在Widget树中的位置。
- Widget build(BuildContext context):描述这部件呈现用户界面的部分。对于StatelessWidget,当Widget第一次插入到树中,或者父节点更改了配置和所依赖的[InheritedWidget]改变,都会被重新调用。
这里说下如何启动一个Flutter应用,并使用Flutter框架:
import 'package:flutter/material.dart'; void main() {return runApp(Widget app); } 复制代码其实就是在main()函数中调用runApp函数。下面直接直接上例子,继承StatelessWidget,通过build方法返回一个控件:
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial风格的小控件 void main(){//运行程序runApp(MyApp(null)); } //继承无状态的StatelessWidget 使程序自身变为Wiget class MyApp extends StatelessWidget{//要显示的内容final String text;//数据内容可以通过构造方法传递进来MyApp(this.text);//重写build方法 返回你需要的控件Widget build(BuildContext context) {// TODO: implement buildreturn Container(//红色背景color: Colors.red,//高度 现在没用 会撑满整个屏幕height: 200,//宽度 运行效果会撑满整个屏幕width: 200,//内容居中alignment: Alignment.center,//Text控件child: new Text(//Dart语法中 ?? 表示如果text为空,就会返回??号的内容text ?? "my name is Knight",textDirection: TextDirection.ltr,//需要加上这句不然报 RichText widgets require a Directionality widget ancestor.),);} } 复制代码Widget和Widget之间通过child进行嵌套,有些Widget只能有一个child。就像上面的Container,有些Widget可以有多个child,像Colum布局。上面例子根布局是Container,Container嵌套了Text。
2.StatefulWidget
什么是有状态的控件呢?状态是在创建控件可以同步读取信息,并且在控件的生命周期内可以改变,当控件状态发生改变时使用State.setState来及时更新,源码也是只有三个方法:
前两个方法和StatelessWidget一样的,而createState()这个方法源码注释是:在Widget树中给定的位置创建此可变状态的小部件,子类应该重写此方法返回新建的,关联子类的实例。当调用一个StatefulWidget,框架就会调用createState这个方法,当一个StatefulWidget从Widget树中移除,再次插入树中,那么会再次调用createState来创建一个新的State对象,这样做简化了State对象的生命周期。 需要创建管理的是主要是State,StatefulWidget用起来麻烦一些,他需要一个State,例子如下: //继承StatefulWidget class StateWidget extends StatefulWidget{State createState(){return _StateWidget();} }class _StateWidget extends State<StateWidget>{//重写build方法Widget build(BuildContext context){} } 复制代码简单观察上面代码,大致流程还是和StatelessWidget一样的,build方法照样返回Widget,不过在StatefulWidget将这个方法放在createState里面。这里细想一下,也知道为什么要这样做,因为当状态改变,就会回调createState方法,重新调用build方法重新创建UI,下面通过每两秒改变UI这个例子来加深理解:
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial风格的小控件 import 'dart:async';//记得导库 void main(){//运行程序runApp(StateWidget()); } //控件继承State class _StateWidget extends State<StateWidget>{int Number = 0;String text;//构造函数_StateWidget(this.text);void initState(){//初始化,这个函数在控件的生命周期内调用一次super.initState();print("进入initState");//3秒后改变text的内容new Future.delayed(const Duration(seconds: 3),(){setState(() {Number++;text = "已经改变数值,数值现在是$Number";});});}void dispose(){//销毁super.dispose();print('销毁');}void didChangeDependencies(){//在initState之后调super.didChangeDependencies();print('进入didChange');}//重写build方法Widget build(BuildContext context){return Container(//红色背景color: Colors.red,//内容居中alignment: Alignment.center,//Text控件child: new Text(//Dart语法中 ?? 表示如果text为空,就会返回??号的内容text ?? "没改变数值",textDirection: TextDirection.ltr,//需要加上这句不然报 RichText widgets require a Directionality widget ancestor.),);} } 复制代码上面例子可以知道知道:在State可以动态更改数据,在调用setState后,改变的数据会除法Widget重新构建,上面代码还写了三个生命周期方法,这里简单说一下:
- initState:初始化操作
- didChangeDependencies:在initState之后调用,可以获取其他State
- dispose:销毁
平时开发中在build实现布局的摆放,把数据添加Widget,通过setState改变数据。那如果很高频率取改变数据,性能肯定受影响,以下三点可以减少重新构建有状态控件的影响:
三、Flutter页面
Flutter有显示的Widget和完整页面呈现的Widget,常见的有MaterialApp、Scaffold、Appbar、Text、Image、FlatButton,下面以表格形式简单列一下:
下面一个个简单上例子介绍:1.MaterialApp
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial风格的小控件 void main(){//运行程序runApp(MyApp()); }//用无状态控件显示 class MyApp extends StatelessWidget{Widget build(BuildContext context){return MaterialApp(//标题title:'Widget_Demo',//主题色theme:ThemeData(//设置为蓝色primarySwatch: Colors.blue),//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面home:MyHomePage(),);} }class MyHomePage extends StatelessWidget{Widget build(BuildContext context){return Scaffold(//设置appbarappBar:new AppBar(title:new Text('This is a Demo'),),//主体body:new Center(//在屏幕中央显示一个文本child:new Text('Hello'),),);} } 复制代码效果如下图:
上面可以看到MaterialApp作为了主界面入口。2.Scaffold
上面例子home:MyHomePage()这里返回了ScaffoldWidget,而这个Widget正是我们所看到的页面,看到Scaffold包含了appBar和body,一开始说到,Scaffold也包含Drawers,下面实现一下:
Widget build(BuildContext context){return Scaffold(//设置appbarappBar:new AppBar(title:new Text('This is a Demo'),),//主体body:new Center(//在屏幕中央显示一个文本child:new Text('Hello'),),//左侧抽屉drawer:Drawer(//添加一个空的ListViewchild:ListView(),),);} 复制代码效果如下:
下面往抽屉里添加点东西,就添加ListView,代码如下: //左侧抽屉drawer:Drawer(child:ListView(//设置paddingpadding:EdgeInsets.zero,children: <Widget>[//据说这里可以替换自定义的header//userHeader,ListTile(//标题内容title: Text("This is Item_one"),//前置图标leading: new CircleAvatar(child:new Icon(Icons.scanner),),),ListTile(//标题内容title: Text("This is Item_two"),//前置图标leading: new CircleAvatar(child:new Icon(Icons.list),),),ListTile(//标题内容title: Text("This is Item_three"),//前置图标leading: new CircleAvatar(child:new Icon(Icons.score),),),],),), 复制代码运行效果就是抽屉里加了三行内容的ListView。
3.AppBar
下面设置一些AppBar属性,玩玩:
//设置appbarappBar: new AppBar(//AppBar内容显示title: new Text('This is a Demo'),//前置图标leading: new Icon(Icons.home),//背景颜色 改为红色backgroundColor: Colors.red,//设置为标题内容居中centerTitle: true,//一个 Widget 列表,代表 Toolbar 中所显示的菜单,// 对于常用的菜单,通常使用 IconButton 来表示;对于不常用的菜单通常使用 PopupMenuButton 来显示为三个点,点击后弹出二级菜单actions: <Widget>[//IconButtonnew IconButton(//图标icon: new Icon(Icons.add_a_photo),//提示tooltip: 'Add photo',//点击事件onPressed: () {},),//菜单弹出按钮new PopupMenuButton<String>(itemBuilder: (BuildContext context) {return <PopupMenuItem<String>>[new PopupMenuItem<String>(value: "one", child: new Text('This one')),new PopupMenuItem<String>(value: "two", child: new Text('This two')),];},//选择点击事件onSelected: (String action) {switch (action) {case "one"://增加点击逻辑break;case "two"://增加点击逻辑break;}},),],), 复制代码效果如下:
可以看到,上面Appbar上加了前置图标、拍照图标、菜单弹出按钮、阴影。4.Text
下面用Text来展示文本,把上面例子用文本显示中间的Hello单独抽出来,如下:
//主体body: new Center(//在屏幕中央显示一个文本 改为自定义样式child: new CustomTextStyle('This is a Text'),),//单独文本样式 class CustomTextStyle extends StatelessWidget{String text;//构造函数 参数外部传进来CustomTextStyle(this.text);Widget build(BuildContext context){return Text(text ?? "Hello");} } 复制代码下面把文本字体大小修改,字体样式修改,背景颜色改改:
//文本 : 单独文本样式 class CustomTextStyle extends StatelessWidget {Paint pg = Paint();String text;//构造函数 参数外部传进来CustomTextStyle(this.text);Widget build(BuildContext context) {//设置画笔颜色为黑色pg.color = Color(0xFF000000);return Text(text ?? "Hello",style: TextStyle(//颜色color: Colors.blue,//字体大小fontSize: 14,//字体加粗fontWeight: FontWeight.bold,//文本背景颜色background: pg),);} } 复制代码上面效果是:
还有很多的属性,根据需要去设置就行: const TextStyle({this.inherit = true,this.color,//文本样式this.fontSize,//字体大小this.fontWeight,//绘制文本时的字体粗细this.fontStyle,//字体变体this.letterSpacing,//水平字母之间的空间间隔(逻辑像素为单位),可以负值this.wordSpacing,//单词之间添加的空间间隔(逻辑像素为单位),可以负值this.textBaseline,//对齐文本的水平线this.height,//文本行与行的高度,作为字体代销的倍数this.locale,//用于选择区域定字形的语言环境this.foreground,//文本的前景色,不能与color共同设置this.background,//文本背景色this.shadows,//Flutter Decoration背景设定(边框,圆角,阴影,渐变等)this.decoration,//绘制文本装饰,添加上下划线,删除线this.decorationColor,//文本装饰的颜色this.decorationStyle,//文本装饰的样式,控制画虚线,点,波浪线this.debugLabel,String fontFamily,//使用字体的名称String package,}) 复制代码5.RichText
这是显示丰富样式的文本,这什么意思呢?Text只能显示一种样式的文字,如果想在一段文字中显示多种样式,就好像Android里面的SpannableString,就需要使用RichText,直接上例子:
//富文本样式 class RichWidget extends StatelessWidget {Widget build(BuildContext context) {return RichText(text: TextSpan(text: 'This is RichText',style: new TextStyle(//false的时候不显示inherit: true,//字体大小fontSize: 16,//黑色color: Colors.black),children: <TextSpan>[new TextSpan(text: 'Android艺术探索',style: new TextStyle(color: Colors.redAccent,//字体粗细fontWeight: FontWeight.bold,),),new TextSpan(text: '第一行代码'),new TextSpan(text: 'Android进阶之光',style: new TextStyle(color: Colors.indigo,//字体样式fontSize: 20,),)],));} }//屏幕中间改为富文本widget//主体body: new Center(//Text在屏幕中央显示一个文本 改为自定义样式//child: new CustomTextStyle('This is a Text'),//富文本child:new RichWidget()), 复制代码效果如下:
6.TextField
下面看看文本输入框,文本输入框平时会经常用到:
body: new Center(//Text在屏幕中央显示一个文本 改为自定义样式//child: new CustomTextStyle('This is a Text'),//富文本//child:new RichWidget()//文本输入框child:new TextFieldWidget() ),//文本输入框 class TextFieldWidget extends StatelessWidget{Widget build(BuildContext context){return TextField();} } 复制代码上面例子只能输入文本内容,如果想要获取输入框内容,就要添加一个controller,通过这个controller添加通知来获取TextField的值,我们一般点击按钮或者需要跟后台交互就要读取controller.text的值:
class MyHomePage extends StatelessWidget {//获取TextEditingControllerfinal editController = TextEditingController();//IconButtonnew IconButton(//图标icon: new Icon(Icons.add_a_photo),//提示tooltip: 'Add photo',//点击事件onPressed: () {//输出print('text inputted: ${editController.text}');//ToastFluttertoast.showToast(msg:'text inputted: ${editController.text}',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);},),....//主体body: new Center(//Text在屏幕中央显示一个文本 改为自定义样式//child: new CustomTextStyle('This is a Text'),//富文本//child:new RichWidget()//文本输入框 以构造函数传递controllerchild:new TextFieldWidget(editController)), } //文本输入框 class TextFieldWidget extends StatelessWidget{final controller;//构造函数传值TextFieldWidget(this.controller);Widget build(BuildContext context){return TextField(controller: controller,);} } 复制代码注意上面用到了Toast,Toast库这里很简单需要两步:
重新运行即可,热重载可能会出现异常。运行在iOS模拟器需要装brew和CocoaPods,有问题运行flutter doctor,它真是如名字一样,就是帮你诊断有没有错误信息,会显示具体信息。效果如下:
下面改一下样式: return TextField(controller: controller,//最大长度,右下角会显示一个输入数量的字符串maxLength: 26,//最大行数maxLines: 1,//是否自动更正autocorrect: true,//是否自动对焦autofocus: true,//设置密码 true:是密码 false:不是秘密obscureText: true,//文本对齐样式textAlign: TextAlign.center,); 复制代码效果如下:
7.Image
Image很好理解就是在界面上区域显示一张图片,而这张图片的来源可以是:本地,网络,资源图片等。下面一一演示一下:
7.1.项目图片资源
首先新建一个资源目录:
在pubspec.yaml中配置图片路径,来识别应用程序所需的assets: class MyHomePage extends StatelessWidget {//主体body: new Center(.....//图片加载child:new ImageWidget()), } //图片 class ImageWidget extends StatelessWidget{Widget build(BuildContext context){//项目资源图片 方式一return Image(image: new AssetImage('images/Image_fluttericon.jpeg'),);//项目资源图片 方式二 // return Image.asset('images/Image_fluttericon.jpeg');} } 复制代码效果如下:
7.2.网络图片加载
下面进行网络图片加载,也是很简单:
class MyHomePage extends StatelessWidget {//图片路径String image_url = "https://ws1.sinaimg.cn/large/0065oQSqgy1fze94uew3jj30qo10cdka.jpg";//主体body: new Center(.....//图片加载child:new ImageWidget(image_url)), } //图片 class ImageWidget extends StatelessWidget{String image_url;ImageWidget(this.image_url);Widget build(BuildContext context){return Image.network(image_url);} } 复制代码效果如下:
下面用一个库来加载和缓存网络图像,也可以与占位符和错误小部件一起使用,在pubspec.yaml添加依赖cached_network_image: ^0.4.1+1,在Dart文件导入这个库import 'package:cached_network_image/cached_network_image.dart'; //图片 class ImageWidget extends StatelessWidget{String image_url;ImageWidget(this.image_url);Widget build(BuildContext context){return new CachedNetworkImage(imageUrl: image_url,//占位符placeholder: new CircularProgressIndicator(),//加载错误时显示的图片errorWidget: new Icon(Icons.error),//宽高width:200,height: 200,);} } 复制代码当图片还没加载出来的时候会显示占位符,当如果加载出错会显示errorWidget的图片。
7.3.声明分辨率相关的图片
另外Flutter可以为当前设备添加合适其分辨率的图像,其实对于Android原生来说,就是在不同分辨率目录下放置不同分辨率的图片,只不过flutter并不是创建drawable-xxdpi文件,而是创建以下文件夹:
.../logo.png .../Mx/logo.png .../Nx/logo.png 复制代码其中M和N是数字标识符,对应于其中包含的图像分辨率,它们指定不同素设备像比例的图片,主资源默认对应于1.0倍的分辨率图片。看下面例子:
在设备像素比率为1.8的设备上,images/2.0x/logo.png 将被选择。对于2.7的设备像素比率,images/3.0x/logo.png将被选择。如果未在Image控件上指定渲染图像的宽度和高度,以便它将占用与主资源相同的屏幕空间量(并不是相同的物理像素),只是分辨率更高。 也就是说,如果images/logo.png是72px乘72px,那么images/3.0x/logo.png应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素(以逻辑像素为单位)。pubspec.yaml中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到的顺序去选择,也就是说1.0x中没有的话会在2.0x中找,2.0x中还没有的话就在3.0x中找。 return Image(// 系统会根据分辨率自动选择不同大小的图片image: AssetImage('images/logo.png'),// ...), 复制代码8.FlatButton
Flutter预先定义了一些按钮控件,如FlatButton,RaisedButton,OutlineButton,IconButton。
下面看看FlatButton,其他的只是样式稍微不一样,大致用法一样。
//按钮 class FlatButtonWidget extends StatelessWidget{Widget build(BuildContext context){return FlatButton(onPressed: (){Fluttertoast.showToast(msg:'你点击了FlatButton',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);},child: Text('FlatButton'),color: Colors.blue,//按钮背景色textColor: Colors.white,//文字的颜色onHighlightChanged: (bool b){//水波纹变化回调},disabledColor: Colors.black,//按钮禁用时的显示的颜色disabledTextColor: Colors.black38,//按钮被禁用的时候文字显示的颜色splashColor: Colors.white,//水波纹的颜色);} } 复制代码上面也设置了一些属性,效果图如下:
四、Flutter布局
Flutter中拥有30多种预定义的布局widget,常用的有Container、Padding、Center、Flex、Row、Colum、ListView、GridView。用一个表格列出它们的特性和使用。
下面一一介绍简单用法:1.Container
一个拥有绘制、定位、调整大小的widget,示意图如下:
下面直接上例子: class MyHomePage extends StatelessWidget {....body:new ContainWidget(),... } //Container布局 class ContainWidget extends StatelessWidget{Widget build(BuildContext context){return Container(child:Text("My name is Knight"),color: Colors.indigo,width:200,//宽height:200,//高margin:EdgeInsets.fromLTRB(5,5,5,5),//设置外边距padding:EdgeInsets.all(30),//内边距);} } 复制代码下面设置边框,添加圆角:
//Container布局 class ContainWidget extends StatelessWidget{Widget build(BuildContext context){return Container(....padding:EdgeInsets.all(30),//内边距decoration: BoxDecoration(//设置边框//背景色color:Colors.redAccent,//圆角borderRadius: BorderRadius.circular(6),),);} } 复制代码运行效果如下:
2.Padding
一个Widget,会给其子Widget添加指定的填充,示意图如下:
class MyHomePage extends StatelessWidget {....body: new PaddingWidget(),... } //Padding布局 class PaddingWidget extends StatelessWidget{Widget build(BuildContext context){return Padding(//设置左上右下内边距为4,10,6,8padding:EdgeInsets.fromLTRB(4, 10, 6, 8),child: Text('My name is Knight'),);} } 复制代码效果图如下:
下面实现Container嵌套Padding: //Container嵌套Padding class ContainPaddWidget extends StatelessWidget{Widget build(BuildContext context){return Container(width:200,//宽height:200,//高child: Padding(padding:EdgeInsets.fromLTRB(4, 10, 6, 8),child: Text("My name is Knight"),),decoration: BoxDecoration(//设置边框//背景色color:Colors.redAccent,//圆角borderRadius: BorderRadius.circular(6),),);} } 复制代码效果图如下:
3.Center
将其子widget居中显示在自身内部的widget,示意图:
//Center class CenterWidget extends StatelessWidget{Widget build(BuildContext context){return Container(width:200,//宽height:200,//高child: Center(child: Text("My name is Knight"),),decoration: BoxDecoration(//设置边框//背景色color:Colors.redAccent,//圆角borderRadius: BorderRadius.circular(6),),);} } 复制代码运行效果如下:
Center作为Container的孩子,Text所以在布局的中间。4.Stack
可以允许其子Widget简单的堆叠在一起,层叠布局,示意图:
下面直接上代码,把之前的布局全部用上试试: class MyHomePage extends StatelessWidget {....body:new Center(child:new StackWidget()),... } //层叠布局 class StackWidget extends StatelessWidget{Widget build(BuildContext context){return Stack(children: <Widget>[new Image.network('https://ws1.sinaimg.cn/large/0065oQSqgy1fze94uew3jj30qo10cdka.jpg',width:300.0,//宽height:300.0,//高),new Opacity(opacity: 0.6,//不透明度child:new Container(width:100.0,height:100.0,color:Colors.redAccent,),),new Opacity(opacity: 0.6,child:new Container(width: 200.0,height:200.0,color:Colors.indigo,),),],);} } 复制代码运行效果:
可以看到控件都按Stack左上角对齐,叠在一起,下面改一下显示位置: //层叠布局 class StackWidget extends StatelessWidget{Widget build(BuildContext context){return Stack(//Aliginment的范围是[-1,1],中心是[0,0].注释有写//和Android一样,左移的取值是往1取,右移是往-1取//这里注意,它是取stack里范围最大的布局为基准,下面是以Container为//基准对齐alignment: new Alignment(-0.6, -0.6),...);} } 复制代码运行效果图:
5.Colum
在垂直方向上排列子Widget,示意图如下:
直接上代码: class MyHomePage extends StatelessWidget {...body:new ColumnWidget(),....} //Column布局 class ColumnWidget extends StatelessWidget {Widget build(BuildContext context) {return Column(children: <Widget>[Container(color:Colors.blue,width: 50,height: 50,),Container(color:Colors.black,width:50,height:50,),Container(color:Colors.green,width:50,height:50,),],);} } 复制代码运行效果:
下面简单设置一下排列方式属性: return Column(//设置垂直方向的对齐方式mainAxisAlignment: MainAxisAlignment.spaceEvenly,...); 复制代码运行效果如下:
垂直方向(主轴上)属性:下面列一下水平方向(交叉轴)的属性:
6.Row
在水平方向上排列子widget的列表,示意图:
直接上代码: class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } //Row class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[Container(color:Colors.blue,width: 50.0,height:50.0,),Container(color:Colors.black,width:50.0,height:50.0,),Container(color:Colors.green,width:50.0,height:50.0,),],);} } 复制代码效果图:
下面简单设置一些属性,和Column没多大差别: return Row(//把剩余空间平分n+1份,然后平分所有的空间mainAxisAlignment: MainAxisAlignment.spaceEvenly,...); 复制代码效果图:
水平方向上(主轴上)属性:7.Expanded
Expanded组件可以使Row、Column、Fiex等子组件在其主轴上方向展开并填充可用的空间,这里注意:Expanded组件必须用在Row、Column、Fiex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或者StatefulWidgets(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸,因此Expanded不能放进RenderObjectWidget),示意图如下:
class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[new RaisedButton(onPressed: (){},color:Colors.green,child:new Text('绿色按钮1')),new Expanded(child:new RaisedButton(onPressed: (){},color:Colors.yellow,child:new Text('黄色按钮2')),),new RaisedButton(onPressed:(){},color:Colors.red,child:new Text('黑色按钮3')),],);} } 复制代码运行效果如下:
class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[Expanded(child:Container(color:Colors.green,padding:EdgeInsets.all(8),height: 40.0,),flex:1,),Expanded(child:Container(color:Colors.yellow,padding:EdgeInsets.all(8),height: 40.0,),flex:2,),Expanded(child:Container(color:Colors.red,padding:EdgeInsets.all(8),height: 40.0,),),],);} } 复制代码上面代码设置了flex,将一行的宽度分成四等分,第一、三child占1/4的区域,第二个child占1/2区域。 效果如下:
8.ListView
我相信这个布局在平时开发会经常用到,这是可滚动的列表控件,ListView是最常用的滚动widget,它在滚动方向上一个接一个地显示它的孩子。在纵轴上,孩子没被要求填充ListView,并且内置ListTitle,示意图如下:
class MyHomePage extends StatelessWidget {....body: new ListViewWidget(new List<String>.generate(1000,(i){return 'Item &i';}),),... } //ListView class ListViewWidget extends StatelessWidget {final List<String> items;ListViewWidget(this.items);Widget build(BuildContext context) {return new ListView.builder(itemCount: items.length,itemBuilder: (context, index) {return new ListTile(title: new Text('This is $index'),);},);} } 复制代码效果图如下:
下面设置水平的ListView: class MyHomePage extends StatelessWidget {....body: new ListViewWidget( new List<String>.generate(1000, (i) { return 'Item &i'; }), ), ... }Widget build(BuildContext context) { return new ListView.builder( itemCount: items.length, //设置水平方向 scrollDirection:Axis.horizontal, //竖直时:确定每一个item的高度 //水平时:确定每一个item的宽度 得要设置 不然不显示 itemExtent: 110.0, itemBuilder: (context, index) { return new ListTile( title: new Text('This is $index'),); }, ); 复制代码效果如下:
9.GridView
GridView是一个网格布局的列组件。GridView继承至CustomScrollView,示意图如下:
直接竖直上例子: //GridView class GridViewWidget extends StatelessWidget{ Widget build(BuildContext context){ return new GridView.count( crossAxisCount: 3, //3列 children: List.generate(40, (i){ return Card( child: Center( child:Text('This is $i'), ), ); }) ); } } 复制代码 下面上水平例子: return new GridView.count( //3行 crossAxisCount: 3, //设置水平 scrollDirection: Axis.horizontal,children: List.generate(40, (i) {return Card( child: Center( child: Text('This is $i'), ), ); }), ); 复制代码效果图如下:
10.TabBar
移动开发中tab切换是一个很常用的功能,那么Flutter有没有提供这个Widget呢?答案是有的,Flutter通过Material库提供了很方便的API来使用tab切换。
10.1.创建TabController
TabBarView和TabBar都有一个TabController的参数,TabbarView和TabBar就是由TabController来控制同步,点击某个Tab后,要同步显示对应的TabBarView,创建TabController有两种方式:
下面就列一下第一种方式:
Widget build(BuildContext context) {return new DefaultTabController();} 复制代码10.2.构建Tab数据
final List<Tab> myTabs = <Tab>[new Tab(text: 'Android'),new Tab(text: 'IOS'),new Tab(text: 'Flutter'),new Tab(text: 'RN'),new Tab(text: 'Java'),new Tab(text: 'C'),new Tab(text: 'C++'),new Tab(text: 'Go'),];复制代码10.3.创建TabBar
TabBar在哪里都可以创建,在AppBar里有一个bottom参数可以接受TabBar,就放在AppBar下:
//设置appbarappBar: new AppBar(//底部bottom: new TabBar(indicatorColor: Colors.red, //指示器颜色 如果和标题栏颜色一样会白色tabs: myTabs,//绑定数据isScrollable: true, //是否可以滑动),), 复制代码10.4.绑定TabBar和TabBarView
class MyHomePage extends StatelessWidget { final List<Tab> myTabs = <Tab>[new Tab(text: 'Android'),new Tab(text: 'IOS'),new Tab(text: 'Flutter'),new Tab(text: 'RN'),new Tab(text: 'Java'),new Tab(text: 'C'),new Tab(text: 'C++'),new Tab(text: 'Go'),];Widget build(BuildContext context) {return new DefaultTabController(length: myTabs.length, //Tab长度child: new Scaffold(//设置appbarappBar: new AppBar(//底部bottom: new TabBar(indicatorColor: Colors.red, //指示器颜色 如果和标题栏颜色一样会白色tabs: myTabs,//绑定数据isScrollable: true, //是否可以滑动),....),body: new TabBarView(//选中哪个Tabs,body就会显示children: myTabs.map((Tab tab) {return new Center(child: new Text(tab.text));}).toList(),),....);} } 复制代码效果如下图:
11.BottomNavigationBar
BottomNavigationBar即是底部导航栏控件,显示在页面底部的设计控件,用于在试图切换,底部导航栏包含多个标签、图标或者两者搭配的形式,简而言之提供了顶级视图之间的快速导航。
11.1.构建底部标签
//底部数据final Map bottomMap ={"首页":Icon(Icons.home),"朋友圈":Icon(Icons.camera),"信息":Icon(Icons.message),"其他":Icon(Icons.devices_other),}; 复制代码11.2.创建导航栏
因为点击导航栏需要对应的字体显示,所以MyHomePage需要继承StatefulWidget,增加State,
//用无状态控件显示 class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(//主题色theme: ThemeData(//设置为红色primarySwatch: Colors.red),//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面home: MyHomePageWidget(),);} }class MyHomePageWidget extends StatefulWidget{State<StatefulWidget> createState(){return new MyHomePage();} } class MyHomePage extends State<MyHomePageWidget> {//底部数据final Map bottomMap ={"首页":Icon(Icons.home),"朋友圈":Icon(Icons.camera),"信息":Icon(Icons.message),"其他":Icon(Icons.devices_other),};int _index = 0;bottomNavigationBar: BottomNavigationBar(items: (){var items = <BottomNavigationBarItem>[];bottomMap.forEach((k,v){items.add(BottomNavigationBarItem(title:Text(k),//取map的值icon : v,//取map的图标backgroundColor:Colors.red,//背景红色));});return items;}(),currentIndex: _index,//选中第几个onTap:(position){Fluttertoast.showToast(msg: 'text inputted: $position',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);setState(() {_index = position;//状态更新});}),} 复制代码最终效果如下:
五、实践
下面实践Flutter中文网的例子:
先上布局分析图:1.实现图像
再说一下如何配置图像
class MyApp extends StatelessWidget{Widget build(BuildContext context){return MaterialApp(home:new MyHomeWidget(),);} }class MyHomeWidget extends StatelessWidget{Widget build(BuildContext context){return new Scaffold(//设置标题栏appBar: new AppBar(title:new Text('Flutter Demo'),),//主体用ListViewbody:new ListView(children: <Widget>[//图片new Image.asset('images/lake.jpg',width:600.0,height:240.0,//顺便设置图片属性fit:BoxFit.cover,)],),);} } 复制代码2.实现标题栏
//实现标题栏Widget titleWidget = new Container(//内边距padding:const EdgeInsets.all(30.0),//整体是一个水平的布局child:new Row(//只有一个孩子children: <Widget>[//用Expanded 会占用icon之外剩余空间new Expanded(//垂直布局 放置两个文本child: new Column(//设置文本一起始端对齐crossAxisAlignment: CrossAxisAlignment.start,//有两个孩子children: <Widget>[new Container(//底部内边距padding:const EdgeInsets.only(bottom:10.0),//孩子 设置字体样式child:new Text('Oeschinen Lake Campground',style: new TextStyle(fontWeight: FontWeight.bold),),),new Text('Kandersteg, Switzerland',style: new TextStyle(color:Colors.grey[450],//设置颜色透明度),)],),),new Icon(Icons.star,color:Colors.red[400],),new Text('41'),],),); 复制代码3.实现按钮行
因为三个按钮样式都是一样的,所以抽取公共部分:
/*** 抽取button行的代码复用**/Column getText(IconData icon,String text){return new Column(//聚集widgetsmainAxisSize:MainAxisSize.min,//child居中mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new Icon(icon,color:Colors.blue[500]),new Container(//上部外边距margin: const EdgeInsets.only(top:8.0),//Text内容样式设定child:new Text(text,style:new TextStyle(color:Colors.blue[500],),),)],);}/*** 按钮实现*/Widget buttonWidget = new Container(//三列child:new Row(//用MainAxisAlignment.spaceEvenly平均分配子空间mainAxisAlignment: MainAxisAlignment.spaceEvenly,//孩子们children: <Widget>[getText(Icons.call, "CALL"),getText(Icons.near_me, "ROUTE"),getText(Icons.share, "SHARE"),],),); 复制代码4.实现文本
/*** 文本实现*/Widget textWidget = new Container(alignment: Alignment.center,//设置内边距padding:const EdgeInsets.all(10.0),child:new Text('Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, ''it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, ''followed by a half-hour walk through pastures and pine forest, ''leads you to the lake, which warms to 20 degrees Celsius in the summer. ''Activities enjoyed here include rowing, and riding the summer toboggan run.',// softWrap: true,//属性表示文本是否应在软换行符(例如句点或逗号)之间断开。// textAlign: TextAlign.center,),); 复制代码5.整合
return new Scaffold(//设置标题栏appBar: new AppBar(title:new Text('Flutter Demo'),),//主体用ListViewbody:new ListView(children: <Widget>[//图片new Image.asset('images/lake.jpg',width:600.0,height:240.0,//顺便设置图片属性fit:BoxFit.cover,),//标题栏titleWidget,//按钮栏buttonWidget,//文本栏textWidget,],),);复制代码运行效果图:
六、总结
Flutter还有很多Widget上面没有说到,就只能自己有空再去学习了,下面直接上一张图,今天学到的内容:
学习链接:flutterchina.club/widgets/
如有不正之处欢迎大家批评指正~
转载于:https://juejin.im/post/5c5c1f21e51d457fcc5a9f9f
总结
以上是生活随笔为你收集整理的Flutter学习之认知基础组件的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Room是怎样和LiveData结合使用
- 下一篇: 每日 30 秒 ⏱ 唯一的数据集