????這個章節本來打算講解Flutter的渲染原理,但是學習初期過多的講解原理性的內容,并不利于大家快速入門和上手,做出一些帶效果的內容;
????所以,我打算換一種思路,先講解一些組件的用法,讓大家習慣Flutter的開發過程和模式,再回頭去鞏固原理性的知識;
????另外,在講解這些Widget的時候,我并不打算將所有的屬性一一列出,因為沒有意義,也記不??;
????我后面打算有一個專題是關于Flutter布局的,會選出一些好看的布局界面帶著大家一起來完成:美團頁面、京東頁面、B站頁面等等,某些我目前沒有講到的屬性,后面應用的會再進行講解
1. 文本Widget
在Android中,我們使用TextView,iOS中我們使用UILabel來顯示文本;
Flutter中,我們使用Text組件控制文本如何展示;
1.1. 普通文本展示
在Flutter中,我們可以將文本的控制顯示分成兩類:
????控制文本布局的參數: 如文本對齊方式 textAlign、文本排版方向 textDirection,文本顯示最大行數 maxLines、文本截斷規則 overflow 等等,這些都是構造函數中的參數;
????控制文本樣式的參數: 如字體名稱 fontFamily、字體大小 fontSize、文本顏色 color、文本陰影 shadows 等等,這些參數被統一封裝到了構造函數中的參數 style 中;
下面我們來看一下其中一些屬性的使用:
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Text(
? ? ? "《定風波》 蘇軾 \n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
? ? ? style: TextStyle(
? ? ? ? fontSize: 20,
? ? ? ? color: Colors.purple
? ? ? ),
? ? );
? }
}
我們可以通過一些屬性來改變Text的布局:
????textAlign:文本對齊方式,比如TextAlign.center
????maxLines:最大顯示行數,比如1
????overflow:超出部分顯示方式,比如TextOverflow.ellipsis
????textScaleFactor:控制文本縮放,比如1.24
代碼如下:
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Text(
? ? ? "《定風波》 蘇軾 \n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
? ? ? textAlign: TextAlign.center, // 所有內容都居中對齊
? ? ? maxLines: 3, // 顯然 "生。" 被刪除了
? ? ? overflow: TextOverflow.ellipsis, // 超出部分顯示...
//? ? ? textScaleFactor: 1.25,
? ? ? style: TextStyle(
? ? ? ? fontSize: 20,
? ? ? ? color: Colors.purple
? ? ? ),
? ? );
? }
}
1.2. 富文本展示
前面展示的文本,我們都應用了相同的樣式,如果我們希望給他們不同的樣式呢?
????比如《定風波》我希望字體更大一點,并且是黑色字體,并且有加粗效果;
????比如 蘇軾 我希望是紅色字體;
如果希望展示這種混合樣式,那么我們可以利用分片來進行操作(在Android中,我們可以使用SpannableString,在iOS中,我們可以使用NSAttributedString完成,了解即可)
代碼如下:
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Text.rich(
? ? ? TextSpan(
? ? ? ? children: [
? ? ? ? ? TextSpan(text: "《定風波》", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Colors.black)),
? ? ? ? ? TextSpan(text: "蘇軾", style: TextStyle(fontSize: 18, color: Colors.redAccent)),
? ? ? ? ? TextSpan(text: "\n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。")
? ? ? ? ],
? ? ? ),
? ? ? style: TextStyle(fontSize: 20, color: Colors.purple),
? ? ? textAlign: TextAlign.center,
? ? );
? }
}
二. 按鈕Widget
2.1. 按鈕的基礎
Material widget庫中提供了多種按鈕Widget如FloatingActionButton、RaisedButton、FlatButton、OutlineButton等
我們直接來對他們進行一個展示:
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Column(
? ? ? children: <Widget>[
? ? ? ? FloatingActionButton(
? ? ? ? ? child: Text("FloatingActionButton"),
? ? ? ? ? onPressed: () {
? ? ? ? ? ? print("FloatingActionButton Click");
? ? ? ? ? },
? ? ? ? ),
? ? ? ? RaisedButton(
? ? ? ? ? child: Text("RaisedButton"),
? ? ? ? ? onPressed: () {
? ? ? ? ? ? print("RaisedButton Click");
? ? ? ? ? },
? ? ? ? ),
? ? ? ? FlatButton(
? ? ? ? ? child: Text("FlatButton"),
? ? ? ? ? onPressed: () {
? ? ? ? ? ? print("FlatButton Click");
? ? ? ? ? },
? ? ? ? ),
? ? ? ? OutlineButton(
? ? ? ? ? child: Text("OutlineButton"),
? ? ? ? ? onPressed: () {
? ? ? ? ? ? print("OutlineButton Click");
? ? ? ? ? },
? ? ? ? )
? ? ? ],
? ? );
? }
}
2.2. 自定義樣式
前面的按鈕我們使用的都是默認樣式,我們可以通過一些屬性來改變按鈕的樣式
RaisedButton(
? child: Text("同意協議", style: TextStyle(color: Colors.white)),
? color: Colors.orange, // 按鈕的顏色
? highlightColor: Colors.orange[700], // 按下去高亮顏色
? shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), // 圓角的實現
? onPressed: () {
? ? print("同意協議");
? },
)
事實上這里還有一個比較常見的屬性:elevation,用于控制陰影的大小,很多地方都會有這個屬性,大家可以自行演示一下
三. 圖片Widget
圖片可以讓我們的應用更加豐富多彩,Flutter中使用Image組件
Image組件有很多的構造函數,我們這里主要學習兩個:
????Image.assets:加載本地資源圖片;
????Image.network:加載網絡中的圖片;
3.1. 加載網絡圖片
相對來講,Flutter中加載網絡圖片會更加簡單,直接傳入URL并不需要什么配置,所以我們先來看一下Flutter中如何加載網絡圖片。
我們先來看看Image有哪些屬性可以設置:
const Image({
? ...
? this.width, //圖片的寬
? this.height, //圖片高度
? this.color, //圖片的混合色值
? this.colorBlendMode, //混合模式
? this.fit,//縮放模式
? this.alignment = Alignment.center, //對齊方式
? this.repeat = ImageRepeat.noRepeat, //重復方式
? ...
})
width、height:用于設置圖片的寬、高,當不指定寬高時,圖片會根據當前父容器的限制,盡可能的顯示其原始大小,如果只設置width、height的其中一個,那么另一個屬性默認會按比例縮放,但可以通過下面介紹的fit屬性來指定適應規則。
fit:該屬性用于在圖片的顯示空間和圖片本身大小不同時指定圖片的適應模式。適應模式是在BoxFit中定義,它是一個枚舉類型,有如下值:
????fill:會拉伸填充滿顯示空間,圖片本身長寬比會發生變化,圖片會變形。
????cover:會按圖片的長寬比放大后居中填滿顯示空間,圖片不會變形,超出顯示空間部分會被剪裁。
????contain:這是圖片的默認適應規則,圖片會在保證圖片本身長寬比不變的情況下縮放以適應當前顯示空間,圖片不會變形。
????fitWidth:圖片的寬度會縮放到顯示空間的寬度,高度會按比例縮放,然后居中顯示,圖片不會變形,超出顯示空間部分會被剪裁。
????fitHeight:圖片的高度會縮放到顯示空間的高度,寬度會按比例縮放,然后居中顯示,圖片不會變形,超出顯示空間部分會被剪裁。
????none:圖片沒有適應策略,會在顯示空間內顯示圖片,如果圖片比顯示空間大,則顯示空間只會顯示圖片中間部分。
????color和 colorBlendMode:在圖片繪制時可以對每一個像素進行顏色混合處理,color指定混合色,而colorBlendMode指定混合模式;
????repeat:當圖片本身大小小于顯示空間時,指定圖片的重復規則。
我們對其中某些屬性做一個演練:
????注意,這里我用了一個Container,大家可以把它理解成一個UIView或者View,就是一個容器;
????后面我會專門講到這個組件的使用;
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Center(
? ? ? child: Container(
? ? ? ? child: Image.network(
? ? ? ? ? ? ? ? ? ? ? ?????????????????????"http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
? ? ? ? ? alignment: Alignment.topCenter,
? ? ? ? ? repeat: ImageRepeat.repeatY,
? ? ? ? ? color: Colors.red,
? ? ? ? ? colorBlendMode: BlendMode.colorDodge,
? ? ? ? ),
? ? ? ? width: 300,
? ? ? ? height: 300,
? ? ? ? color: Colors.yellow,
? ? ? ),
? ? );
? }
}
3.2. 加載本地圖片
加載本地圖片稍微麻煩一點,需要將圖片引入,并且進行配置
class MyHomeBody extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Center(
? ? ? child: Container(
? ? ? ? width: 300,
? ? ? ? height: 300,
? ? ? ? color: Colors.yellow,
? ? ? ? child: Image.asset("images/test.jpeg"),
? ? ? ),
? ? );
? }
}
3.3. 實現圓角圖像
在Flutter中實現圓角效果也是使用一些Widget來實現的。
3.3.1. 實現圓角頭像
方式一:CircleAvatar
CircleAvatar可以實現圓角頭像,也可以添加一個子Widget:
const CircleAvatar({
? Key key,
? this.child, // 子Widget
? this.backgroundColor, // 背景顏色
? this.backgroundImage, // 背景圖像
? this.foregroundColor, // 前景顏色
? this.radius, // 半徑
? this.minRadius, // 最小半徑
? this.maxRadius, // 最大半徑
})
我們來實現一個圓形頭像:
????注意一:這里我們使用的是NetworkImage,因為backgroundImage要求我們傳入一個ImageProvider;
????????ImageProvider是一個抽象類,事實上所有我們前面創建的Image對象都有包含image屬性,該屬性就是一個ImageProvider
????注意二:這里我還在里面添加了一個文字,但是我在文字外層包裹了一個Container;
????????這里Container的作用是為了可以控制文字在其中的位置調整;
class HomeContent extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Center(
? ? ? child: CircleAvatar(
? ? ? ? radius: 100,
? ? ? ? backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
? ? ? ? child: Container(
? ? ? ? ? alignment: Alignment(0, .5),
? ? ? ? ? width: 200,
? ? ? ? ? height: 200,
? ? ? ? ? child: Text("兵長利威爾")
? ? ? ? ),
? ? ? ),
? ? );
? }
}
方式二:ClipOval
ClipOval也可以實現圓角頭像,而且通常是在只有頭像時使用
class HomeContent extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Center(
? ? ? child: ClipOval(
? ? ? ? child: Image.network(
? ? ? ? ? "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
? ? ? ? ? width: 200,
? ? ? ? ? height: 200,
? ? ? ? ),
? ? ? ),
? ? );
? }
}
實現方式三:Container+BoxDecoration
這種方式我們放在講解Container時來講這種方式
3.3.2. 實現圓角圖片
方式一:ClipRRect
ClipRRect用于實現圓角效果,可以設置圓角的大小。
實現代碼如下,非常簡單:
class HomeContent extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Center(
? ? ? child: ClipRRect(
? ? ? ? borderRadius: BorderRadius.circular(10),
? ? ? ? child: Image.network(
? ? ? ? ? "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
? ? ? ? ? width: 200,
? ? ? ? ? height: 200,
? ? ? ? ),
? ? ? ),
? ? );
? }
}
方式二:Container+BoxDecoration
這個也放到后面講解Container時講解
四. 表單Widget
和用戶交互的其中一種就是輸入框,比如注冊、登錄、搜索,我們收集用戶輸入的內容將其提交到服務器。
4.1. TextField的使用
4.1.1. TextField的介紹
TextField用于接收用戶的文本輸入,它提供了非常多的屬性,我們來看一下源碼:
????但是我們沒必要一個個去學習,很多時候用到某個功能時去查看是否包含某個屬性即可
const TextField({
? Key key,
? this.controller,
? this.focusNode,
? this.decoration = const InputDecoration(),
? TextInputType keyboardType,
? this.textInputAction,
? this.textCapitalization = TextCapitalization.none,
? this.style,
? this.strutStyle,
? this.textAlign = TextAlign.start,
? this.textAlignVertical,
? this.textDirection,
? this.readOnly = false,
? ToolbarOptions toolbarOptions,
? this.showCursor,
? this.autofocus = false,
? this.obscureText = false,
? this.autocorrect = true,
? this.maxLines = 1,
? this.minLines,
? this.expands = false,
? this.maxLength,
? this.maxLengthEnforced = true,
? this.onChanged,
? this.onEditingComplete,
? this.onSubmitted,
? this.inputFormatters,
? this.enabled,
? this.cursorWidth = 2.0,
? this.cursorRadius,
? this.cursorColor,
? this.keyboardAppearance,
? this.scrollPadding = const EdgeInsets.all(20.0),
? this.dragStartBehavior = DragStartBehavior.start,
? this.enableInteractiveSelection = true,
? this.onTap,
? this.buildCounter,
? this.scrollController,
? this.scrollPhysics,
})
我們來學習幾個比較常見的屬性:
????一些屬性比較簡單:keyboardType鍵盤的類型,style設置樣式,textAlign文本對齊方式,maxLength最大顯示行數等等;
decoration:用于設置輸入框相關的樣式
????icon:設置左邊顯示的圖標
????labelText:在輸入框上面顯示一個提示的文本
????hintText:顯示提示的占位文字
????border:輸入框的邊框,默認底部有一個邊框,可以通過InputBorder.none刪除掉
????filled:是否填充輸入框,默認為false
????fillColor:輸入框填充的顏色
controller:
onChanged:監聽輸入框內容的改變,傳入一個回調函數
onSubmitted:點擊鍵盤中右下角的down時,會回調的一個函數
4.1.2. TextField的樣式以及監聽
我們來演示一下TextField的decoration屬性以及監聽:
class HomeContent extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Container(
? ? ? padding: EdgeInsets.all(20),
? ? ? child: Column(
? ? ? ? mainAxisAlignment: MainAxisAlignment.center,
? ? ? ? children: <Widget>[
? ? ? ? ? TextFieldDemo()
? ? ? ? ],
? ? ? ),
? ? );
? }
}
class TextFieldDemo extends StatefulWidget {
? @override
? _TextFieldDemoState createState() => _TextFieldDemoState();
}
class _TextFieldDemoState extends State<TextFieldDemo> {
? @override
? Widget build(BuildContext context) {
? ? return TextField(
? ? ? decoration: InputDecoration(
? ? ? ? icon: Icon(Icons.people),
? ? ? ? labelText: "username",
? ? ? ? hintText: "請輸入用戶名",
? ? ? ? border: InputBorder.none,
? ? ? ? filled: true,
? ? ? ? fillColor: Colors.lightGreen
? ? ? ),
? ? ? onChanged: (value) {
? ? ? ? print("onChanged:$value");
? ? ? },
? ? ? onSubmitted: (value) {
? ? ? ? print("onSubmitted:$value");
? ? ? },
? ? );
? }
}
4.1.3. TextField的controller
????我們可以給TextField添加一個控制器(Controller),可以使用它設置文本的初始值,也可以使用它來監聽文本的改變;
????事實上,如果我們沒有為TextField提供一個Controller,那么會Flutter會默認創建一個TextEditingController的,這個結論可以通過閱讀源碼得到:
@override
? void initState() {
? ? super.initState();
? ? // ...其他代碼
? ? if (widget.controller == null)
? ? ? _controller = TextEditingController();
? }
我們也可以自己來創建一個Controller控制一些內容:
class _TextFieldDemoState extends State<TextFieldDemo> {
? final textEditingController = TextEditingController();
? @override
? void initState() {
? ? super.initState();
? ? // 1.設置默認值
? ? textEditingController.text = "Hello World";
? ? // 2.監聽文本框
? ? textEditingController.addListener(() {
? ? ? print("textEditingController:${textEditingController.text}");
? ? });
? }
? // ...省略build方法
}
4.2. Form表單的使用
????在我們開發注冊、登錄頁面時,通常會有多個表單需要同時獲取內容或者進行一些驗證,如果對每一個TextField都分別進行驗證,是一件比較麻煩的事情。
????做過前端的開發知道,我們可以將多個input標簽放在一個form里面,Flutter也借鑒了這樣的思想:我們可以通過Form對輸入框進行分組,統一進行一些操作。
4.2.1. Form表單的基本使用
Form表單也是一個Widget,可以在里面放入我們的輸入框。
但是Form表單中輸入框必須是FormField類型的
????我們查看剛剛學過的TextField是繼承自StatefulWidget,并不是一個FormField類型;
????我們可以使用TextFormField,它的使用類似于TextField,并且是繼承自FormField的;
我們通過Form的包裹,來實現一個注冊的頁面:
class FormDemo extends StatefulWidget {
? @override
? _FormDemoState createState() => _FormDemoState();
}
class _FormDemoState extends State<FormDemo> {
? @override
? Widget build(BuildContext context) {
? ? return Form(
? ? ? child: Column(
? ? ? ? mainAxisAlignment: MainAxisAlignment.center,
? ? ? ? children: <Widget>[
? ? ? ? ? TextFormField(
? ? ? ? ? ? decoration: InputDecoration(
? ? ? ? ? ? ? icon: Icon(Icons.people),
? ? ? ? ? ? ? labelText: "用戶名或手機號"
? ? ? ? ? ? ),
? ? ? ? ? ),
? ? ? ? ? TextFormField(
? ? ? ? ? ? obscureText: true,
? ? ? ? ? ? decoration: InputDecoration(
? ? ? ? ? ? ? icon: Icon(Icons.lock),
? ? ? ? ? ? ? labelText: "密碼"
? ? ? ? ? ? ),
? ? ? ? ? ),
? ? ? ? ? SizedBox(height: 16,),
? ? ? ? ? Container(
? ? ? ? ? ? width: double.infinity,
? ? ? ? ? ? height: 44,
? ? ? ? ? ? child: RaisedButton(
? ? ? ? ? ? ? color: Colors.lightGreen,
? ? ? ? ? ? ? child: Text("注 冊", style: TextStyle(fontSize: 20, color: Colors.white),),
? ? ? ? ? ? ? onPressed: () {
? ? ? ? ? ? ? ? print("點擊了注冊按鈕");
? ? ? ? ? ? ? },
? ? ? ? ? ? ),
? ? ? ? ? )
? ? ? ? ],
? ? ? ),
? ? );
? }
}
4.2.2. 保存和獲取表單數據
有了表單后,我們需要在點擊注冊時,可以同時獲取和保存表單中的數據,怎么可以做到呢?
????1、需要監聽注冊按鈕的點擊,在之前我們已經監聽的onPressed傳入的回調中來做即可。(當然,如果嵌套太多,我們待會兒可以將它抽取到一個單獨的方法中)
????2、監聽到按鈕點擊時,同時獲取用戶名和密碼的表單信息。
如何同時獲取用戶名和密碼的表單信息?
????如果我們調用Form的State對象的save方法,就會調用Form中放入的TextFormField的onSave回調:
TextFormField(
? decoration: InputDecoration(
? ? icon: Icon(Icons.people),
? ? labelText: "用戶名或手機號"
? ),
? onSaved: (value) {
? ? print("用戶名:$value");
? },
),
????但是,我們有沒有辦法可以在點擊按鈕時,拿到 Form對象 來調用它的save方法呢?
知識點:在Flutter如何可以獲取一個通過一個引用獲取一個StatefulWidget的State對象呢?
答案:通過綁定一個GlobalKey即可。
案例代碼演練:
class FormDemo extends StatefulWidget {
? @override
? _FormDemoState createState() => _FormDemoState();
}
class _FormDemoState extends State<FormDemo> {
? final registerFormKey = GlobalKey<FormState>();
? String username, password;
? void registerForm() {
? ? registerFormKey.currentState.save();
? ? print("username:$username password:$password");
? }
? @override
? Widget build(BuildContext context) {
? ? return Form(
? ? ? key: registerFormKey,
? ? ? child: Column(
? ? ? ? mainAxisAlignment: MainAxisAlignment.center,
? ? ? ? children: <Widget>[
? ? ? ? ? TextFormField(
? ? ? ? ? ? decoration: InputDecoration(
? ? ? ? ? ? ? icon: Icon(Icons.people),
? ? ? ? ? ? ? labelText: "用戶名或手機號"
? ? ? ? ? ? ),
? ? ? ? ? ? onSaved: (value) {
? ? ? ? ? ? ? this.username = value;
? ? ? ? ? ? },
? ? ? ? ? ),
? ? ? ? ? TextFormField(
? ? ? ? ? ? obscureText: true,
? ? ? ? ? ? decoration: InputDecoration(
? ? ? ? ? ? ? icon: Icon(Icons.lock),
? ? ? ? ? ? ? labelText: "密碼"
? ? ? ? ? ? ),
? ? ? ? ? ? onSaved: (value) {
? ? ? ? ? ? ? this.password = value;
? ? ? ? ? ? },
? ? ? ? ? ),
? ? ? ? ? SizedBox(height: 16,),
? ? ? ? ? Container(
? ? ? ? ? ? width: double.infinity,
? ? ? ? ? ? height: 44,
? ? ? ? ? ? child: RaisedButton(
? ? ? ? ? ? ? color: Colors.lightGreen,
? ? ? ? ? ? ? child: Text("注 冊", style: TextStyle(fontSize: 20, color: Colors.white),),
? ? ? ? ? ? ? onPressed: registerForm,
? ? ? ? ? ? ),
? ? ? ? ? )
? ? ? ? ],
? ? ? ),
? ? );
? }
}
4.2.3. 驗證填寫的表單數據
在表單中,我們可以添加驗證器,如果不符合某些特定的規則,那么給用戶一定的提示信息
比如我們需要賬號和密碼有這樣的規則:賬號和密碼都不能為空。
按照如下步驟就可以完成整個驗證過程:
????1、為TextFormField添加validator的回調函數;
????2、調用Form的State對象的validate方法,就會回調validator傳入的函數;
也可以為TextFormField添加一個屬性:autovalidate
????不需要調用validate方法,會自動驗證是否符合要求;