今天和大家一起來看一些UI組件:
好的,那我們就開始吧!
要直接取用Flutter 內預設提供的material design元件,需要先在pubspec.yaml
加入uses-material-design:true
flutter:
uses-material-design: true
接著,建立Icon 實體,並在IconData位置填入想使用的Icon名稱(從Icons中選取)
Icon(
Icons.favorite, // iconData
color: Colors.pink,
size: 24.0,
semanticLabel: 'Text to announce in accessibility modes',
),
textInput可以讓使用者輸入訊息
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter a search term',
),
),
在手機上點選TextInput
會觸發鍵盤,可以使用KeyboardType來變更鍵盤型態,例如
keyboardType: TextInputType.number
可以將鍵盤變更為數字鍵盤。 (若在ios simulator上沒有出現鍵盤請用command
+K
啟用模擬器鍵盤)
透過 maxLine
和minLine
可以設定最大和最小行數
我們也可以建立一個controller
來監聽TextInput
輸入文字的變化(輸入或刪除),直接用controller來改變輸入框內容controller.text = 'DESIRED_VALUE'
設置onChanged
可以指定文字產生變化時要執行的動作,但是注意直接使用controller
改動文字並不會觸發onChanged
。
onSubmit
則是可以在使用者按下手機鍵盤上送出/電腦Enter鍵時,觸發送出後的行為
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController(); // 建立controller
@override
void dispose() {
_controller.dispose(); // 在離開頁面、Widget被銷毀時,要記得將controller註銷
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('What number comes next in the sequence?'),
const Text('1, 1, 2, 3, 5, 8...?'),
TextField(
controller: _controller, // 指派Controller給TextField
onChanged: (String value) async { // 隨時監聽輸入文字的變化
if (value != '13') {
return;
}
await showDialog<void>( // 若當下的輸入值是13,會跳出視窗提示
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('That is correct!'),
content: const Text('13 is the right answer.'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
},
onSubmitted: (String value) async { // 觸發送出行為,會把文字長度印出來
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Thanks!'),
content: Text(
'You typed "$value", which has length ${value.characters.length}.'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
},
),
],
),
);
}
}
TextField
和FormField
功能的整合,可以直接在Form
之中使用。額外包含了一些表單相關功能如:驗證規則(validator)、儲存時的行為(onSaved)
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter your username',
validator: (value) {
if (value == null || value.trim() == '') {
return '此欄位為必填';
}
return null;
},
onSaved: (value) {
print('onSaved!')
},
),
),
SingleChildScrollView
,所以ListView
包括了捲軸滑動的行為builder
: 建立一連串連續排列的區塊構成的列表separated
: 建立一連串區塊且中間有分隔線的列表custom
: 建立自訂義的子組件itemCount
: 告訴ListView列表項目的數量itemBuilder
: 定義項目的UI和資料,交由ListView繪製final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
);
在主頁面將favorite按鍵內容改為Icon.favorite
構成的愛心圖示
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.white24),
onPressed: () {
print('add to favorite');
},
child: Icon(
Icons.favorite,
color: Colors.pink[200],
)),
在主頁面最下方加入TextInput,並加入Decorator
讓它變成白色輸入框,外層包上Padding
const Padding(
padding: EdgeInsets.all(10.0),
child: TextField(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.all(10),
border: InputBorder.none,
),
keyboardType: TextInputType.number,
maxLines: 8,
minLines: 3,
),
),
在TextInput加入controller,以便取得TextInput內容
TextField(
controller: _controller,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.all(10),
border: InputBorder.none,
),
maxLines: 5,
minLines: 3,
),
在TextInput右方加入「儲存」按鈕,按下時會將TextInput內的內容改以Text顯示,按鈕變成「編輯」
// 加在MyHomeState class之中
final TextEditingController _controller = TextEditingController();
NoteType _noteType = NoteType.editable;
// 加在build method的圖片說明文字Text的下面
_noteType == NoteType.text
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(10.0),
child: Text(_controller.text))),
Container(
width: 100,
padding: const EdgeInsets.all(10),
child: OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: const Size(50, 50),
),
onPressed: () {
setState(() {
_noteType = NoteType.editable;
});
},
child: const Icon(Icons.edit)))
],
)
: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
controller: _controller,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.all(10),
border: InputBorder.none,
),
maxLines: 5,
minLines: 3,
),
),
),
Container(
width: 100,
padding: const EdgeInsets.all(10),
child: OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: const Size(50, 50),
),
onPressed: () {
setState(() {
_noteType = NoteType.text;
});
},
child: const Icon(
Icons.save,
),
),
)
],
)
],
),
),
在FavoritePage
加入ListView
。製作列表項目:以Card
顯示,並在Card
上加入需要的標題。
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
padding: const EdgeInsets.all(5.0),
child: const Expanded(
child: Card(
elevation: 5.0,
child: Center(
child: Text(
'I am Image Title',
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
),
)),
),
);
},
itemCount: 2,
),
Day15
相關commit今天介紹了三的Widget: Icon
, TextInput
, ListView
都是很常使用的組件喔!
明天一起來看看Custom Widget,如何將一些組件抽出來形成一個自定義的組件,以應付後面各種愈來愈複雜的需求。