文本
Flutter中使用Text來(lái)實(shí)現(xiàn)一般的文本,構(gòu)建一個(gè)Text方法如下:
//構(gòu)造實(shí)例
Text(......)
new Text(......)
屬性
屬性 | 作用 |
---|---|
style | 文本樣式,返回TextStyle,可以設(shè)置字體顏色、下劃線、字體大小等(最常用的玩意兒,基本上都得用) |
textAlign | 文本對(duì)齊方式 |
softWrap | 是否自動(dòng)換行,值為boolean型 |
overflow | 當(dāng)文本內(nèi)容超過(guò)最大行數(shù)時(shí),剩余內(nèi)容的顯示方式當(dāng)文本內(nèi)容超過(guò)最大行數(shù)時(shí),剩余內(nèi)容的顯示方式 |
textDirection | 文本方向,即文字從那邊開(kāi)始顯示(布局里這屬性有些用,這里貌似基本上沒(méi)啥用) |
locale | 此屬性很少設(shè)置,用于選擇區(qū)域特定字形的語(yǔ)言環(huán)境(基本上沒(méi)啥用) |
semanticsLabel | 圖像的語(yǔ)義描述,用于向Andoid上的TalkBack和iOS上的VoiceOver提供圖像描述(可忽略,暫時(shí)沒(méi)啥用) |
[站外圖片上傳中...(image-6886f2-1562061232581)]
TextStyle
TextStyle可以設(shè)置文字的屬性,其中比較簡(jiǎn)單的包括Color(文本顏色)、fontSize(文字大小),如果不設(shè)置,文本將使用最接近的DefaultTextStyle的樣式。如果給定樣式的TextStyle.inherit屬性為true(默認(rèn)值),則給定樣式將與最接近的DefaultTextStyle合并。以下是屬性:
屬性 | 作用 |
---|---|
color | 文本顏色。與foreground互斥 |
decoration | 繪制文本裝飾(下劃線、上劃線、刪除線) |
decorationColor | Decoration的顏色,和decoration配合使用,單獨(dú)使用沒(méi)效果 |
decorationStyle | 繪制文本裝飾的樣式 |
fontWeight | 繪制文本時(shí)使用的字體粗細(xì)(默認(rèn)w400),文字加粗效果用這個(gè) |
fontStyle | 字體變體(斜體、正常) |
fontFamily | 設(shè)置字體,外面導(dǎo)入的稀奇古怪的字體 |
fontSize | 字體大小 |
letterSpacing | 水平字母之間的空間間隔 |
wordSpacing | 水平單詞之間的空間間隔 |
height | 文本行與行的高度(非控件高度,注意了) |
locale | 區(qū)域特定字形 |
background | 文本背景色 |
foreground | 文本的前景色,和color互斥,需要返回paint(畫(huà)筆) |
這里做了一個(gè)簡(jiǎn)單的實(shí)現(xiàn)
Widget _ZongheText() {
return Text(
"一片喧嘩叫嚷之中,忽聽(tīng)得山下一個(gè)雄壯的聲音說(shuō)道:誰(shuí)說(shuō)星宿派武功勝得了丐幫的降龍十八掌?這聲音也不如此響亮,但清清楚楚的傳入了從人耳中,眾人一愕之間,都住了口。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
如果需要對(duì)一段文本的不同段落采用不同的文字樣式,可以使用該控件。具體使用方法如下:
//RichText構(gòu)造實(shí)例
new RichText(......)
Text.rich(......)
//測(cè)試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用于處理一段文字可能有不同的風(fēng)格,如:",
children: <TextSpan>[
TextSpan(text: "紅色,", style: TextStyle(color: Colors.red)),
TextSpan(text: "黃色,", style: TextStyle(color: Colors.yellow)),
TextSpan(text: "藍(lán)色,", style: TextStyle(color: Colors.blue)),
TextSpan(
text: "點(diǎn)我試試",
recognizer: TapGestureRecognizer()
..onTap = () {
var snackBar = SnackBar(
content: Text("hello world"),
duration: Duration(seconds: 3), // 持續(xù)時(shí)間
);
_scaffoldkey.currentState.showSnackBar(snackBar);
}),
],
style: TextStyle(color: Color(0xff000000)))));
}
效果如下:
[站外圖片上傳中...(image-9b1769-1562061232581)]
PS
- Text沒(méi)有寬度、高度以及padding設(shè)置,如果需要設(shè)置這些屬性,則需要在外面包裹一層Container,設(shè)置Container的這些屬性
- 不同于Android的TextView,Text沒(méi)有點(diǎn)擊觸發(fā)監(jiān)聽(tīng),如果需要可點(diǎn)擊,則需要設(shè)置手勢(shì)(感覺(jué)比較麻煩,不推薦)或者使用FlatButton(相當(dāng)于一個(gè)可點(diǎn)擊的Text)
- color和foreground只能存在一個(gè),否則報(bào)錯(cuò)
- RichText中如果內(nèi)部的TextSpan設(shè)置了TextStyle,則外部的TextStyle無(wú)效。
按鈕
在 Flutter 里有很多的 Button,包括了:MaterialButton、RaisedButton、FloatingActionButton、FlatButton、IconButton、ButtonBar、DropdownButton 等。
一般常用的 Button 是 MaterialButton、IconButton、FloatingActionButton。
一般按鈕(MaterialButton、RaisedButton、OutlineButton、FlatButton)
RaisedButton、OutlineButton、FlatButton都是一般的按鈕,這三個(gè)按鈕均是繼承于MaterialButton,它們的區(qū)別在于:
- RaisedButton:一般的按鈕,類似android的button,有凸起
- FlatButton:扁平式按鈕,相當(dāng)于有點(diǎn)擊事件的text
- OutlineButton:帶邊框的背景透明的按鈕
構(gòu)造方法如下:
//構(gòu)造MaterialButton,參數(shù)里面如果沒(méi)有onPressed,會(huì)報(bào)警告,不影響編譯,默認(rèn)為button被禁用,
//這里onPressed回調(diào)里面,()表示回調(diào)參數(shù),這里沒(méi)有表示未傳參,{}里面編寫(xiě)回調(diào)后的操作代碼
MaterialButton( onPressed: () {})
由于這三個(gè)button均是繼承于MaterialButton,MaterialButton具有的屬性這三個(gè)button也都具有,只是MaterialButton的部分屬性它們不具有,這里MaterialButton的具體屬性如下,常用屬性加粗顯示:
屬性 | 作用 | |
---|---|---|
onPressed | 點(diǎn)擊按鈕的回調(diào),設(shè)置為null的話,點(diǎn)擊效果失效 | |
textColor | 文字顏色 | |
disabledTextColor | 按鈕被禁用時(shí)的文字顏色 | |
color | 按鈕的背景色 | 均有 |
disabledColor | 按鈕禁用時(shí)的背景顏色 | |
disabledTextColor | 按鈕禁用時(shí)的文字顏色 | |
splashColor | 點(diǎn)擊按鈕時(shí)水波紋的顏色 | |
shape | 設(shè)置按鈕的形狀 | |
padding | 內(nèi)邊距 | |
elevation | 陰影高度 | |
highlightElevation | 按鈕高亮(按下)時(shí)的陰影高度 | |
disabledElevation | 按鈕禁用時(shí)的陰影高度 | |
minWidth | 最小寬度 | |
height | 按鈕高度 |
左邊FlatButton,右邊MaterialButton
[站外圖片上傳中...(image-49792b-1562061232581)]
return Row(children: <Widget>[
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
這個(gè)屬性需要多注意下,用來(lái)設(shè)定button的邊框形狀。常用的有以下幾個(gè):
- BeveledRectangleBorder 帶斜角的長(zhǎng)方形邊框
- CircleBorder 圓形邊框
- RoundedRectangleBorder 圓角矩形
- StadiumBorder 兩端是半圓的邊框
圖標(biāo)按鈕(IconButton)
這個(gè)比較簡(jiǎn)單,一個(gè)可交互的圖形圖標(biāo)。屬性頁(yè)沒(méi)啥難以理解的,邊框是個(gè)透明圓形,類似Android的ImageButton。直接上代碼:
Widget _IconButton() {
return IconButton(
onPressed: () {},
icon: Image.asset("assets/images/food01.jpeg"),
// icon: Icon(Icons.forward),
iconSize: 24,
);
}
懸浮按鈕(FloatingActionButton)
FloatingActionButton,在Material Design中,一般用來(lái)處理界面中最常用,最基礎(chǔ)的用戶動(dòng)作。它一般出現(xiàn)在屏幕內(nèi)容的前面,通常是一個(gè)圓形,中間有一個(gè)圖標(biāo)。 FAB有三種類型:regular, mini, and extended。不要強(qiáng)行使用FAB,只有當(dāng)使用場(chǎng)景符合FAB功能的時(shí)候使用才最為恰當(dāng)。需要注意的屬性如下:
屬性 | 作用 |
---|---|
heroTag | hero效果使用的tag,系統(tǒng)默認(rèn)會(huì)給所有FAB使用同一個(gè)tag,方便做動(dòng)畫(huà)效果,使得界面切換不再那么生硬 |
mini | 是否為“mini”類型,默認(rèn)為false,設(shè)置為true,要小一號(hào),正常size = 56dp ,mini為size = 40dp |
isExtended | 是否為”extended”類型 |
tooltip | FAB的文字解釋,F(xiàn)AB被長(zhǎng)按時(shí)顯示在按鈕上方(沒(méi)啥用,可以不管) |
floatingActionButtonLocation
這是Scaffold Widget的一個(gè)屬性,和FloatingActionButton配合使用。可以設(shè)置FloatingActionButton的位置。具體效果如下圖:
[站外圖片上傳中...(image-38ec77-1562061232581)]
使用FloatActionButtonLocation中的centerDocked屬性和BottomNavigationBar配合可以實(shí)現(xiàn)下面的設(shè)計(jì),這個(gè)設(shè)計(jì)在喵嘰和王者榮耀里面出現(xiàn)過(guò),具體效果如下:
[站外圖片上傳中...(image-7c7736-1562061232581)]
class TestPage extends StatefulWidget {
@override
_TestSate createState() {
// TODO: implement createState
return _TestSate();
}
}
class _TestSate extends State<TestPage> {
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<BottomNavigationBarItem> _list() {
List<BottomNavigationBarItem> 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<Widget> _PageList() {
List<Widget> result = [];
result.add(ImageConstructPage());
result.add(ImageAttributePage());
return result;
}
}
下拉按鈕(DropdownButton)
Material Style下拉菜單按鈕,使用方法如下:
class _DropdownState extends State<_DropdownWidget>{
CityItem _item;
List<DropdownMenuItem<CityItem>> _list;
@override
void initState() {
_list = [];
_list.add(new DropdownMenuItem<CityItem>(child: Text("上海"),value: CityItem("上海", 0)));
_list.add(DropdownMenuItem<CityItem>(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("請(qǐng)選擇城市"),);
}
由于我們?cè)邳c(diǎn)擊每一個(gè)條目后,展示的選中條目會(huì)變化,所以DropdownButton應(yīng)當(dāng)繼承StatefulWidget,在選中條目后也就是onChange的回調(diào)中使用setState((){})更新對(duì)象的狀態(tài)。另外,這里的列表數(shù)據(jù)必須是List<DropdownMenuItem<T>>。DropdownMenuItem<T>里面包含一個(gè)child和value,綁定了view和data,這里每個(gè)item都可以設(shè)置一個(gè)child,意味著下拉菜單的每個(gè)item可以有不同的view。這里需要注意的是下面兩個(gè)屬性,其他的屬性比較簡(jiǎn)單或者已經(jīng)再其他button說(shuō)明過(guò),就不再說(shuō)明。
屬性 | 作用 |
---|---|
onChanged | 菜單選中回調(diào) |
items | 要顯示的列表 |
上面代碼的結(jié)果如下:
[站外圖片上傳中...(image-75c22c-1562061232581)]
ButtonBar
可以水平擺放一堆button的控件,但我試了下,也可以擺text,不可滑動(dòng),超過(guò)屏幕寬度報(bào)錯(cuò),感覺(jué)這更像一個(gè)水平布局,不明白為啥官方歸納到基礎(chǔ)組件里面。使用簡(jiǎn)單,在此不舉例了。
PS
- RaisedButton、FlatButton以及OutlineButton的minWidth=88dp,minHeight=36dp,且無(wú)法設(shè)置,只有通過(guò)Container包裹設(shè)置其寬度,如果需要設(shè)置其寬度自適應(yīng),可以使用MaterialButton,設(shè)置minWidth = 0。
- 如果要設(shè)置button的高度,可以使用Container包裹button,或者使用MaterialButton(這玩意兒有屬性height)。
- 如果設(shè)置MaterialButton的onPressed為null,則此時(shí)button為空白。
- DropdownButton下拉菜單選中的值(注意:在初始化時(shí),要么為null,這時(shí)顯示默認(rèn)hint的值;如果自己設(shè)定值,則值必須為列表中的一個(gè)值,如果不在列表中,會(huì)拋出異常)
- IconButton需要被包裹在Scaffold widget中,否則會(huì)報(bào)錯(cuò) textfield widgets require a material widget ancestor
- 如果需要去掉點(diǎn)擊效果且不禁用該控件,可以把splashColor和hightlightColor同時(shí)設(shè)置為transparent
- OutlineButton是一個(gè)有默認(rèn)邊線且背景透明的按鈕,也就是說(shuō)我們?cè)O(shè)置其邊線和顏色是無(wú)效的,其他屬性跟MaterialButton中屬性基本一致
圖片
Image, 圖片顯示W(wǎng)idget, 和Android ImageView相似,但是從實(shí)際使用的方法上看,與常用的圖片加載庫(kù),如Picasso,Glide等相似,支持本地圖片,資源圖片,網(wǎng)絡(luò)圖片等加載方式。
- Image:通過(guò)ImageProvider來(lái)加載圖片
- Image.asset:用來(lái)加載本地資源圖片
- Image.file:用來(lái)加載本地(File文件)圖片
- Image.network:用來(lái)加載網(wǎng)絡(luò)圖片
- Image.memory:用來(lái)加載Uint8List資源(byte數(shù)組)圖片
屬性
屬性 | 作用 |
---|---|
width & height | 容器寬度高度 |
fit | 圖片填充方式 |
color & colorBlendMode | 感覺(jué)類似Android Paint Xfermode |
repeat | 復(fù)制方式,桌面的圖片平鋪 (橫向、縱向、橫向縱向) |
centerSlice | 當(dāng)圖片需要被拉伸顯示的時(shí)候,centerSlice定義的矩形區(qū)域會(huì)被拉伸(感覺(jué)沒(méi)啥用) |
matchTextDirection | 與Directionality配合使用,圖片展示方向(鏡像) |
fit
屬性 | 作用 |
---|---|
BoxFit.contain | 全圖顯示,顯示原比例,不需充滿 |
BoxFit.fill | 全圖顯示,顯示可能拉伸,填滿 |
BoxFit.cover | 顯示可能拉伸,可能裁剪,充滿 |
BoxFit.fitWidth | 顯示可能拉伸,可能裁剪,寬度充滿 |
BoxFit.fitHeight | 顯示可能拉伸,可能裁剪,高度充滿 |
看圖紙觀點(diǎn):
[站外圖片上傳中...(image-f400a9-1562061232581)]
在平時(shí)的開(kāi)發(fā)中,基本上都是用上面兩個(gè),其他基本上用不到。
圓角圖片
- 使用ClipOval,這里注意如果原圖不是正方形,使用這個(gè)會(huì)成一個(gè)橢圓。
Widget _AssetImageRec() {
return Image.asset(
"assets/images/food01.jpeg",
width: 200,
height: 200,
fit: BoxFit.fill,
);
}
Widget _CircleAvatar() {
return new ClipOval(child: _AssetImageRec());
}
- 可以用Container包裹一個(gè)Image,然后設(shè)置Container的decoration
- 使用_ClipRRect,通過(guò)設(shè)置BorderRadius來(lái)實(shí)現(xiàn)。
Widget _ClipRRect() {
return ClipRRect(
child: _AssetImage(),
borderRadius: BorderRadius.all(Radius.circular(60)),
);
}
網(wǎng)絡(luò)緩存圖片(占位圖片)
- 使用FadeInImage.assetNetwork(flutter自帶)
Widget _FadeInImage() {
return FadeInImage.assetNetwork(
placeholder: "assets/images/food01.jpeg",//占位圖
image://網(wǎng)絡(luò)圖片
"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),
);
}
- 使用第三方庫(kù):(cached_network_image:https://pub.dartlang.org/packages/cached_network_image)
列表item實(shí)現(xiàn)
下圖是斗魚(yú)極速版中首頁(yè)的item,基本上可以通過(guò)text和image來(lái)實(shí)現(xiàn),這里會(huì)用到Stack來(lái)實(shí)現(xiàn)控件的覆蓋,Column實(shí)現(xiàn)豎直線性布局,Row實(shí)現(xiàn)水平線性布局,這里不多做介紹,具體代碼貼上:
[站外圖片上傳中...(image-5658f9-1562061232581)]
Widget _RoomItem(RoomItem item, double screenWidth) {
return FlatButton(
child: Column(children: <Widget>[
_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: <Widget>[
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: <Widget>[
_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: <Widget>[
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: <Widget>[
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),
)
])),
)
]))
],
),
),
]);
}
單選框、復(fù)選框
Material widgets庫(kù)中提供了Material風(fēng)格的單選開(kāi)關(guān)Switch和復(fù)選框Checkbox,它們都是繼承自StatelessWidget,所以它們本身不會(huì)保存當(dāng)前選擇狀態(tài),所以一般都是在父widget中管理選中狀態(tài)。當(dāng)用戶點(diǎn)擊Switch或Checkbox時(shí),它們會(huì)觸發(fā)onChanged回調(diào),我們可以在此回調(diào)中處理選中狀態(tài)改變邏輯。
公共屬性
屬性 | 作用 |
---|---|
value | 當(dāng)前控件的值 |
activeColor | 選中時(shí)控件顏色 |
onChanged | 狀態(tài)發(fā)生變化時(shí)回調(diào),(bool value) {} |
Switch(ios的那種滑動(dòng)的單項(xiàng)選擇器)
屬性 | 作用 |
---|---|
inactiveTrackColor | 未選中時(shí)橫條顏色 |
inactiveThumbColor | 未選中時(shí)滑塊顏色 |
inactiveThumbImage | 未選中時(shí)滑塊上的圖片 |
activeThumbImage | 選中時(shí)滑塊上的圖片 |
activeTrackColor | 選中時(shí)橫條顏色 |
Checkbox(類似于android的checkbox)
屬性 | 作用 |
---|---|
checkColor | 選中后里面那個(gè)勾的顏色 |
tristate | 會(huì)添加一個(gè)狀態(tài)(true-null-false),在null時(shí)會(huì)有顯示一個(gè)橫條 |
inactiveTrackColor | 選中時(shí)橫條顏色 |
Radio(類似于android的RadioButton)
屬性 | 作用 |
---|---|
groupValue | 和value一起控制是否為選中狀態(tài),當(dāng)groupValue = value時(shí)代表選中狀態(tài) |
tristate | 會(huì)添加一個(gè)狀態(tài)(true-null-false),在null時(shí)會(huì)有顯示一個(gè)橫條 |
inactiveTrackColor | 選中時(shí)橫條顏色 |
直接代碼:
class TestPage extends StatefulWidget {
@override
_TestState createState() {
// TODO: implement createState
return _TestState();
}
}
class _TestState extends State<TestPage> {
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: <Widget>[_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: <Widget>[
Radio<String>(
value: "北京",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
},
activeColor: Color(0xFFFF6633),),
Radio<String>(
value: "上海",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
}),
Expanded(child: RadioListTile<String>(
value: "廣州",
title: new Text("廣州"),
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
}),)
,
Radio<String>(
value: "深圳",
groupValue: _radioChoosed,
onChanged: (String s) {
setState(() {
this._radioChoosed = s;
});
})
],
);
}
}
效果如下:
[站外圖片上傳中...(image-e0ca8e-1562061232581)]
PS
- 還有個(gè)CheckboxListTile和RadioListTile,可以關(guān)注下。
進(jìn)度條
LinearProgressIndicator & CircularProgressIndicator
Flutter使用LinearProgressIndicator表示條形進(jìn)度條,CircularProgressIndicator表示圓形進(jìn)度條,由于兩個(gè)控件的屬性基本相同,下面是幾個(gè)比較重要的屬性:
屬性 | 作用 |
---|---|
value | 當(dāng)前進(jìn)度,如果 value 為 null 或空,進(jìn)度條會(huì)是一個(gè)動(dòng)畫(huà),很像一個(gè)loading動(dòng)畫(huà),值只能設(shè)置 0 ~ 1.0,如果大于 1,則表示已經(jīng)結(jié)束,顯示背景顏色。 |
valueColor | 進(jìn)度條的顏色,是個(gè)顏色漸變的動(dòng)畫(huà) |
slider(滑塊)
滑塊組件,可用于數(shù)量的選擇,一般可用于調(diào)節(jié)變量,像音量啥的,以下是它的屬性:
屬性 | 說(shuō)明 |
---|---|
value | 控件的位置,只能在min和max之間,否則報(bào)錯(cuò) |
onChanged | 變化時(shí)回調(diào) |
onChangeStart | 滑動(dòng)開(kāi)始時(shí)回調(diào)一次 |
onChangeEnd | 滑動(dòng)結(jié)束時(shí)回調(diào)一次 |
min | 最小值,不設(shè)置默認(rèn)為0 |
max | 最大值,不設(shè)置默認(rèn)為1 |
divisions | 分為幾塊,比如設(shè)置為5,Slider只能滑動(dòng)到5個(gè)位置 |
label | divisions設(shè)置顯示在節(jié)點(diǎn)上的label,配合divisions使用,不設(shè)置divisions則無(wú)效 |
activeColor | 滑動(dòng)過(guò)的區(qū)域的顏色 |
inactiveColor | 未 滑動(dòng)過(guò)的區(qū)域的顏色 |
直接代碼:
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都具有狀態(tài)的變化,一般都應(yīng)放在StatefulWidget中
- LinearProgressIndicator、CircularProgressIndicator的value如果設(shè)置為null,會(huì)形成一個(gè)永不停止的動(dòng)畫(huà),很有l(wèi)oading動(dòng)畫(huà)的風(fēng)格,簡(jiǎn)單的loading可以用這個(gè)一試。
輸入框
Flutter中的文本輸入框(TextField)就類似于Android中的EditText,但是用起來(lái)比EditText方便很多,改變樣式也更加的方便。
屬性
下面看下TextField中特有的屬性(之前其他控件已列過(guò)的屬性不再列)
屬性 | 作用 |
---|---|
controller | 編輯框的控制器,如果不創(chuàng)建的話默認(rèn)會(huì)自動(dòng)創(chuàng)建,用于和文本交互,例如清除文本 |
focusNode | 用于管理焦點(diǎn) |
decoration | 輸入框的裝飾器,用來(lái)修改外觀,返回InputDecoration |
keyboardType | 設(shè)置輸入類型,不同的輸入類型鍵盤(pán)不一樣 |
textInputAction | 用于控制鍵盤(pán)動(dòng)作(一般位于右下角,默認(rèn)是完成) |
autofocus | 是否自動(dòng)獲取焦點(diǎn) |
obscureText | 是否隱藏輸入的文字,一般用在密碼輸入框中 |
autocorrect | 是否自動(dòng)校驗(yàn) |
maxLength | 能輸入的最大字符個(gè)數(shù),設(shè)置這個(gè)了右下角會(huì)冒出個(gè)計(jì)數(shù)器角標(biāo),且沒(méi)法隱藏這個(gè)角標(biāo),略雞肋 |
maxLengthEnforced | 配合maxLength一起使用,在達(dá)到最大長(zhǎng)度時(shí)是否阻止輸入,默認(rèn)為true |
onEditingComplete | 點(diǎn)擊鍵盤(pán)完成按鈕時(shí)觸發(fā)的回調(diào),該回調(diào)沒(méi)有參數(shù),(){} |
onSubmitted | 同樣是點(diǎn)擊鍵盤(pán)完成按鈕時(shí)觸發(fā)的回調(diào),該回調(diào)有參數(shù),參數(shù)即為當(dāng)前輸入框中的值。(String){} |
inputFormatters | 對(duì)輸入文本的校驗(yàn) |
cursorWidth | 光標(biāo)的寬度 |
cursorRadius | 光標(biāo)的圓角 |
cursorColor | 光標(biāo)的顏色 |
onTap | 點(diǎn)擊輸入框時(shí)的回調(diào)(){} |
TextEditingController
TextEditingController作為T(mén)extField的controller屬性。 在用戶輸入時(shí),controller的text和selection屬性不斷的更新。要在這些屬性更改時(shí)得到通知,請(qǐng)使用controller的addListener方法監(jiān)聽(tīng)控制器 。 (如果你添加了一個(gè)監(jiān)聽(tīng)器,需要在State對(duì)象的dispose方法中刪除監(jiān)聽(tīng)器)。該TextEditingController還可以讓您控制TextField的內(nèi)容。最常見(jiàn)的是TextEditingController.text用來(lái)獲取當(dāng)前輸入框的內(nèi)容,TextEditingController.clear清空輸入框的內(nèi)容。
InputDecoration
InputDecoration主要用于控制TextField的外觀以及提示信息等。主要可以設(shè)置前綴樣式、后綴樣式等,屬性很多,這里借用下網(wǎng)上的勞動(dòng)成果,具體如下:
[站外圖片上傳中...(image-234926-1562061232581)]
斗魚(yú)頂部搜索框
斗魚(yú)頂部的搜索框前部和后面均有一個(gè)圖標(biāo),這里一開(kāi)始想用InputDecoration.prefix和InputDecoration.suffix來(lái)實(shí)現(xiàn),但是和輸入框沒(méi)法對(duì)齊,因此這里就沒(méi)用這倆屬性。
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: <Widget>[
Expanded(child: _search()),
_cancelBtn(context)
]));
}
Widget _search() {
return Container(
color: Color(0xffF0F2F5),
child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
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) {
//內(nèi)容改變的回調(diào)
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無(wú)法包裹TextField,需要在TextField外再包裹一層Expanded方可
- TextField需要被包裹在Scaffold widget中,否則會(huì)報(bào)錯(cuò) textfield widgets require a material widget ancestor,后來(lái)發(fā)現(xiàn)用Material包裹也行
- TextField的prefixIcon和prefix不好用,prefixIcon限制了類型,prefix的對(duì)齊效果賊差,建議還是用Row包裹Image+TextField實(shí)現(xiàn)圖標(biāo)后面緊跟輸入框的視圖。
- TextField的最大限制輸入maxLength設(shè)置之后導(dǎo)致右下角會(huì)冒出個(gè)計(jì)數(shù)器角標(biāo),而且消不掉,這就尷尬了,感覺(jué)只能在onChanged里面處理了。
對(duì)話框
SimpleDialog & AlertDialog & AboutDialog
- SimpleDialog,一般可以利用多個(gè)SimpleDialogOption為用戶提供了幾個(gè)選項(xiàng)。
- AlertDialog,警告對(duì)話框。警告對(duì)話框有一個(gè)可選標(biāo)題title和一個(gè)可選列表的actions選項(xiàng)。
- AboutDialog,關(guān)于對(duì)話框。提供一個(gè)app信息的對(duì)話框
展示 & 隱藏
- 對(duì)話框本質(zhì)上是屬于一個(gè)路由的頁(yè)面route,由Navigator進(jìn)行管理,所以控制對(duì)話框的顯示和隱藏,也是調(diào)用Navigator.of(context)的push和pop方法。
- 在Flutter中,提供了showDialog()、showGeneralDialog()、showCupertinoDialog(),其中調(diào)用showDialog()方法展示的是material風(fēng)格的對(duì)話框,調(diào)用showCupertinoDialog()方法展示的是ios風(fēng)格的對(duì)話框。而這兩個(gè)方法其實(shí)都會(huì)去調(diào)用showGeneralDialog()方法,可以從源碼中看到最后是利用Navigator.of(context, rootNavigator: true).push()一個(gè)頁(yè)面。這里我在android上使用showCupertinoDialog(),出現(xiàn)的漸變動(dòng)畫(huà)會(huì)有一點(diǎn)卡頓,另外還有一個(gè)showAboutDialog(),打開(kāi)的是一個(gè)關(guān)于的對(duì)話框。
//打開(kāi)關(guān)于對(duì)話框
showAboutDialog(
context: context,
applicationIcon:
Image.asset("assets/images/house.png"),
applicationName: "flutter-douyu",
applicationVersion: "1.0.0"); })
//打開(kāi)一個(gè)對(duì)話框
void _showDialog(BuildContext context){
showDialog(context: context, builder: (_) =>
AlertDialog(title: Text("測(cè)試"), content: Text("測(cè)試測(cè)試測(cè)試測(cè)試測(cè)試"),
actions: <Widget>[
FlatButton(onPressed: () {
Navigator.of(context).pop();//關(guān)閉對(duì)話框
}, child: Text("確定")),
FlatButton(onPressed: () {
Navigator.of(context).pop();
}, child: Text("取消")),
],));
}
自定義對(duì)話框
自定義Dialog可以有兩種方式:
- 在Dialog提供了child參數(shù)供寫(xiě)視圖界面
- 繼承Dialog類,就需要重寫(xiě)build函數(shù)。
斗魚(yú)的登錄界面就是個(gè)自定義對(duì)話框,具體視圖如下圖所示:
[站外圖片上傳中...(image-bcd28a-1562061232581)]
代碼太長(zhǎng),不一一貼,主要代碼如下:
//第一種方式
void _showDialog1(BuildContext context) {
showDialog(
context: context,
builder: (_) => Dialog(
backgroundColor: Colors.transparent,
child: Container(
width: 311,
height: 500,
child: Center(
child: Stack(
children: <Widget>[
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: <Widget>[
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),//
),//底部那個(gè)大白框
_LogoImg(),//上面那個(gè)鯊魚(yú)娘
_MainWidget()//最主要的操作面板
],
)));
}
}
PS
- 按照我們公司的UI以往的設(shè)計(jì)方案(坑爹尿性)來(lái)看,基本上用Flutter自帶的Dialog樣式固定,基本上沒(méi)啥用了,還是自定義吧~~
- 繼承Dialog類,重寫(xiě)build函數(shù)時(shí)會(huì)發(fā)現(xiàn)里面所有的Text控件會(huì)默認(rèn)有個(gè)雙下劃波浪線,不知道為啥這么設(shè)置。。。
總結(jié)
- 不同于android的控件,F(xiàn)lutter的控件很少能夠自己設(shè)置寬高,基本上都需要被包裹在Container里面才行。
- 使用本地文件,除了導(dǎo)入文件之外,還需要在項(xiàng)目根目錄下的pubspec.yaml文件中的assets:目錄下添加該文件
- 實(shí)際寫(xiě)布局的時(shí)候會(huì)發(fā)現(xiàn)層級(jí)很深,導(dǎo)致代碼里的child一個(gè)接一個(gè),建議最好不要超過(guò)三層,如果超過(guò)寫(xiě)在新的方法體內(nèi),否則極難閱讀。
- 控件里的屬性并不是不設(shè)置就沒(méi)有,例如button里面的splashColor不設(shè)置會(huì)使用默認(rèn)的,如果想不使用該屬性,需要自己設(shè)置。
- 部分控件屬性極多,不需要完全搞明白,弄明白重要的就行