Flutter基本組件Basics Widget學習
1. 概述
上一篇說到,Basics Widget 並不是 Flutter 的一個專門的Widget類別,而是 Flutter 官方挑選一些開發常用的 Widget 構成的,希望我們掌握到一些最基本的開發能力。
包括:
- 文本 Text
- 按鈕 Button
- 圖片 Image
- 單選框、復選框
- 輸入框、表單
- 指示器
- Container
- …
2. 常用組件
2.1 Text
Text
用於顯示簡單樣式文本,然後可以填充一些文本顯示樣式的屬性,如下例子:
Text("Hello World", textAlign: TextAlign.left, maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 1.5);
textAlign
文本對齊方式maxLines
、overflow
maxLines 指定文本顯示的最大行數。
當文本內容超過最大行數時,overflow
指定瞭階段方式, 例如ellipsis
就是將多餘的文本用 “…” 表示textScaleFactor
代表文本相對於當前字體大小的縮放因子,想你對於去設置文本的樣式 style 屬性的 fontSize, 它是調整字體大小的一個快捷方式, 該屬性的默認值可以通過MediaQueryData.textScaleFactor
獲得, 如果沒有MediaQuery
,那麼會默認值為 1.0
2.1.1 TextStyle
TextStyle
用於指定文本樣式,例如顏色、字體、粗細、背景等,如下:
@override Widget build(BuildContext context) { return MaterialApp( title: "Flutter", home: Scaffold( appBar: AppBar( title: const Text("Basics Widget"), ), body: Text( "Hello World", style: TextStyle( color: Colors.blue, fontSize: 19.0, height: 2, fontFamily: "Courier", background: Paint()..color = Colors.yellow, decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.dashed), ))); }
效果如圖:
一些屬性:
height
行高,它不是一個絕定的值,因為具體的行高為height*fontSize
,同理行寬也是fontFamily
由於不同平臺默認支持的字體集不同,所以在手動指定字體時一定要先在不同平臺測試一下fontSize
改屬性和 Text 的textScaleFactor
都用於控制字體大小,但是有兩個區別,
①:fontSize
可以精確指定字體大小, 而 textScaleFactor 隻能縮放比例
②:textScaleFactor
主要是用於系統字體大小設置改變時,對Flutter 應用字體進行全局調整,而 fontSzie通常用於單個文本,字體大小不會跟隨系統字體大小變化
2.1.2 TextSpan
如果我們需要對Text內容不同部分按照不同的樣式顯示,就可以使用 TextSpan,代表文本的一個“片段”,看看 TextSpan的定義:
const TextSpan({ this.text, this.children, TextStyle? style, this.recognizer, MouseCursor? mouseCursor, this.onEnter, this.onExit, this.semanticsLabel, this.locale, this.spellOut, })
其中 style
和 text
代表樣式和文本內容, children是 List<InlineSpan>?
類型,也就說 TextSpan 可以包含其他 Span
reconizer
用於表示該文本片段上用於手勢進行識別處理,下面我們看一個效果圖,然後用 TextSpan
來實現:
body: const Text.rich(TextSpan(children: [ TextSpan(text: "Home: "), TextSpan( text: "https://flutterchina.club", style: TextStyle(color: Colors.blue), recognizer: _recognizer ), ]))));
這裡的代碼,用 TextSpan實現瞭一個基礎文本和一個鏈接片段
Text.rich
方法將TextSpan
添加到 Text 中,之所以可以這樣做,是因為 Text 其實就是 RichText 的一個包裝,而 RichText 是可以顯示多種多樣的 widget_reconizer
是點擊鏈接的處理器
2.1.3 DefaultTextStyle
在 Widget 樹中, 文本的樣式默認是可以被繼承的,因此如果 Widget樹的某一個節點處設置一個默認的文本樣式,那麼該節點的子樹所有的文本都會默認使用這個樣式,而 DefaultTextStyle
正是用於設置默認文本樣式的,看下面例子:
DefaultTextStyle( //1.設置文本默認樣式 style: TextStyle( color:Colors.red, fontSize: 20.0, ), textAlign: TextAlign.start, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text("hello world"), Text("I am Jack"), Text("I am Jack", style: TextStyle( inherit: false, //2.不繼承默認樣式 color: Colors.grey ), ), ], ), );
這裡的代碼首先設置瞭一個默認的樣式,字體大小為20,、顏色為紅色,然後將 DefaultTextStyle
設置給瞭子樹,這樣一來 Column 所有子孫 Text 默認都會繼承該樣式, 除非 Text 設置 inherit: false
,如下所示:
2.1.4 使用字體
在 Flutter 中可以使用自定義的字體,或者其他第三方字體, 這裡就不介紹配置瞭,具體可以看官方文檔:字體
2.2 Button
Material 組件庫提供瞭多種多樣的按鈕,他們都是直接或間接對 RawMaterialButton
的包裝定制,所以大部分屬性都一樣。另外 Marterial 庫中的按鈕都有以下共同點:
- 按下時都有水波紋
- 動畫統一用
onPressed
屬性來設置回調,當按鈕按下時會執行該回調,如果不提供回調則按鈕會處於禁用狀態,不會響應用戶點擊
2.2.1 ElevatedButton
即 帶陰影的按鈕, 默認帶有陰影和灰色背景,按下後陰影會變大,如下所示:
代碼如下:
child: ElevatedButton( child: const Text("i am ElevatedButton"), onPressed: () {}, ), ),
2.2.2 TextButton
文本按鈕,按下後會有背景色,如下圖所示:
2.2.3 OutlinedButton
默認有一個邊框,不帶陰影且背景透明,按下後,邊框顏色會變亮、同時出現背景和陰影,如下圖所示:
2.2.4 IconButton
可以點擊的 Icon, 不包含文字,點擊後會出現背景,如下所示:
代碼設置為:
IconButton( icon: Icon(Icons.eleven_mp), onPressed: () {}, ),
2.2.5 帶圖標的按鈕
上面學到的 ElevatedButton
、 TextButton
、 OutlinedButton
都有一個 icon()
的構造函數,這樣就可以代入一個圖片進去,例如設置:
ElevatedButton.icon( icon: const Icon(Icons.send), label: const Text("發送"), onPressed: () {}, ),
效果為(這裡有編碼問題,可以無視):
2.3 圖片及Icon
2.3.1 圖片
可以通過 Image
組件來加載並顯示佈局, Image
的數據源可以是
- asset
- 文件
- 內存
- 網絡
2.3.1.1 ImageProvider
ImageProvider
是抽象類,主要定義瞭圖片的獲取接口 load()
,從不同的數據源獲取圖片需要實現不同的 ImageProvider
,如 AssetImage
是實現瞭從 Asset 中加載圖片, NetworkImage
則實現瞭從網絡中加載圖片。
2.3.1.2 Image Widget
Image
組件在構建時有一個必選的 image
參數,它對應一個 ImageProvier
,下面分別演示一下如何從 asset 和 網絡中加載圖片。
1.從 asset 中加載圖片
在工程根目錄下創建一個 images 目錄,並將圖片拷貝到該目錄。
接下來在 pubspec.yaml
文件的 flutter部分 中,寫入(註意縮進):
flutter: .. assets: - assets/images/bobo.jpg
最後在代碼中使用:
Image( image: AssetImage("images/bobo.jpg"), width: 100.0, )
就能展示圖片。
(不過我這裡遇到一個問題,使用手機運行Flutter應用能正常展示圖片,但是使用 Chrome 模擬器會報錯,不知道是什麼原因造成的
2.從網絡URL中加載圖片
直接使用代碼:
Image( image: NetworkImage("https://www.wahaotu.com/uploads/allimg/201904/1554901831804910.jpg"), width: 100.0, )
可以正常展示圖片。
(不過這裡出現瞭很上面一樣的問題,但是使用官方使用的url又能正常展示圖片
2.3.1.3 Image 參數
我們可以來看下 Image
的參數,通過這些參數可以控制圖片外觀、大小、混合效果等。
const Image({ Key? key, required this.image, this.frameBuilder, this.loadingBuilder, this.errorBuilder, this.semanticLabel, this.excludeFromSemantics = false, this.width, this.height, this.color, this.opacity, this.colorBlendMode, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.centerSlice, this.matchTextDirection = false, this.gaplessPlayback = false, this.isAntiAlias = false, this.filterQuality = FilterQuality.low, })
width
、height
設置圖片寬高,當不指定寬高時,會根據當前父容器的限制盡可能的顯示其原始大小,如果隻設置其中一個,那麼另一個屬性默認會按比例縮放fit
該屬性用於用於在圖片的顯示空間和圖片本身大小不同時指定圖片的適應模式。適應模式是在BoxFit
中定義的,它是一個枚舉類型,有這些值:
①fill
:拉伸填充滿顯示空間 ,圖片會便是
②cover
:會按圖片的長寬比放大後居中填滿顯示空間,圖片不會變形,超出顯示部分會被剪裁
③contain
:圖片默認適應規則,圖片會保證圖片本身長寬比不變的情況下縮放以適應當前的顯示空間
④fitWidth
:圖片寬度會縮放到顯示空間的寬度,高度會按比例縮放,居中顯示,圖片不會變形
⑤fitHeight
:和上面的反著來- ⑥
none
:圖片沒有適應策略,會在顯示空間內顯示圖片
color
和colorBlendMode
:在圖片繪制時可以對每一個像素進行顏色混合處理,color
指定混合色,而colorBlendMode
指定混合模式下,因為用的比較少,這裡就不做實例repeat
:當圖片本身大小小於顯示空間時,指定圖片的重復規則,這裡也不做展示
2.3.2 Icon
Android中有 svg 矢量圖, 而 Flutter 中的也有,就是 Icon
,它有下面這些優點:
- 體積小
- 因為是矢量圖,所以拉伸不會影響清晰程度
- 可以通過 TextSpan 和 文本混用
- 可以引用到文本樣式
Flutter 默認實現瞭一套Icon,在 pubspec.yaml
的配置文件可以看到:
flutter: uses-material-design: true
來看下官方的示例代碼:
String icons = ""; // accessible: 0xe03e icons += "\uE03e"; // error: 0xe237 icons += " \uE237"; // fingerprint: 0xe287 icons += " \uE287"; Text( icons, style: TextStyle( fontFamily: "MaterialIcons", fontSize: 24.0, color: Colors.green, ), );
效果為:
為瞭不讓開發者碼點,Flutter 封裝瞭 IconData
和 Icon
來專門顯示字體圖片,上面的例子也可以用下面方式實現:
Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Icon(Icons.accessible,color: Colors.green), Icon(Icons.error,color: Colors.green), Icon(Icons.fingerprint,color: Colors.green), ], )
我們也可以使用自定義的字體圖標,這裡就不贅述瞭,可以看看官方示例:Icon自定義字體圖標
2.4 單選開關和復選框
Flutter 提供瞭 Material 風格的 開關Switch
和 復選框Checkbox
,它們都繼承自 StatfulWidget
,但是它們不會保存選中的狀態,選中狀態是由父組件來管理的。 當 Switch
或者 Checkbox
被點擊時,會觸發 onChanged
回調,我們可以在此回調中處理選中狀態改變邏輯,下面看官方例子:
class SwitchAndCheckBoxTestRoute extends StatefulWidget { @override _SwitchAndCheckBoxTestRouteState createState() => _SwitchAndCheckBoxTestRouteState(); } class _SwitchAndCheckBoxTestRouteState extends State<SwitchAndCheckBoxTestRoute> { bool _switchSelected=true; //維護單選開關狀態 bool _checkboxSelected=true;//維護復選框狀態 @override Widget build(BuildContext context) { return Column( children: <Widget>[ Switch( value: _switchSelected,//當前狀態 onChanged:(value){ //重新構建頁面 setState(() { _switchSelected=value; }); }, ), Checkbox( value: _checkboxSelected, activeColor: Colors.red, //選中時的顏色 onChanged:(value){ setState(() { _checkboxSelected=value!; }); } , ) ], ); } }
代碼中需要維護 Switch
和 Checkbox
的選中狀態,所以 Widget 繼承自 StatefulWidget
。 在其 build
方法中分別狀態瞭 Switch 和 Checkbox, 並且用兩個 bool 值來維護分別的選中狀態。 當按鈕被點擊時,會回調 onChanged
回調選中狀態出去,此時我們需要調用 setState()
方法來觸發 Flutter 重繪。
為什麼要這樣子設計,我的理解是:
- 將開關、復選框的狀態拋給父組件,可以更加靈活,比如在勾選時候做一些網絡請求,即異步的操作
- 一般來說,這些item是否選中,是和用戶數據關聯的,用戶數據也不可能是他們的私有狀態,所以放在一起管理更好
2.4.1 屬性
它們的屬性比較簡單,常用的有:
activeColor
:設置激活狀態的顏色tristate
: 是否為三態,僅 Checbox有,一般情況下隻有 “true” 和 “false”,表示選中和非選中,如果設置瞭tristate
後,還會增加一個 “null” 狀態
此外, Checkbox 不可設置寬高,其大小是自定義的,而 Switch 也僅能設置寬度而已。
2.5 輸入框以及表單
Flutter Material組件提供瞭 輸入款TextField
和 表單Form
2.5.1 輸入框 TextField
2.5.1.1 屬性
來看下 TextField
提供的屬性:
const TextField({ ... 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.obscuringCharacter = '•', this.obscureText = false, this.autocorrect = true, SmartDashesType? smartDashesType, SmartQuotesType? smartQuotesType, this.enableSuggestions = true, this.maxLines = 1, this.minLines, this.expands = false, this.maxLength, this.maxLengthEnforcement, this.onChanged, this.onEditingComplete, this.onSubmitted, this.onAppPrivateCommand, this.inputFormatters, this.enabled, this.cursorWidth = 2.0, this.cursorHeight, this.cursorRadius, this.cursorColor, this.selectionHeightStyle = ui.BoxHeightStyle.tight, this.selectionWidthStyle = ui.BoxWidthStyle.tight, this.keyboardAppearance, this.scrollPadding = const EdgeInsets.all(20.0), this.dragStartBehavior = DragStartBehavior.start, this.enableInteractiveSelection = true, this.selectionControls, this.onTap, this.mouseCursor, this.buildCounter, this.scrollController, this.scrollPhysics, this.autofillHints, this.restorationId, this.enableIMEPersonalizedLearning = true, })
屬性比較多,列幾個關鍵的講解:
controller
編輯框的控制器,通過它可以設置/獲取編輯框的內容、選擇編輯內容、監聽編輯文本改變事件。大多數情況下我們都需要顯示提供一個controller
來與文本框交互,如果設置的話, TextField 內部會創建一個focusNode
用於控制TextField
是否占有當前鍵盤的輸入焦點InputDecoration
用於控制 TextField 的外觀顯示,如提示文本、背景顏色、邊框等。keyboardType
用於設置該輸入框默認的鍵盤輸入類型, 有文本、電話、email等格式textInputAction
鍵盤動作按鈕圖標,就是右下角的那個圖標設置style
文本的樣式(正在編輯中的)textAlign
輸入框內編輯文本在水平方向的對齊方式autofocus
是否自動獲取焦點obscureText
是否隱藏正在編輯的文本, 比如輸入密碼的場景,文本內容會用 “•” 來代替maxLines
最大行數maxLenth
和maxLengthEnforcement
maxLenth
代表輸入框文本的最大長度,設置後輸入框右下角會顯示輸入的文本計數
maxLengthEnforcement
決定輸入文本長度超過maxLength
時如何處理,如截斷toolbarOptions
長按時出現的菜單,可以選擇 copy、cut、paste 、selectAll- onChange
輸入框內容改變的回調, 當然controller
也可以做到監聽 onEditingComplete
、onSubmitted
作用一樣,都是在輸入完成時觸發,比如點擊瞭鍵盤的 完成鍵、搜索鍵不同的是兩個回調簽名不同inputFormatters
指定輸入格式,當用戶輸入內容改變時,會根據指定格式來校驗enable
如果為false, 則輸入框會被禁用cursorWidth
、cursorRadius
、cursorColor
分別表示自定義輸入框光標寬度、圓角和顏色
一個簡單的設置代碼如下:
Column(children: const <Widget>[ TextField( autofocus: true, decoration: InputDecoration( labelText: "用戶名", hintText: "請輸入用戶名或密碼", prefixIcon: Icon(Icons.person) ), ), TextField( decoration: InputDecoration( labelText: "密碼", hintText: "請輸入密碼", prefixIcon: Icon(Icons.lock) ), obscureText: true, ) ]),
2.5.1.2 通過 controller 獲取輸入內容
我們可以通過 onChange 拿到內容。 當然也可以使用 controller 來獲取
步驟為:
定義一個 controller
:
final TextEditingController _tfController = TextEditingController();
然後在 TextFiled 中傳入這個 controller
TextField( controller: _tfController, ... )
最後就可以通過 : print(_tfController.text)
來獲得輸入框的內容
2.5.1.3 通過 controller 監聽文本內容變化
可以通過 onChange
來監聽文本, controller 可以通過設置監聽器來監聽文本,如下:
@override void initState() { super.initState(); _tfController.addListener(() { print(_tfController.text); }); }
controller 的功能更多,除瞭監聽文本,還可以設置默認值、選擇文本等,這裡就不多贅述。
2.5.1.4 控制焦點
可以使用 FocusNode
和 FocusScopeNode
來控制焦點。默認情況下是由 FocusScope
來管理,可以在這個范圍內通過 FocusScopeNode
在輸入框之間移動焦點、設置默認焦點。
我們可以通過下面代碼來獲取當前 Widget 樹中默認的 FocusScopeNode:
focusScopeNode = FocusScope.of(context)
拿到句柄後,可以使用下面代碼來獲取焦點:
focusScopeNode.requestFocus(focusNode);
其中 focucsNode 是為 TextField 創建的 FocusNode, 這個操作可以讓該 TextField 獲取焦點。 調用 focusNode.unfocus()
可以取消焦點。
2.5.1.5 監聽焦點狀態改變事件
通過 FocusNode
可以監聽焦點改變的事件:
focusNode.addListener((){ print(focusNode.hasFocus); })
true為獲取焦點,false為失去焦點
2.5.2 表單
表單Form
對輸入框進行分組和統一操作。 就像 Android 的原生組件 RadioGroup 之於 RadioButton 一樣, Form
可以管理內容校驗、輸入框重置等。
Form 繼承自 StatefulWidget
,其狀態管理在 FormState
裡面,來看看 From 的定義:
class Form extends StatefulWidget { const Form({ Key? key, required this.child, @Deprecated( 'Use autovalidateMode parameter which provides more specific ' 'behavior related to auto validation. ' 'This feature was deprecated after v1.19.0.', ) this.autovalidate = false, this.onWillPop, this.onChanged, AutovalidateMode? autovalidateMode, }) ...
autovalidate
是否自動校驗輸入內容,當為true時,每一個 FormField 內容發生變化時都會校驗合法性,並直接顯示錯誤信息,否則就需要通過調用FormState.validate()
來手動校驗
v1.19 已經廢棄瞭,改成使用AutovalidateMode
autovalidateMode
自動校驗模式,是上面的替換,它有三個枚舉值:
①disable
:當 FormField 內容改變時不做校驗
②always
:即使用戶沒有用戶交互也要校驗合法性
③onUserInteraction
:隻有在用戶交互時才會去校驗合法性onWillPop
決定Form
所在的路由是否可以直接返回。該回調返回一個Future
對象,如果 Future 的最終結果是 false,則當前路由不會返回,如果為 true,則會返回到上一個路由。
這個屬性通常是用於攔截返回按鈕的onChanged
Form 的任意一個 FormField 內容發生改變時就會調用該方法
2.5.2.1 FormField
Form 的子孫元素是 FormField
類型,FormField 是一個抽象類,定義瞭幾個屬性, FormState 內部通過他們來完成操作, FormField 部分定義如下:
const FormField({ Key? key, required this.builder, this.onSaved, this.validator, this.initialValue, @Deprecated( 'Use autovalidateMode parameter which provides more specific ' 'behavior related to auto validation. ' 'This feature was deprecated after v1.19.0.', ) this.autovalidate = false, this.enabled = true, AutovalidateMode? autovalidateMode, this.restorationId, })
onSaved
保存時的回調validator
驗證合法性的回調initValue
初始值
為瞭方便使用, Flutter 提供瞭一個 TextFormFild
組件,繼承自 FormField
類,還包裝瞭 TextFileld ,可以直接當成 Form 的 FormField 來使用, 相當於用 Form 來管理 TextField
2.5.2.2 FormState
Form 表單的狀態類就是 FormState
, 可以通過 Form.of
或者 GlobalKey
獲得,通過獲得它來對 Form 的子孫 FormField 進行統一操作。
FormState 常用的三個方法:
FormState.validate()
:調用此方法後, 會調用Form
子孫FormField.validate()
回調,如果有一個檢驗失敗,那麼會返回 false,這樣所有校驗失敗的 Widget 都會給出錯誤提示FormState.save()
:調用此方法後,會調用 子孫的FormFild.save()
回調,用於保存表單內容FormState.reset()
: 會將子孫 FormField 的內容清空
2.5.2.3 示例
我們做一個用戶登錄的程序,再點擊登錄前需要做到輸入檢查:
- 用戶名不能為空,如果為空則提示“用戶名不能為空”
- 密碼不能小於6位,如果小於6位則提示 “密碼不能少於6位”
代碼如下:
import 'package:flutter/material.dart'; class FormTestRoute extends StatefulWidget { const FormTestRoute({Key? key}) : super(key: key); @override State<StatefulWidget> createState() => _FormTestRouteState(); } class _FormTestRouteState extends State<FormTestRoute> { final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final GlobalKey _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Form demo'), ), body: Form( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ TextFormField( autofocus: true, controller: _usernameController, decoration: const InputDecoration( labelText: "username", hintText: "username or email", icon: Icon(Icons.person)), validator: (username) { return username!.trim().isNotEmpty ? null : "username cannot empty"; }, ), TextFormField( controller: _passwordController, decoration: const InputDecoration( labelText: "password", hintText: "please input your password", icon: Icon(Icons.lock)), obscureText: true, validator: (pwd) { return pwd!.trim().length >= 6 ? null : "password digit cannot less than 6!"; }, ), // login button Padding( padding: const EdgeInsets.only(top: 28.0), child: Row( children: [ Expanded( child: ElevatedButton( onPressed: () { if ((_formKey.currentState as FormState).validate()) { print("Loing success"); } }, child: const Padding( padding: EdgeInsets.all(16.0), child: Text("Login"), ), )) ], ), ) ], ))); } }
效果如下圖所示:
以上所述是小編給大傢介紹的Flutter基本組件Basics Widget學習,希望對大傢有所幫助。在此也非常感謝大傢對WalkonNet網站的支持!