文本
Flutter中使用Text来实现一般的文本,构建一个Text方法如下:
//构造实例
Text(......)
new Text(......)
属性
属性 | 作用 |
---|---|
style | 文本样式,返回TextStyle,可以设置字体颜色、下划线、字体大小等(最常用的玩意儿,基本上都得用) |
textAlign | 文本对齐方式 |
softWrap | 是否自动换行,值为boolean型 |
overflow | 当文本内容超过最大行数时,剩余内容的显示方式当文本内容超过最大行数时,剩余内容的显示方式 |
textDirection | 文本方向,即文字从那边开始显示(布局里这属性有些用,这里貌似基本上没啥用) |
locale | 此属性很少设置,用于选择区域特定字形的语言环境(基本上没啥用) |
semanticsLabel | 图像的语义描述,用于向Andoid上的TalkBack和iOS上的VoiceOver提供图像描述(可忽略,暂时没啥用) |
[站外图片上传中...(image-6886f2-1562061232581)]
TextStyle
TextStyle可以设置文字的属性,其中比较简单的包括Color(文本颜色)、fontSize(文字大小),如果不设置,文本将使用最接近的DefaultTextStyle的样式。如果给定样式的TextStyle.inherit属性为true(默认值),则给定样式将与最接近的DefaultTextStyle合并。以下是属性:
属性 | 作用 |
---|---|
color | 文本颜色。与foreground互斥 |
decoration | 绘制文本装饰(下划线、上划线、删除线) |
decorationColor | Decoration的颜色,和decoration配合使用,单独使用没效果 |
decorationStyle | 绘制文本装饰的样式 |
fontWeight | 绘制文本时使用的字体粗细(默认w400),文字加粗效果用这个 |
fontStyle | 字体变体(斜体、正常) |
fontFamily | 设置字体,外面导入的稀奇古怪的字体 |
fontSize | 字体大小 |
letterSpacing | 水平字母之间的空间间隔 |
wordSpacing | 水平单词之间的空间间隔 |
height | 文本行与行的高度(非控件高度,注意了) |
locale | 区域特定字形 |
background | 文本背景色 |
foreground | 文本的前景色,和color互斥,需要返回paint(画笔) |
这里做了一个简单的实现
Widget _ZongheText() {
return Text(
"一片喧哗叫嚷之中,忽听得山下一个雄壮的声音说道:谁说星宿派武功胜得了丐帮的降龙十八掌?这声音也不如此响亮,但清清楚楚的传入了从人耳中,众人一愕之间,都住了口。hello world hheh aaaayy",
style: TextStyle(
inherit: false,
color: Colors.pinkAccent,
fontWeight: FontWeight.w600,
fontSize: 16,
letterSpacing: 2.0,
wordSpacing: 5.0,
//只适用于英文
fontStyle: FontStyle.italic,
textBaseline: TextBaseline.alphabetic,
height: 3,
decorationStyle: TextDecorationStyle.dashed,
decoration: TextDecoration.underline,
fontFamily: "systemmessage"),
);
}
效果如下:
[站外图片上传中...(image-1dc262-1562061232581)]
RichText
如果需要对一段文本的不同段落采用不同的文字样式,可以使用该控件。具体使用方法如下:
//RichText构造实例
new RichText(......)
Text.rich(......)
//测试demo
Widget _RichTestWidget(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(0, 10, 5, 0),
padding: EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Text.rich(TextSpan(
text: "flutter中RichText用于处理一段文字可能有不同的风格,如:",
children: [
TextSpan(text: "红色,", style: TextStyle(color: Colors.red)),
TextSpan(text: "黄色,", style: TextStyle(color: Colors.yellow)),
TextSpan(text: "蓝色,", style: TextStyle(color: Colors.blue)),
TextSpan(
text: "点我试试",
recognizer: TapGestureRecognizer()
..onTap = () {
var snackBar = SnackBar(
content: Text("hello world"),
duration: Duration(seconds: 3), // 持续时间
);
_scaffoldkey.currentState.showSnackBar(snackBar);
}),
],
style: TextStyle(color: Color(0xff000000)))));
}
效果如下:
[站外图片上传中...(image-9b1769-1562061232581)]
PS
- Text没有宽度、高度以及padding设置,如果需要设置这些属性,则需要在外面包裹一层Container,设置Container的这些属性
- 不同于Android的TextView,Text没有点击触发监听,如果需要可点击,则需要设置手势(感觉比较麻烦,不推荐)或者使用FlatButton(相当于一个可点击的Text)
- color和foreground只能存在一个,否则报错
- RichText中如果内部的TextSpan设置了TextStyle,则外部的TextStyle无效。
按钮
在 Flutter 里有很多的 Button,包括了:MaterialButton、RaisedButton、FloatingActionButton、FlatButton、IconButton、ButtonBar、DropdownButton 等。
一般常用的 Button 是 MaterialButton、IconButton、FloatingActionButton。
一般按钮(MaterialButton、RaisedButton、OutlineButton、FlatButton)
RaisedButton、OutlineButton、FlatButton都是一般的按钮,这三个按钮均是继承于MaterialButton,它们的区别在于:
- RaisedButton:一般的按钮,类似android的button,有凸起
- FlatButton:扁平式按钮,相当于有点击事件的text
- OutlineButton:带边框的背景透明的按钮
构造方法如下:
//构造MaterialButton,参数里面如果没有onPressed,会报警告,不影响编译,默认为button被禁用,
//这里onPressed回调里面,()表示回调参数,这里没有表示未传参,{}里面编写回调后的操作代码
MaterialButton( onPressed: () {})
由于这三个button均是继承于MaterialButton,MaterialButton具有的属性这三个button也都具有,只是MaterialButton的部分属性它们不具有,这里MaterialButton的具体属性如下,常用属性加粗显示:
属性 | 作用 | |
---|---|---|
onPressed | 点击按钮的回调,设置为null的话,点击效果失效 | |
textColor | 文字颜色 | |
disabledTextColor | 按钮被禁用时的文字颜色 | |
color | 按钮的背景色 | 均有 |
disabledColor | 按钮禁用时的背景颜色 | |
disabledTextColor | 按钮禁用时的文字颜色 | |
splashColor | 点击按钮时水波纹的颜色 | |
shape | 设置按钮的形状 | |
padding | 内边距 | |
elevation | 阴影高度 | |
highlightElevation | 按钮高亮(按下)时的阴影高度 | |
disabledElevation | 按钮禁用时的阴影高度 | |
minWidth | 最小宽度 | |
height | 按钮高度 |
左边FlatButton,右边MaterialButton
[站外图片上传中...(image-49792b-1562061232581)]
return Row(children: [
Container(
height: 24,
child: FlatButton(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
child: Text(
this.text,
style: TextStyle(color: _color(), fontSize: 12),
),
onPressed: () {
setState(() {
if (this.choose) {
this.choose = false;
} else {
this.choose = true;
}
});
},
shape: _shapeBorder(),
)),
MaterialButton(
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
minWidth: 0,
height: 24,
child: Text(
this.text,
style: TextStyle(color: _color(), fontSize: 12),
),
onPressed: () {
setState(() {
if (this.choose) {
this.choose = false;
} else {
this.choose = true;
}
});
},
shape: _shapeBorder(),
),
]);
shape
这个属性需要多注意下,用来设定button的边框形状。常用的有以下几个:
- BeveledRectangleBorder 带斜角的长方形边框
- CircleBorder 圆形边框
- RoundedRectangleBorder 圆角矩形
- StadiumBorder 两端是半圆的边框
图标按钮(IconButton)
这个比较简单,一个可交互的图形图标。属性页没啥难以理解的,边框是个透明圆形,类似Android的ImageButton。直接上代码:
Widget _IconButton() {
return IconButton(
onPressed: () {},
icon: Image.asset("assets/images/food01.jpeg"),
// icon: Icon(Icons.forward),
iconSize: 24,
);
}
悬浮按钮(FloatingActionButton)
FloatingActionButton,在Material Design中,一般用来处理界面中最常用,最基础的用户动作。它一般出现在屏幕内容的前面,通常是一个圆形,中间有一个图标。 FAB有三种类型:regular, mini, and extended。不要强行使用FAB,只有当使用场景符合FAB功能的时候使用才最为恰当。需要注意的属性如下:
属性 | 作用 |
---|---|
heroTag | hero效果使用的tag,系统默认会给所有FAB使用同一个tag,方便做动画效果,使得界面切换不再那么生硬 |
mini | 是否为“mini”类型,默认为false,设置为true,要小一号,正常size = 56dp ,mini为size = 40dp |
isExtended | 是否为”extended”类型 |
tooltip | FAB的文字解释,FAB被长按时显示在按钮上方(没啥用,可以不管) |
floatingActionButtonLocation
这是Scaffold Widget的一个属性,和FloatingActionButton配合使用。可以设置FloatingActionButton的位置。具体效果如下图:
[站外图片上传中...(image-38ec77-1562061232581)]
使用FloatActionButtonLocation中的centerDocked属性和BottomNavigationBar配合可以实现下面的设计,这个设计在喵叽和王者荣耀里面出现过,具体效果如下:
[站外图片上传中...(image-7c7736-1562061232581)]
class TestPage extends StatefulWidget {
@override
_TestSate createState() {
// TODO: implement createState
return _TestSate();
}
}
class _TestSate extends State {
int _tabIndex = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title: new Text("ZXZ-IMAGE")),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(backgroundColor: Colors.lightBlueAccent,child : Text("+",style: TextStyle(fontSize: 24),),onPressed: (){}),
body: _PageList()[_tabIndex],
bottomNavigationBar: BottomNavigationBar(
items: _list(),
currentIndex: _tabIndex,
onTap: (index) {
setState(() {
_tabIndex = index;
});
}),
);
}
List _list() {
List list = [];
list.add(BottomNavigationBarItem(
icon: Image.asset("assets/images/house.png"),
title: Text("Construct")));
list.add(BottomNavigationBarItem(
icon: Image.asset("assets/images/plane.png"),
title: Text("Attribute")));
return list;
}
List _PageList() {
List result = [];
result.add(ImageConstructPage());
result.add(ImageAttributePage());
return result;
}
}
下拉按钮(DropdownButton)
Material Style下拉菜单按钮,使用方法如下:
class _DropdownState extends State<_DropdownWidget>{
CityItem _item;
List> _list;
@override
void initState() {
_list = [];
_list.add(new DropdownMenuItem(child: Text("上海"),value: CityItem("上海", 0)));
_list.add(DropdownMenuItem(child: Text("北京",style: TextStyle(color: Colors.red),),value: CityItem("北京", 1)));
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return DropdownButton(onChanged: (value) {
setState(() {
_item = value;
});
},items: _list,
value: _item,
hint: Text("请选择城市"),);
}
由于我们在点击每一个条目后,展示的选中条目会变化,所以DropdownButton应当继承StatefulWidget,在选中条目后也就是onChange的回调中使用setState((){})更新对象的状态。另外,这里的列表数据必须是List
属性 | 作用 |
---|---|
onChanged | 菜单选中回调 |
items | 要显示的列表 |
上面代码的结果如下:
[站外图片上传中...(image-75c22c-1562061232581)]
ButtonBar
可以水平摆放一堆button的控件,但我试了下,也可以摆text,不可滑动,超过屏幕宽度报错,感觉这更像一个水平布局,不明白为啥官方归纳到基础组件里面。使用简单,在此不举例了。
PS
- RaisedButton、FlatButton以及OutlineButton的minWidth=88dp,minHeight=36dp,且无法设置,只有通过Container包裹设置其宽度,如果需要设置其宽度自适应,可以使用MaterialButton,设置minWidth = 0。
- 如果要设置button的高度,可以使用Container包裹button,或者使用MaterialButton(这玩意儿有属性height)。
- 如果设置MaterialButton的onPressed为null,则此时button为空白。
- DropdownButton下拉菜单选中的值(注意:在初始化时,要么为null,这时显示默认hint的值;如果自己设定值,则值必须为列表中的一个值,如果不在列表中,会抛出异常)
- IconButton需要被包裹在Scaffold widget中,否则会报错 textfield widgets require a material widget ancestor
- 如果需要去掉点击效果且不禁用该控件,可以把splashColor和hightlightColor同时设置为transparent
- OutlineButton是一个有默认边线且背景透明的按钮,也就是说我们设置其边线和颜色是无效的,其他属性跟MaterialButton中属性基本一致
图片
Image, 图片显示Widget, 和Android ImageView相似,但是从实际使用的方法上看,与常用的图片加载库,如Picasso,Glide等相似,支持本地图片,资源图片,网络图片等加载方式。
- Image:通过ImageProvider来加载图片
- Image.asset:用来加载本地资源图片
- Image.file:用来加载本地(File文件)图片
- Image.network:用来加载网络图片
- Image.memory:用来加载Uint8List资源(byte数组)图片
属性
属性 | 作用 |
---|---|
width & height | 容器宽度高度 |
fit | 图片填充方式 |
color & colorBlendMode | 感觉类似Android Paint Xfermode |
repeat | 复制方式,桌面的图片平铺 (横向、纵向、横向纵向) |
centerSlice | 当图片需要被拉伸显示的时候,centerSlice定义的矩形区域会被拉伸(感觉没啥用) |
matchTextDirection | 与Directionality配合使用,图片展示方向(镜像) |
fit
属性 | 作用 |
---|---|
BoxFit.contain | 全图显示,显示原比例,不需充满 |
BoxFit.fill | 全图显示,显示可能拉伸,填满 |
BoxFit.cover | 显示可能拉伸,可能裁剪,充满 |
BoxFit.fitWidth | 显示可能拉伸,可能裁剪,宽度充满 |
BoxFit.fitHeight | 显示可能拉伸,可能裁剪,高度充满 |
看图纸观点:
[站外图片上传中...(image-f400a9-1562061232581)]
在平时的开发中,基本上都是用上面两个,其他基本上用不到。
圆角图片
- 使用ClipOval,这里注意如果原图不是正方形,使用这个会成一个椭圆。
Widget _AssetImageRec() {
return Image.asset(
"assets/images/food01.jpeg",
width: 200,
height: 200,
fit: BoxFit.fill,
);
}
Widget _CircleAvatar() {
return new ClipOval(child: _AssetImageRec());
}
- 可以用Container包裹一个Image,然后设置Container的decoration
- 使用_ClipRRect,通过设置BorderRadius来实现。
Widget _ClipRRect() {
return ClipRRect(
child: _AssetImage(),
borderRadius: BorderRadius.all(Radius.circular(60)),
);
}
网络缓存图片(占位图片)
- 使用FadeInImage.assetNetwork(flutter自带)
Widget _FadeInImage() {
return FadeInImage.assetNetwork(
placeholder: "assets/images/food01.jpeg",//占位图
image://网络图片
"https://rpic.douyucdn.cn/live-cover/roomCover/cover_update/2019/05/29/43cf32dcde0b77c765681c6f0a9af6fb.jpg",
width: 200,
height: 200,
fit: BoxFit.fill,
fadeInDuration: Duration(milliseconds: 400),
fadeOutDuration: Duration(milliseconds: 400),
);
}
- 使用第三方库:(cached_network_image:https://pub.dartlang.org/packages/cached_network_image)
列表item实现
下图是斗鱼极速版中首页的item,基本上可以通过text和image来实现,这里会用到Stack来实现控件的覆盖,Column实现竖直线性布局,Row实现水平线性布局,这里不多做介绍,具体代码贴上:
[站外图片上传中...(image-5658f9-1562061232581)]
Widget _RoomItem(RoomItem item, double screenWidth) {
return FlatButton(
child: Column(children: [
_ImageItem(item, screenWidth),
Container(
child: Text(item.roomName,
textAlign: TextAlign.left,
softWrap: false,
overflow: TextOverflow.ellipsis),
margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
)
], crossAxisAlignment: CrossAxisAlignment.start),
padding: EdgeInsets.zero,
onPressed: () {},
);
}
Widget _ImageItem(RoomItem item, double screenWidth) {
double itemWidth = (screenWidth - 5) / 2;
return Stack(children: [
Image.network(
item.coverUrl,
width: itemWidth,
height: itemWidth * 3 / 5,
fit: BoxFit.fill,
),
Container(
width: itemWidth,
height: itemWidth * 3 / 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_CornerWidget(),
Expanded(
child: Container(),
),
Container(
height: 29,
width: itemWidth,
alignment: AlignmentDirectional.bottomStart,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.transparent, Color(0x7b000000)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter)),
child: Row(children: [
Container(
padding: EdgeInsets.fromLTRB(4, 0, 0, 6),
width: 100,
child: Text(item.authorName,
style: TextStyle(color: Colors.white, fontSize: 11),
softWrap: false,
overflow: TextOverflow.ellipsis)),
Expanded(
child: Container(
padding: EdgeInsets.fromLTRB(0, 0, 6, 6),
child: Row(
textDirection: TextDirection.rtl,
children: [
Text(item.hot,
style: TextStyle(
color: Colors.white, fontSize: 11),
textAlign: TextAlign.right),
Container(
child: Image.asset(
"assets/images/icon_room_hot.webp",
fit: BoxFit.contain,
width: 9,
height: 9),
margin: EdgeInsets.fromLTRB(0, 0, 4, 0),
)
])),
)
]))
],
),
),
]);
}
单选框、复选框
Material widgets库中提供了Material风格的单选开关Switch和复选框Checkbox,它们都是继承自StatelessWidget,所以它们本身不会保存当前选择状态,所以一般都是在父widget中管理选中状态。当用户点击Switch或Checkbox时,它们会触发onChanged回调,我们可以在此回调中处理选中状态改变逻辑。
公共属性
属性 | 作用 |
---|---|
value | 当前控件的值 |
activeColor | 选中时控件颜色 |
onChanged | 状态发生变化时回调,(bool value) {} |
Switch(ios的那种滑动的单项选择器)
属性 | 作用 |
---|---|
inactiveTrackColor | 未选中时横条颜色 |
inactiveThumbColor | 未选中时滑块颜色 |
inactiveThumbImage | 未选中时滑块上的图片 |
activeThumbImage | 选中时滑块上的图片 |
activeTrackColor | 选中时横条颜色 |
Checkbox(类似于android的checkbox)
属性 | 作用 |
---|---|
checkColor | 选中后里面那个勾的颜色 |
tristate | 会添加一个状态(true-null-false),在null时会有显示一个横条 |
inactiveTrackColor | 选中时横条颜色 |
Radio(类似于android的RadioButton)
属性 | 作用 |
---|---|
groupValue | 和value一起控制是否为选中状态,当groupValue = value时代表选中状态 |
tristate | 会添加一个状态(true-null-false),在null时会有显示一个横条 |
inactiveTrackColor | 选中时横条颜色 |
直接代码:
class TestPage extends StatefulWidget {
@override
_TestState createState() {
// TODO: implement createState
return _TestState();
}
}
class _TestState extends State {
bool _isSwitchChoosed = false;
bool _isCheckboxChoosed = false;
String _radioChoosed = null;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("ZXZ-CHOOSE"),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [_SwitchWidget(), _CheckBox(), _Radio()],
),
);
}
Widget _SwitchWidget() {
return Switch(
onChanged: (bool value) {
setState(() {
_isSwitchChoosed = value;
});
},
value: _isSwitchChoosed,
activeColor: Color(0xFFFF6633),
inactiveThumbColor: Colors.white);
}
Widget _CheckBox() {
return Checkbox(
onChanged: (bool value) {
setState(() {
_isCheckboxChoosed = value;
});
},
value: _isCheckboxChoosed,
activeColor: Color(0xFFFF6633),
tristate: true,
);
}
Widget _Radio() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Radio(
value: "北京",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
},
activeColor: Color(0xFFFF6633),),
Radio(
value: "上海",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
}),
Expanded(child: RadioListTile(
value: "广州",
title: new Text("广州"),
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
}),)
,
Radio(
value: "深圳",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
})
],
);
}
}
效果如下:
[站外图片上传中...(image-e0ca8e-1562061232581)]
PS
- 还有个CheckboxListTile和RadioListTile,可以关注下。
进度条
LinearProgressIndicator & CircularProgressIndicator
Flutter使用LinearProgressIndicator表示条形进度条,CircularProgressIndicator表示圆形进度条,由于两个控件的属性基本相同,下面是几个比较重要的属性:
属性 | 作用 |
---|---|
value | 当前进度,如果 value 为 null 或空,进度条会是一个动画,很像一个loading动画,值只能设置 0 ~ 1.0,如果大于 1,则表示已经结束,显示背景颜色。 |
valueColor | 进度条的颜色,是个颜色渐变的动画 |
slider(滑块)
滑块组件,可用于数量的选择,一般可用于调节变量,像音量啥的,以下是它的属性:
属性 | 说明 |
---|---|
value | 控件的位置,只能在min和max之间,否则报错 |
onChanged | 变化时回调 |
onChangeStart | 滑动开始时回调一次 |
onChangeEnd | 滑动结束时回调一次 |
min | 最小值,不设置默认为0 |
max | 最大值,不设置默认为1 |
divisions | 分为几块,比如设置为5,Slider只能滑动到5个位置 |
label | divisions设置显示在节点上的label,配合divisions使用,不设置divisions则无效 |
activeColor | 滑动过的区域的颜色 |
inactiveColor | 未 滑动过的区域的颜色 |
直接代码:
Widget _getCircularProgressIndicator() {
return Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: CircularProgressIndicator(
value: null,
backgroundColor: Colors.grey,
strokeWidth: 2,
),
);
}
class _SliderTest extends StatefulWidget {
@override
_SliderState createState() {
return _SliderState();
}
}
class _SliderState extends State<_SliderTest> {
double _value = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
child: Slider(
value: _value,
onChanged: (double val) {
setState(() {
this._value = val;
});
},
divisions: 5,
label: "1",),
);
}
}
这里截图如下:
[站外图片上传中...(image-94d658-1562061232581)]
PS
- LinearProgressIndicator、CircularProgressIndicator、slider都具有状态的变化,一般都应放在StatefulWidget中
- LinearProgressIndicator、CircularProgressIndicator的value如果设置为null,会形成一个永不停止的动画,很有loading动画的风格,简单的loading可以用这个一试。
输入框
Flutter中的文本输入框(TextField)就类似于Android中的EditText,但是用起来比EditText方便很多,改变样式也更加的方便。
属性
下面看下TextField中特有的属性(之前其他控件已列过的属性不再列)
属性 | 作用 |
---|---|
controller | 编辑框的控制器,如果不创建的话默认会自动创建,用于和文本交互,例如清除文本 |
focusNode | 用于管理焦点 |
decoration | 输入框的装饰器,用来修改外观,返回InputDecoration |
keyboardType | 设置输入类型,不同的输入类型键盘不一样 |
textInputAction | 用于控制键盘动作(一般位于右下角,默认是完成) |
autofocus | 是否自动获取焦点 |
obscureText | 是否隐藏输入的文字,一般用在密码输入框中 |
autocorrect | 是否自动校验 |
maxLength | 能输入的最大字符个数,设置这个了右下角会冒出个计数器角标,且没法隐藏这个角标,略鸡肋 |
maxLengthEnforced | 配合maxLength一起使用,在达到最大长度时是否阻止输入,默认为true |
onEditingComplete | 点击键盘完成按钮时触发的回调,该回调没有参数,(){} |
onSubmitted | 同样是点击键盘完成按钮时触发的回调,该回调有参数,参数即为当前输入框中的值。(String){} |
inputFormatters | 对输入文本的校验 |
cursorWidth | 光标的宽度 |
cursorRadius | 光标的圆角 |
cursorColor | 光标的颜色 |
onTap | 点击输入框时的回调(){} |
TextEditingController
TextEditingController作为TextField的controller属性。 在用户输入时,controller的text和selection属性不断的更新。要在这些属性更改时得到通知,请使用controller的addListener方法监听控制器 。 (如果你添加了一个监听器,需要在State对象的dispose方法中删除监听器)。该TextEditingController还可以让您控制TextField的内容。最常见的是TextEditingController.text用来获取当前输入框的内容,TextEditingController.clear清空输入框的内容。
InputDecoration
InputDecoration主要用于控制TextField的外观以及提示信息等。主要可以设置前缀样式、后缀样式等,属性很多,这里借用下网上的劳动成果,具体如下:
[站外图片上传中...(image-234926-1562061232581)]
斗鱼顶部搜索框
斗鱼顶部的搜索框前部和后面均有一个图标,这里一开始想用InputDecoration.prefix和InputDecoration.suffix来实现,但是和输入框没法对齐,因此这里就没用这俩属性。
class _TextField extends State<_TextFieldWidget> {
bool _hasInput = false;
TextEditingController controller;
@override
void initState() {
// TODO: implement initState
super.initState();
controller = TextEditingController();
}
@override
Widget build(BuildContext context) {
double statusBarHeight = MediaQuery.of(context).padding.top;
// TODO: implement build
return Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.fromLTRB(16, statusBarHeight, 0, 0),
child: Row(children: [
Expanded(child: _search()),
_cancelBtn(context)
]));
}
Widget _search() {
return Container(
color: Color(0xffF0F2F5),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Container(
padding: EdgeInsets.all(10),
child: Image.asset(
"assets/images/icon_home_search.webp",
width: 24,
height: 24,
)),
Expanded(
child: TextField(
controller: controller,
cursorColor: Color(0xffff7700),
cursorWidth: 1.5,
onChanged: (text) {
//内容改变的回调
setState(() {
if (text.length > 0) {
_hasInput = true;
} else {
_hasInput = false;
}
});
},
decoration: InputDecoration(
counter: null,
contentPadding: EdgeInsets.fromLTRB(0, 10, 0, 10),
fillColor: Color(0xffF0F2F5),
filled: true,
hintText: "搜索房间/主播/分类",
hintStyle: TextStyle(fontSize: 14, color: Color(0xffbbbbbb)),
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xffF0F2F5)),
borderRadius: BorderRadius.all(Radius.circular(2))),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xffF0F2F5)),
borderRadius: BorderRadius.all(Radius.circular(2))),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xffF0F2F5)),
borderRadius: BorderRadius.all(Radius.circular(2)))),
style: TextStyle(
fontSize: 14,
color: Color(0xff333333),
),
),
),
Container(
padding: EdgeInsets.fromLTRB(0, 0, 12, 0),
child: IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
icon: Image.asset(
_hasInput ? "assets/images/icon_search_clear.webp" : "",
width: 16,
height: 16,
),
onPressed: () {
setState(() {
controller.clear();
});
})),
]));
}
Widget _cancelBtn(BuildContext context) {
return MaterialButton(
minWidth: 0,
padding: EdgeInsets.all(0),
onPressed: () {
showDialog(
context: context,
builder: (_) => new LoginDialog(),
barrierDismissible: false);
},
child: Text("取消",
style: TextStyle(
color: Color(0x54000000),
fontSize: 14,
fontWeight: FontWeight.bold)),
elevation: null);
}
}
[站外图片上传中...(image-ca4c74-1562061232581)]
PS
- Row无法包裹TextField,需要在TextField外再包裹一层Expanded方可
- TextField需要被包裹在Scaffold widget中,否则会报错 textfield widgets require a material widget ancestor,后来发现用Material包裹也行
- TextField的prefixIcon和prefix不好用,prefixIcon限制了类型,prefix的对齐效果贼差,建议还是用Row包裹Image+TextField实现图标后面紧跟输入框的视图。
- TextField的最大限制输入maxLength设置之后导致右下角会冒出个计数器角标,而且消不掉,这就尴尬了,感觉只能在onChanged里面处理了。
对话框
SimpleDialog & AlertDialog & AboutDialog
- SimpleDialog,一般可以利用多个SimpleDialogOption为用户提供了几个选项。
- AlertDialog,警告对话框。警告对话框有一个可选标题title和一个可选列表的actions选项。
- AboutDialog,关于对话框。提供一个app信息的对话框
展示 & 隐藏
- 对话框本质上是属于一个路由的页面route,由Navigator进行管理,所以控制对话框的显示和隐藏,也是调用Navigator.of(context)的push和pop方法。
- 在Flutter中,提供了showDialog()、showGeneralDialog()、showCupertinoDialog(),其中调用showDialog()方法展示的是material风格的对话框,调用showCupertinoDialog()方法展示的是ios风格的对话框。而这两个方法其实都会去调用showGeneralDialog()方法,可以从源码中看到最后是利用Navigator.of(context, rootNavigator: true).push()一个页面。这里我在android上使用showCupertinoDialog(),出现的渐变动画会有一点卡顿,另外还有一个showAboutDialog(),打开的是一个关于的对话框。
//打开关于对话框
showAboutDialog(
context: context,
applicationIcon:
Image.asset("assets/images/house.png"),
applicationName: "flutter-douyu",
applicationVersion: "1.0.0"); })
//打开一个对话框
void _showDialog(BuildContext context){
showDialog(context: context, builder: (_) =>
AlertDialog(title: Text("测试"), content: Text("测试测试测试测试测试"),
actions: [
FlatButton(onPressed: () {
Navigator.of(context).pop();//关闭对话框
}, child: Text("确定")),
FlatButton(onPressed: () {
Navigator.of(context).pop();
}, child: Text("取消")),
],));
}
自定义对话框
自定义Dialog可以有两种方式:
- 在Dialog提供了child参数供写视图界面
- 继承Dialog类,就需要重写build函数。
斗鱼的登录界面就是个自定义对话框,具体视图如下图所示:
[站外图片上传中...(image-bcd28a-1562061232581)]
代码太长,不一一贴,主要代码如下:
//第一种方式
void _showDialog1(BuildContext context) {
showDialog(
context: context,
builder: (_) => Dialog(
backgroundColor: Colors.transparent,
child: Container(
width: 311,
height: 500,
child: Center(
child: Stack(
children: [
Container(
margin: EdgeInsets.fromLTRB(0, 80, 0, 0),
padding: EdgeInsets.all(0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Colors.white),
width: 311,
height: 420,
child: _CloseImg(context),
),
_LogoImg(),
_MainWidget()
],
)),
)));
}
//第二种方式
class LoginDialog extends Dialog {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Stack(
children: [
Container(
margin: EdgeInsets.fromLTRB(0, 80, 0, 0),
padding: EdgeInsets.all(0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Colors.white),
width: 311,
height: 420,
child: _CloseImg(context),//
),//底部那个大白框
_LogoImg(),//上面那个鲨鱼娘
_MainWidget()//最主要的操作面板
],
)));
}
}
PS
- 按照我们公司的UI以往的设计方案(坑爹尿性)来看,基本上用Flutter自带的Dialog样式固定,基本上没啥用了,还是自定义吧~~
- 继承Dialog类,重写build函数时会发现里面所有的Text控件会默认有个双下划波浪线,不知道为啥这么设置。。。
总结
- 不同于android的控件,Flutter的控件很少能够自己设置宽高,基本上都需要被包裹在Container里面才行。
- 使用本地文件,除了导入文件之外,还需要在项目根目录下的pubspec.yaml文件中的assets:目录下添加该文件
- 实际写布局的时候会发现层级很深,导致代码里的child一个接一个,建议最好不要超过三层,如果超过写在新的方法体内,否则极难阅读。
- 控件里的属性并不是不设置就没有,例如button里面的splashColor不设置会使用默认的,如果想不使用该属性,需要自己设置。
- 部分控件属性极多,不需要完全搞明白,弄明白重要的就行