前一天,学习了Dart
语法,对Dart
的语法和特性有了更深一步的了解。今天,来学习Flutter
的基础控件,身为Android
开发者都知道,一开始入坑Android
就要熟悉学习其控件,如:TextView
,ImageView
,Button
,ListView
,RecycleView
等。为何要学习呢?由于平时的开发都离不开这些控件,UI
的呈现都是有这些控件组成的,所以,其重要性就不用说了。对于Flutter
来说,基础控件(widget
)就更加剧要了。Flutter
和Android
有所不同,Android
布局包含布局(RelativeLayout,LinearLayout,ConstrainLayou)和组件。Flutter
的一切都是Widget
,包括最顶层布局也是Widget
,一个页面有不少不少的Widget
组合而成,Widget
也称为装饰品,窗口小部件。java
在Flutter
里,UI
控件就是Widget
,Widget
根据不一样的功能能够分为结构元素(如按钮或菜单),文本样式(字体或者颜色方案),布局属性(如填充,对齐,居中),能够这么理解,一个flutter
的页面是有一棵树型的Widget
组成,包括根节点,树枝和树叶,全都是Widget
,只是Widget
嵌套Widget
,那就能够用下面这张图来表示:git
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
。
源码StatelessWidget
只有三个方法:github
Widget
、Element
、SemanticsNode
的惟一标识符,是用来控制Widget
数中替换Widget
的时候使用的。Widget
树中的位置。StatelessWidget
,当Widget
第一次插入到树中,或者父节点更改了配置和所依赖的[InheritedWidget]改变,都会被从新调用。这里说下如何启动一个Flutter
应用,并使用Flutter
框架:缓存
import 'package:flutter/material.dart';
void main() {
return runApp(Widget app);
}
复制代码
其实就是在main()
函数中调用runApp
函数。下面直接直接上例子,继承StatelessWidget
,经过build
方法返回一个控件:bash
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方法 返回你须要的控件
@override
Widget build(BuildContext context) {
// TODO: implement build
return 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
。网络
什么是有状态的控件呢?状态是在建立控件能够同步读取信息,而且在控件的生命周期内能够改变,当控件状态发生改变时使用State.setState
来及时更新,源码也是只有三个方法:app
StatelessWidget
同样的,而
createState()
这个方法源码注释是:在
Widget
树中给定的位置建立此可变状态的小部件,子类应该重写此方法返回新建的,关联子类的实例。当调用一个
StatefulWidget
,框架就会调用
createState
这个方法,当一个
StatefulWidget
从
Widget
树中移除,再次插入树中,那么会再次调用
createState
来建立一个新的
State
对象,这样作简化了
State
对象的生命周期。 须要建立管理的是主要是
State
,
StatefulWidget
用起来麻烦一些,他须要一个
State
,例子以下:
//继承StatefulWidget
class StateWidget extends StatefulWidget{
@override
State createState(){
return _StateWidget();
}
}
class _StateWidget extends State<StateWidget>{
//重写build方法
@override
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);
@override
void initState(){
//初始化,这个函数在控件的生命周期内调用一次
super.initState();
print("进入initState");
//3秒后改变text的内容
new Future.delayed(const Duration(seconds: 3),(){
setState(() {
Number++;
text = "已经改变数值,数值如今是$Number";
});
});
}
@override
void dispose(){
//销毁
super.dispose();
print('销毁');
}
@override
void didChangeDependencies(){
//在initState以后调
super.didChangeDependencies();
print('进入didChange');
}
//重写build方法
@override
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
从新构建,上面代码还写了三个生命周期方法,这里简单说一下:less
initState
以后调用,能够获取其余State
平时开发中在build
实现布局的摆放,把数据添加Widget
,经过setState
改变数据。那若是很高频率取改变数据,性能确定受影响,如下三点能够减小从新构建有状态控件的影响:async
build
方法所建立的节点数量和控件数量。const
修饰控件。 怎么去选择有状态和无状态,最简单就是能够跟用户进行交互应该使用StatefulWidget
,例如:点击,滑动屏幕信息流数据更新,若是只是仅仅显示数据,那就能够选择使用StatelessWidget
建立一个无状态控件。Flutter有显示的Widget
和完整页面呈现的Widget
,常见的有MaterialApp
、Scaffold
、Appbar
、Text
、Image
、FlatButton
,下面以表格形式简单列一下:
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial风格的小控件
void main(){
//运行程序
runApp(MyApp());
}
//用无状态控件显示
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
//标题
title:'Widget_Demo',
//主题色
theme:ThemeData(
//设置为蓝色
primarySwatch: Colors.blue
),
//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面
home:MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Scaffold(
//设置appbar
appBar:new AppBar(
title:new Text('This is a Demo'),
),
//主体
body:new Center(
//在屏幕中央显示一个文本
child:new Text('Hello'),
),
);
}
}
复制代码
效果以下图:
MaterialApp
做为了主界面入口。
上面例子home:MyHomePage()
这里返回了Scaffold
Widget,而这个Widget
正是咱们所看到的页面,看到Scaffold
包含了appBar
和body
,一开始说到,Scaffold
也包含Drawers
,下面实现一下:
@override
Widget build(BuildContext context){
return Scaffold(
//设置appbar
appBar:new AppBar(
title:new Text('This is a Demo'),
),
//主体
body:new Center(
//在屏幕中央显示一个文本
child:new Text('Hello'),
),
//左侧抽屉
drawer:Drawer(
//添加一个空的ListView
child:ListView(),
),
);
}
复制代码
效果以下:
ListView
,代码以下:
//左侧抽屉
drawer:Drawer(
child:ListView(
//设置padding
padding: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
。
下面设置一些AppBar
属性,玩玩:
//设置appbar
appBar: 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>[
//IconButton
new 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
上加了前置图标、
拍照图标
、菜单弹出按钮、阴影。
下面用Text
来展现文本,把上面例子用文本显示中间的Hello
单独抽出来,以下:
//主体
body: new Center(
//在屏幕中央显示一个文本 改成自定义样式
child: new CustomTextStyle('This is a Text'),
),
//单独文本样式
class CustomTextStyle extends StatelessWidget{
String text;
//构造函数 参数外部传进来
CustomTextStyle(this.text);
@override
Widget build(BuildContext context){
return Text(text ?? "Hello");
}
}
复制代码
下面把文本字体大小修改,字体样式修改,背景颜色改改:
//文本 : 单独文本样式
class CustomTextStyle extends StatelessWidget {
Paint pg = Paint();
String text;
//构造函数 参数外部传进来
CustomTextStyle(this.text);
@override
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, }) 复制代码
这是显示丰富样式的文本,这什么意思呢?Text
只能显示一种样式的文字,若是想在一段文字中显示多种样式,就好像Android
里面的SpannableString
,就须要使用RichText
,直接上例子:
//富文本样式
class RichWidget extends StatelessWidget {
@override
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()
),
复制代码
效果以下:
下面看看文本输入框,文本输入框平时会常常用到:
body: new Center(
//Text在屏幕中央显示一个文本 改成自定义样式
//child: new CustomTextStyle('This is a Text'),
//富文本
//child:new RichWidget()
//文本输入框
child:new TextFieldWidget()
),
//文本输入框
class TextFieldWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return TextField();
}
}
复制代码
上面例子只能输入文本内容,若是想要获取输入框内容,就要添加一个controller
,经过这个controller
添加通知来获取TextField
的值,咱们通常点击按钮或者须要跟后台交互就要读取controller.text
的值:
class MyHomePage extends StatelessWidget {
//获取TextEditingController
final editController = TextEditingController();
//IconButton
new IconButton(
//图标
icon: new Icon(Icons.add_a_photo),
//提示
tooltip: 'Add photo',
//点击事件
onPressed: () {
//输出
print('text inputted: ${editController.text}');
//Toast
Fluttertoast.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()
//文本输入框 以构造函数传递controller
child:new TextFieldWidget(editController)
),
}
//文本输入框
class TextFieldWidget extends StatelessWidget{
final controller;
//构造函数传值
TextFieldWidget(this.controller);
@override
Widget build(BuildContext context){
return TextField(
controller: controller,
);
}
}
复制代码
注意上面用到了Toast
,Toast库这里很简单须要两步:
pubspec.yaml
添加依赖库fluttertoast: ^2.1.1
import 'package:fluttertoast/fluttertoast.dart';
从新运行便可,热重载可能会出现异常。运行在iOS模拟器须要装brew
和CocoaPods
,有问题运行flutter doctor
,它真是如名字同样,就是帮你诊断有没有错误信息,会显示具体信息。效果以下:
return TextField(
controller: controller,
//最大长度,右下角会显示一个输入数量的字符串
maxLength: 26,
//最大行数
maxLines: 1,
//是否自动更正
autocorrect: true,
//是否自动对焦
autofocus: true,
//设置密码 true:是密码 false:不是秘密
obscureText: true,
//文本对齐样式
textAlign: TextAlign.center,
);
复制代码
效果以下:
Image
很好理解就是在界面上区域显示一张图片,而这张图片的来源能够是:本地,网络,资源图片等。下面一一演示一下:
首先新建一个资源目录:
pubspec.yaml
中配置图片路径,来识别应用程序所需的assets:
class MyHomePage extends StatelessWidget {
//主体
body: new Center(
.....
//图片加载
child:new ImageWidget()
),
}
//图片
class ImageWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
//项目资源图片 方式一
return Image(
image: new AssetImage('images/Image_fluttericon.jpeg'),
);
//项目资源图片 方式二
// return Image.asset('images/Image_fluttericon.jpeg');
}
}
复制代码
效果以下:
下面进行网络图片加载,也是很简单:
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);
@override
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);
@override
Widget build(BuildContext context){
return new CachedNetworkImage(
imageUrl: image_url,
//占位符
placeholder: new CircularProgressIndicator(),
//加载错误时显示的图片
errorWidget: new Icon(Icons.error),
//宽高
width:200,
height: 200,
);
}
}
复制代码
当图片还没加载出来的时候会显示占位符,当若是加载出错会显示errorWidget
的图片。
另外Flutter能够为当前设备添加合适其分辨率的图像,其实对于Android原生来讲,就是在不一样分辨率目录下放置不一样分辨率的图片,只不过flutter并非建立drawable-xxdpi
文件,而是建立如下文件夹:
.../logo.png
.../Mx/logo.png
.../Nx/logo.png
复制代码
其中M和N是数字标识符,对应于其中包含的图像分辨率,它们指定不一样素设备像比例的图片,主资源默认对应于1.0倍的分辨率图片。看下面例子:
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'),
// ...
),
复制代码
Flutter
预先定义了一些按钮控件,如FlatButton
,RaisedButton
,OutlineButton
,IconButton
。
MaterialButton
MaterialButton
MaterialButton
StatelessWidget
下面看看FlatButton
,其余的只是样式稍微不同,大体用法同样。
//按钮
class FlatButtonWidget extends StatelessWidget{
@override
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
中拥有30多种预约义的布局widget
,经常使用的有Container
、Padding
、Center
、Flex
、Row
、Colum
、ListView
、GridView
。用一个表格列出它们的特性和使用。
一个拥有绘制、定位、调整大小的widget
,示意图以下:
class MyHomePage extends StatelessWidget {
....
body:new ContainWidget(),
...
}
//Container布局
class ContainWidget extends StatelessWidget{
@override
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{
@override
Widget build(BuildContext context){
return Container(
....
padding:EdgeInsets.all(30),//内边距
decoration: BoxDecoration(//设置边框
//背景色
color:Colors.redAccent,
//圆角
borderRadius: BorderRadius.circular(6),
),
);
}
}
复制代码
运行效果以下:
一个Widget
,会给其子Widget
添加指定的填充,示意图以下:
class MyHomePage extends StatelessWidget {
....
body: new PaddingWidget(),
...
}
//Padding布局
class PaddingWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Padding(
//设置左上右下内边距为4,10,6,8
padding:EdgeInsets.fromLTRB(4, 10, 6, 8),
child: Text('My name is Knight'),
);
}
}
复制代码
效果图以下:
Container
嵌套
Padding
:
//Container嵌套Padding
class ContainPaddWidget extends StatelessWidget{
@override
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),
),
);
}
}
复制代码
效果图以下:
将其子widget
居中显示在自身内部的widget
,示意图:
//Center
class CenterWidget extends StatelessWidget{
@override
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
因此在布局的中间。
能够容许其子Widget
简单的堆叠在一块儿,层叠布局,示意图:
class MyHomePage extends StatelessWidget {
....
body:new Center(
child:new StackWidget()
),
...
}
//层叠布局
class StackWidget extends StatelessWidget{
@override
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{
@override
Widget build(BuildContext context){
return Stack(
//Aliginment的范围是[-1,1],中心是[0,0].注释有写
//和Android同样,左移的取值是往1取,右移是往-1取
//这里注意,它是取stack里范围最大的布局为基准,下面是以Container为//基准对齐
alignment: new Alignment(-0.6, -0.6),
...
);
}
}
复制代码
运行效果图:
在垂直方向上排列子Widget
,示意图以下:
class MyHomePage extends StatelessWidget {
...
body:new ColumnWidget(),
....
}
//Column布局
class ColumnWidget extends StatelessWidget {
@override
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,
...
);
复制代码
运行效果以下:
widget
的数量,而后把其中一份空间分红2份,放在第一个child的前面,和最后一个child的后面,也就是子widget
的以前以后之间均匀分割空闲的一半空间widget
以前以后之间均匀的分割空闲的空间下面列一下水平方向(交叉轴)的属性:
child
填充满布局textBaseline
一块儿使用在水平方向上排列子widget
的列表,示意图:
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
//Row
class RowWidget extends StatelessWidget{
@override
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,
...
);
复制代码
效果图:
widget
的数量,而后把其中一份空间分红2份,放在第一个child的前面,和最后一个child的后面,也就是子widget
的以前以后之间均匀分割空闲的一半空间widget
以前以后之间均匀的分割空闲的空间 而交叉轴(垂直方向)的属性:child
填充满布局textBaseline
一块儿使用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{
@override
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{
@override
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
,将一行的宽度分红四等分,第1、三child
占1/4的区域,第二个child
占1/2区域。 效果以下:
我相信这个布局在平时开发会常常用到,这是可滚动的列表控件,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);
@override
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'),
);
},
);
复制代码
效果以下:
GridView
是一个网格布局的列组件。GridView
继承至CustomScrollView
,示意图以下:
//GridView
class GridViewWidget extends StatelessWidget{
@override
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'),
),
);
}),
);
复制代码
效果图以下:
移动开发中tab
切换是一个很经常使用的功能,那么Flutter
有没有提供这个Widget
呢?答案是有的,Flutter
经过Material
库提供了很方便的API来使用tab
切换。
TabBarView
和TabBar
都有一个TabController
的参数,TabbarView
和TabBar
就是由TabController
来控制同步,点击某个Tab
后,要同步显示对应的TabBarView
,建立TabController
有两种方式:
DefaultTabController
,在Scaffold
套一层DefaultTabController
,这种方式TabBarView
会自动查找这个tabController
。TabController
,实现SingleTickerProviderStateMixin
下面就列一下第一种方式:
@override
Widget build(BuildContext context) {
return new DefaultTabController();
}
复制代码
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'),
];
复制代码
TabBar
在哪里均可以建立,在AppBar
里有一个bottom
参数能够接受TabBar
,就放在AppBar
下:
//设置appbar
appBar: new AppBar(
//底部
bottom: new TabBar(
indicatorColor: Colors.red, //指示器颜色 若是和标题栏颜色同样会白色
tabs: myTabs,//绑定数据
isScrollable: true, //是否能够滑动
),
),
复制代码
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'),
];
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: myTabs.length, //Tab长度
child: new Scaffold(
//设置appbar
appBar: 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(),
),
....
);
}
}
复制代码
效果以下图:
BottomNavigationBar
便是底部导航栏控件,显示在页面底部的设计控件,用于在试图切换,底部导航栏包含多个标签、图标或者二者搭配的形式,简而言之提供了顶级视图之间的快速导航。
//底部数据
final Map bottomMap ={
"首页":Icon(Icons.home),
"朋友圈":Icon(Icons.camera),
"信息":Icon(Icons.message),
"其余":Icon(Icons.devices_other),
};
复制代码
由于点击导航栏须要对应的字体显示,因此MyHomePage
须要继承StatefulWidget
,增长State
,
//用无状态控件显示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//主题色
theme: ThemeData(
//设置为红色
primarySwatch: Colors.red),
//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面
home: MyHomePageWidget(),
);
}
}
class MyHomePageWidget extends StatefulWidget{
@override
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
中文网的例子:
再说一下如何配置图像
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
home:new MyHomeWidget(),
);
}
}
class MyHomeWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Scaffold(
//设置标题栏
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主体用ListView
body:new ListView(
children: <Widget>[
//图片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//顺便设置图片属性
fit:BoxFit.cover,
)
],
),
);
}
}
复制代码
//实现标题栏
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'),
],
),
);
复制代码
由于三个按钮样式都是同样的,因此抽取公共部分:
/** * 抽取button行的代码复用 * */
Column getText(IconData icon,String text){
return new Column(
//汇集widgets
mainAxisSize: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"),
],
),
);
复制代码
/** * 文本实现 */
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,
),
);
复制代码
return new Scaffold(
//设置标题栏
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主体用ListView
body: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/
若有不正之处欢迎你们批评指正~