Flutter之常见控件
Flutter列表控件
在 Flutter 中,ListView 可以沿一个方向(垂直或水平方向)来排列其所有子 Widget,常被用于需要展示一组连续视图元素的场景。
ListView 构造方法:
- ListView:仅适用于列表中含有少量元素的场景
- ListView.build:适用于子 Widget 比较多的场景
- ListView.separated:适用于需要设置分割线的场景
构造方法名 特点 使用场景 ListView 一次性创建好所有子 Widget 适用于展示少量连续子 Widget 的场景。 ListView.build 提供了子 Widget 创建方法,仅在需要展示时才创建 适用于子 Widget 较多,且视觉效果呈现某种规律性的场景。 ListView.separated 提供了子 Widget 创建方法,仅在需要展示时才创建,且提供了自定义分割线的功能 适用于子 Widget 较多,且视觉效果呈现某种规律性、每个子 Widget 之间需要分割线的场景。
1.ListView
可以通过设置 children 参数,将所有子 Widget 包含到 listView 中,但这种创建方法要求提前将所有子 Widget 一次性创建好,而不是等到真正需要在屏幕上显示时才创建,即这种方法是导致性能下降 。因此,这种方式只适合列表中含有少量元素的场景:
1 | class ListPage extends StatelessWidget { |
2.ListView.builder
- itemBuilder:列表项的创建方法。当列表滚动到相应位置时,ListView 会调用该方法创建对应的子 Widget
- itemCount:列表项的数目。如果不设置或设置为空,则表示 ListView 为无限列表
- itemExtent:列表项高度。可选参数,但对于定高的列表项元素,建议设置该参数的值(不设置时,ListView 会动态的根据子 Widget 创建完成后的结果,决定自身的视图高度,以及子 Widget 在 ListView 中的相对位置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class ListBuild extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemBuilder: (context, index) => ListTile(
leading: Icon(Icons.adb),
title: Text("下标" + index.toString()),
),
itemExtent: 46, // 列表项高度
itemCount: 50, //列表项总数,不设置为无限加载
),
);
}
}
3.ListView.separatorBuilder
设置列表项之间的分隔线,可以根据下标设置不同的分隔线
1 | class ListSeparated extends StatelessWidget { |
Flutter设置margin和padding
方式一使用Container包裹
用Container包裹起来然后在Container上面设置:
1 | padding: EdgeInsets.fromLTRB(10, 10, 15, 20),//内边距,里边的蓝块,需要给宽高 |
方式二使用Padding和SizedBox 组件
Flutter开发,万物皆Widget,对于内边距,我们一般是在目标控件包裹一层父级控件Padding,并通过Padding控件的padding属性指定内边距数值,例如:
统一指定上下左右的内边距
1 | Padding( |
指定左侧内边距
1 | Padding( |
指定上下左右内边距
1 | Padding( |
对于外边距,Flutter没有类似Padding的控件,但是很多时候我们又想要实现控件间的间距效果,这个时候SizedBox控件就派上用场了,它是一种类似于Android平台的space占位view,只占空间不显示,使用方法也很简单:
设置上下外边距
1 | SizedBox( |
设置左右外边距
1 | SizedBox( |
2.使用Spacer填充尽可能大的空间
1 | Row( |
FlutterFlex弹性布局
弹性系数
如果为 0 或 null,则 child 是没有弹性的,即不会被扩伸占用的空间。
如果大于 0,所有的Expanded按照其flex的比例来分割主轴的全部空闲空间。
Expanded占据剩余的空间
Expanded使用与类似与Column,Row,Flex等展示多个组件集合的组件,Expanded包含的组件可以占据剩余的空间。
Expanded组件可以使Row、Column、Flex等子组件在其主轴方向上展开并填充可用空间(例如,Row在水平方向,Column在垂直方向)。如果多个子组件展开,可用空间会被其flex factor(表示扩展的速度、比例)分割。
Expanded组件必须用在Row、Column、Flex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸了,因此Expanded不能放进RenderObjectWidget)。
注意一点:在Row中使用Expanded的时候,无法指定Expanded中的子组件的宽度width,但可以指定其高度height。同理,在Column中使用Expanded的时候,无法指定Expanded中的子组件的高度height,可以指定宽度width。
Flutter给组件设置点击事件
- 在flutter 开发中用InkWell或者GestureDetector将某个组件包起来,可添加点击事件。
- GestureDetector 使用点击无水波纹出现,InkWell可以实现水波纹效果。
1. InkWell有点击效果
1 | InkWell( |
2. GestureDetector 设置点击
1 | GestureDetector( |
Flutter快捷键生成控件
- 输入stless就可以创建一个StatelessWidget。
- 输入stful就可以创建一个StatefulWidget。
Flutter圆角控件
1.通过Card的shape属性
1 | Card( |
唯一值的注意的地方就是borderRadius看准了,不要用错了,要不然没效果。
2.通过Container的decoration
1 | Container( |
使用的DecorationImage,相当于把图片当做一个背景,这里需要注意的就是Container的child的尺寸问题,就算不放内容,也需要设置一个带尺寸的child Widget。
3.直接使用ClipRRect
1 | ClipRRect( |
这种方式是最简单的,直接使用即可。
装饰容器DecoratedBox
DecoratedBox可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。DecoratedBox定义如下:
1 | /** |
- decoration:代表将要绘制的装饰,它的类型为Decoration。Decoration是一个抽象类,它定义了一个接口 createBoxPainter(),子类的主要职责是需要通过实现它来创建一个画笔,该画笔用于绘制装饰。
- position:此属性决定在哪里绘制Decoration,它接收DecorationPosition的枚举类型,该枚举类有两个值:
- background:在子组件之后绘制,即背景装饰。
- foreground:在子组件之上绘制,即前景。
BoxDecoration
我们通常会直接使用BoxDecoration类,它是一个Decoration的子类,实现了常用的装饰元素的绘制。
定义:1
2
3
4
5
6
7
8
9
10
11
12/** 装饰器,可以用来修饰其他的组件,和Android里面的shape很相似
const BoxDecoration({
this.color,//背景色
this.image,//图片
this.border,//描边
this.borderRadius,//圆角大小
this.boxShadow,//阴影
this.gradient,//过度效果
this.backgroundBlendMode,//背景混合模式
this.shape = BoxShape.rectangle,//形状,BoxShape.circle和borderRadius不能同时使用
})
*/
Flutter Container组件宽度撑满屏幕
在flutter开发中,如果不给Container组件设置宽度的话,它的宽度是取决于子组件的宽度,如何给Container设置撑满屏幕的宽度呢?
以下两种方式都可:
1 | Container( |
1 | Container( |
Flutter中的颜色
1.常规使用
Flutter中颜色的设置有很多方法,但是一般我使用的有4种.
1 | Color c1 = Color(0xFF3CAAFA); |
Color(int value)
Color(0xFF3CAAFA),value接收的是一个十六进制(0x开头),FF表示的是十六进制透明度(00-FF),3CAAFA是十六进制色值。Color.fromRGBO(int r, int g, int b, double opacity)
Color.fromRGBO(60, 170, 250, 1),r、g、b分别表示red、green、blue,常规的红绿蓝三色,取值范围为0-255,opacity表示透明度,取值0.0-1.0。Color.fromARGB(int a, int r, int g, int b)
Color.fromARGB(255, 60, 170, 250),a表示透明度,取值0-255,rgb同上一样。Colors._()
Colors类定义了很多颜色,可以直接使用,例如 Colors.blue,其实就是第一种Color(int value)的封装。
2.在Flutter中使用16进制颜色
(1.)方法一: 使用原生方法
Flutter中, Color类仅接收整数作为参数. 你也可以使用fromARGB或者fromRGBO.
比如拿到了一个16进制颜色#b74093. 因为Color还需要传入透明度, 255就是最大值(也就是不透明), 转为16进制就是0xFF, 所以我们只需这样表示:
1 | const color = Color(0xffb74093); |
正规一点的写法(可选, 因为大小写不敏感):
1 | const color = Color(0xFFB74093); |
(2.)方法二: 接收字符串格式, 转为Color
创建一个HexColor类:
1 | class HexColor extends Color { |
然后进行调用:
1 | Color color1 = HexColor("b74093"); |
3.封装Color使用
1 | class Colours { |
调用:
1 | theme: ThemeData( |
Flutter根据状态显示隐藏widget
1.占位方案
1 | buildTestWidget() { |
2.Visibility控件
1 | Visibility( |
3.Offstage控件
offstage为true时表示不渲染,也不占位,相当于gone。
1 | Offstage( |
4.可以通过if条件控制控件的显示或隐藏
用 Row 或者 Column 控件,控件里面有一个包含Widget的list,所以可以根据条件把需要展示的 Widget 放入 list 中,然后再使用 Row 或 Column 控件来展示 list,达到控制显示还是不显示的目的。
1 | Column( |
5.使用Opacity控件
opacity 其实是根据visible 控制透明度而已,其实还是占位的,相当于invisible,而且也是会渲染绘制的。
1 | Opacity( |
Flutter中的输入框
TextField输入框
1.获取输入内容
获取输入内容有两种方式:
- 定义两个变量,用于保存用户名和密码,然后在onChange触发时,各自保存一下输入内容。
- 通过controller直接获取。
2.监听文本变化
监听文本变化也有两种方式:
- 设置onChange回调
1
2
3
4
5
6TextField(
autofocus: true,
onChanged: (v) {
print("onChange: $v");
}
) - 通过controller监听
1
2
3
4
5
6
7@override
void initState() {
//监听输入改变
_unameController.addListener((){
print(_unameController.text);
});
}onChanged是专门用于监听文本变化,而controller的功能却多一些,除了能监听文本变化外,它还可以设置默认值、选择文本
Flutter中的常见错误
1. Navigator operation requested with a context that does not include a Navigator.
1 | class MyApp extends StatelessWidget { |
当我们在 build 函数中使用Navigator.of(context)的时候,这个context实际上是通过 MyApp 这个widget创建出来的Element对象,而of方法向上寻找祖先节点的时候(MyApp的祖先节点)并不存在MaterialApp,也就没有它所提供的Navigator。
所以当我们把Scaffold部分拆成另外一个widget的时候,我们在FirstPage的build函数中,获得了FirstPage的BuildContext,然后向上寻找发现了MaterialApp,并找到它提供的Navigator,于是就可以愉快进行页面跳转了。
Flutter的TabBar组件(顶部Tab切换组件)
1.TabBar组件的常用属性
属性 | 描述 |
---|---|
tabs | 显示的标签内容,一般使用 Tab 对象,也可以是其他的Widget |
controller | TabController 对象 |
isScrollable | 是否可滚动 |
indicatorColor | 指示器颜色 |
indicatorWeight | 指示器高度 |
indicatorPadding | 底部指示器的 Padding |
indicator | 指示器 decoration,例如边框等 |
indicatorSize | 指示器大小计算方式,TabBarIndicatorSize.label 跟文字等宽,TabBarIndicatorSize.tab 跟每个 tab 等宽 |
labelColor | 选中 label 颜色 |
labelStyle | 选中 label 的 Style |
labelPadding | 每个 label 的 padding 值 |
unselectedLabelColor | 未选中 label 颜色 |
unselectedLabelStyle | 未选中 label 的 Style |
2.TabBar的实现方式1(不常用)
1 | import 'package:flutter/material.dart'; |
3.TabBar的实现方式2(常用)
1 | import 'package:flutter/material.dart'; |
flutter TabBarView 没有跟Scaffold 一起使用的时候,容易报 Horizontal viewport was given unbounded height 错误,例如将其作为Column的子元素,就会出现该错误。错误提示意思是水平视图高是无限的,这里由于是用在Column中, 所以水平应该理解为垂直方向。解决该问题就是需要在其父级添加高度限制。
例如在其外层包裹Expanded,并设置flex。如下:
1 | Widget build(BuildContext context) { |
写动态tabbar将 singleTickerProviderStateMixin 改成 TickerProviderStateMixin 才能调用setState重绘
1 | class _TravelPageState extends State<TravelPage> with TickerProviderStateMixin{ |
Flutter中的状态栏
通过SafeArea包裹内容来防止布局的内容填充到状态栏里面。
Flutter的嵌套层级深
解决方法
解决Flutter Widget地狱的方法有很多种,根据我的开发经验,着重介绍以下几种方法。
- 将组件转化为方法,这一种方式非常常用。
- 将组件转化为 StatelessWidget 或者 StatefulWidget ,我们习惯只把重复用到的组件做封装,实际上这样写更好,这个我会在后面提到。
- 第三种灵感来自于掘金的一篇文章《Flutter嵌套深?扩展函数了解一下》,有兴趣的朋友可以看一下。
Flutter 自定义 Widget 的方式
当我们在实际开发中,可能 Flutter 的基础 Widget 组件并不能满足我们的需求,这时我们就需要自定义 Widget 来实现我们的需求。
Flutter 有多种实现自定义 Widget 的方式:
- 通过继承 Widget 来修改和扩展它的功能;
- 通过组合 Widget 来扩展功能;
使用 CustomPaint 绘制自定义 Widget。这几种方式都有各自的优势和特点,相对来说 CustomPaint 绘制实现自定义是这里面比较复杂的一种自定义 Widget 方式。
Flutter 中的很多基础 Widget 也是通过继承 Widget 进行扩展形成新的 Widget 或者是自己绘制 Widget。其实在大部分的平台都存在 Canvas 这个对象,它可以实现绘制布局、组件等功能,
当然 Flutter 也可以通过 Canvas 来实现 Widget 的绘制。自定义Widget 在开发中也非常常见,例如:我们可以自定义封装实现一个加载中的对话框、实现一个通用的 ToolBar 等等。
『Flutter』组件通信传值学习
Flutter | 深入理解BuildContext
Dio官方文档
Flutter基础(十一)网络请求(Dio)与JSON数据解析
Flutter如何高效的JSON转Model
Flutter动态加载TabBar
善用 Provider 榨干 Flutter 最后一点性能
实操flutter避免嵌套地狱的5种方法