Flutter 初探(二):基礎類Widgets和佈局類Widgets上手
- 按鈕
- 圖片及ICON元件
- 單選開關和複選框
- 輸入框及表單
- 表單驗證
佈局類Widget包括:
Row Flex Wrap Stack
路由:
routes: { "new_page": (context) => NewRoute(), // 使用新路由來學習 文字及樣式 /* 開始 基礎Widget 的學習: */ // 新增一個 基礎Widget總頁面: "base_widget_page": (context) => BaseWidget(), "text_page": (context) => NewText(), // 學習 按鈕 "button_page": (context) => NewButton(), //學習圖片元件及ICON 'image_page': (context) => NewImage(), // 學習單選開關和複選框 "switch_and_checkbox": (context) => NewSwitchAndCheckBox(), // 學習輸入框及表單 "text_field_page": (context) => NewTextField(), // Form表單 "form_page": (context) => FormTestRoute(), /* 開始頁面 佈局類Widget 的學習: */ // 新增一個頁面佈局總頁面 "row_column_page": (context) => RowAndColumnRoute(), // 學習縱向Row佈局 "row_page": (context) => NewRow(), // 學習Flex彈性佈局 "flex_page": (context) => NewFlex(), // 學習流式佈局Wrap "wrap_page": (context) => NewWrap(), // 學習層疊佈局Stack "stack_page": (context) => NewStack(), }, 複製程式碼
詳細使用:
// 匯入Material UI元件庫 import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; import 'package:flutter/cupertino.dart'; // 應用程式入口,runApp功能即啟動Flutter應用,接收的引數為Widget引數 void main() => runApp(new MyApp()); // 繼承一個無狀態Widget元件,MyApp類代表Flutter應用 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // MaterialApp 設定應用名稱、主題、語言、首頁及路由列表等,其本身也是個Widget元件 return new MaterialApp( // 應用名稱 title: 'Flutter Demos', // 應用主題 theme: new ThemeData( // 藍色主題 primarySwatch: Colors.blue, ), // 使用命名路由管理route,首先註冊路由表 routes: { "new_page": (context) => NewRoute(), // 使用新路由來學習 文字及樣式 /* 開始 基礎Widget 的學習: */ // 新增一個 基礎Widget總頁面: "base_widget_page": (context) => BaseWidget(), "text_page": (context) => NewText(), // 學習 按鈕 "button_page": (context) => NewButton(), //學習圖片元件及ICON 'image_page': (context) => NewImage(), // 學習單選開關和複選框 "switch_and_checkbox": (context) => NewSwitchAndCheckBox(), // 學習輸入框及表單 "text_field_page": (context) => NewTextField(), // Form表單 "form_page": (context) => FormTestRoute(), /* 開始頁面 佈局類Widget 的學習: */ // 新增一個頁面佈局總頁面 "row_column_page": (context) => RowAndColumnRoute(), // 學習縱向Row佈局 "row_page": (context) => NewRow(), // 學習Flex彈性佈局 "flex_page": (context) => NewFlex(), // 學習流式佈局Wrap "wrap_page": (context) => NewWrap(), // 學習層疊佈局Stack "stack_page": (context) => NewStack(), }, // 應用首頁路由 home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } } // 即繼承一個有狀態Widget元件 class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override // 對應該類的狀態類 _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { // “+” 次數記錄 int _counter = 0; // 設定狀態的自增函式 void _incrementCounter() { setState(() { _counter++; }); } @override // 構建UI介面的邏輯build方法 Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: Wrap( spacing: 8.0, // 主軸(水平)方向間距 runSpacing: 4.0, // 縱軸(垂直)方向間距 alignment: WrapAlignment.center, //沿主軸方向居中 children: <Widget>[ new Text( 'You have pushed the button this many times:', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), new Text('''基礎顯示'''), // 新增一個按鈕元件,用於跳轉新路由(新頁面) // 跳轉至新路由的按鈕 FlatButton( child: Text('open new route'), textColor: Colors.blue, // 導航至新路由 onPressed: () { // 推至路由棧,路由管理Widget元件,通過棧來管理一個路由widget集合 即先進先出管理原則,這樣好理解多了 // Navigator.push(context, //new MaterialPageRoute(builder: (context){ //return new NewRoute(); // // 通過路由名稱也可以開啟新的路由頁 //}, //) // ); Navigator.pushNamed(context, "new_page"); }), // 新增文字及樣式路由按鈕 RaisedButton( child: Text('基礎Widgets'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, "base_widget_page"), ), RaisedButton( child: Text('佈局學習'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'row_column_page'), ), // 通過english_words包隨機顯示一個英文單詞 new RandomWordsWidget(), // 列印文字的元件 Echo( text: "大致學習過程", ) ], ), // 右下角的按鈕 floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ) // This trailing comma makes auto-formatting nicer for build methods. ); } } // 根據路由管理,嘗試新的頁面構建: class NewRoute extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('This is new route.')), body: Center(child: Text('nice route.'))); } } // 基礎Weight學習 class BaseWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('基礎Widgets')), body: new Column( children: <Widget>[ FlatButton( child: Text('文字及樣式', style: TextStyle( background: new Paint()..color = Colors.yellow, )), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, "text_page"), ), // 新增按鈕路由按鈕,該button有陰影效果 RaisedButton( child: Text('按鈕'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, "button_page"), ), RaisedButton( child: Text('圖片及ICON'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, "image_page"), ), RaisedButton( child: Text('單選開關及複選框'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, "switch_and_checkbox"), ), RaisedButton( child: Text("輸入框及表單"), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'text_field_page'), ), RaisedButton( child: Text('表單Form'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'form_page'), ), ], ), ); } } // 文字及樣式 class NewText extends StatelessWidget { @override Widget build(BuildContext context) { var scaffold = new Scaffold( appBar: AppBar( title: Text('文字及樣式'), ), body: new Center( child: Column( // mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( "文字居中對齊." * 7, // 文字居中對齊 textAlign: TextAlign.center, ), Text( "文字顯示的最大行數." * 4, // 文字顯示的最大行數 maxLines: 1, // 指定若文字超過一行的戒斷方式,ellipsis 表示截斷為省略號 overflow: TextOverflow.ellipsis, ), Text( "字型大小的縮放因子,類似於大小調節", textScaleFactor: 1.5, ), Text("TextStyle用於指定文字顯示的樣式", style: TextStyle( color: Colors.blue, fontSize: 18, height: 1.2, fontFamily: "Courier", background: new Paint()..color = Colors.yellow, decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.dashed, )), // 對不同片段設定不同的樣式,可以不斷巢狀設定 Text.rich(TextSpan(children: [ TextSpan( text: 'Home ', ), TextSpan( text: "https://flutterchina.club", style: TextStyle( color: Colors.blue, ), // recognizer: _tapRecognizer, ), ])), // 設定預設樣式,這種樣式是可以繼承的 DefaultTextStyle( style: TextStyle( color: Colors.red, fontSize: 20.0, ), textAlign: TextAlign.start, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text("hello word!"), Text('多行測試'), // 嘗試再次編輯文字樣式,檢視是否會覆蓋預設樣式 Text( '嘗試再次編輯文字樣式,檢視是否會覆蓋預設樣式,結果:成功覆蓋預設設定的樣式', style: TextStyle( color: Colors.blue, fontSize: 14.0, ), ) ], ), ), ], )), ); return scaffold; } } // 按鈕 class NewButton extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('按鈕')), body: Center( child: Column( children: <Widget>[ RaisedButton( child: Text('RaiseButton'), onPressed: () {}, ), FlatButton( child: Text("FlatButton"), onPressed: () {}, ), OutlineButton( child: Text("OutlineButton"), onPressed: () {}, ), // 可點選的Icon IconButton( icon: Icon(Icons.thumb_up), onPressed: () {}, ), // 設定按鈕樣式,陰影風格真的不錯。 RaisedButton( child: Text("按鈕樣式"), color: Colors.blue, highlightColor: Colors.blue[700], colorBrightness: Brightness.dark, splashColor: Colors.grey, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20.0)), onPressed: () {}, ), ], ), ), ); } } // 圖片和Icon class NewImage extends StatelessWidget { // 預定義一組字型圖示: @override Widget build(BuildContext context) { String icons = ""; // accessible: or 0xE914 or E914 icons += "\uE914"; // error: or 0xE000 or E000 icons += " \uE000"; // fingerprint: or 0xE90D or E90D icons += " \uE90D"; return new Scaffold( appBar: AppBar(title: Text('圖片及ICON')), body: Center( child: Column( children: <Widget>[ // 本地圖片組建 Image.asset( "images/avatar.png", width: 100.0, ), // 顯示網路圖片 Image.network( "https://avatars1.githubusercontent.com/u/20992063?s=460&v=4", width: 200.0, ), //簡單設定圖片屬性 Image( image: NetworkImage( 'https://avatars1.githubusercontent.com/u/20992063?s=460&v=4'), width: 100, height: 100.0, color: Colors.blue, colorBlendMode: BlendMode.difference, // 圖片空間小於顯示空間,設定圖片顯示的重複規則 repeat: ImageRepeat.repeatY, ), // 字型圖示的使用 Text( icons, style: TextStyle( fontFamily: "MaterialIcons", fontSize: 24.0, color: Colors.green), ), ], ), ), ); } } // 單選開關和複選框 class NewSwitchAndCheckBox extends StatefulWidget { @override _NewSwitchAndCheckBoxState createState() => new _NewSwitchAndCheckBoxState(); } class _NewSwitchAndCheckBoxState extends State<NewSwitchAndCheckBox> { bool _switchSelected = true; bool _checkboxSelected = true; @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('單選開關和複選框')), body: new Center( child: Column( children: <Widget>[ Switch( value: _switchSelected, //當前狀態 onChanged: (value) { //重新構建頁面 setState(() { _switchSelected = value; }); }, ), Checkbox( value: _checkboxSelected, // 選中時的顏色 activeColor: Colors.red, onChanged: (value) { setState(() { _checkboxSelected = value; }); }, ), ], )), ); } } // 簡單輸入 class NewTextField extends StatelessWidget { @override Widget build(BuildContext context) { // 定義一個controller,可以獲取內容,也可以監聽文字變化 TextEditingController _selectionControlle = new TextEditingController(); _selectionControlle.text = "Hello world"; _selectionControlle.selection = TextSelection( baseOffset: 2, extentOffset: _selectionControlle.text.length, ); return new Scaffold( appBar: AppBar(title: Text('輸入框及表單')), body: new Center( child: new Column( children: <Widget>[ // 登陸輸入框 TextField( autofocus: true, decoration: InputDecoration( labelText: "使用者名稱", hintText: "使用者名稱或郵箱", prefixIcon: Icon(Icons.person), ), // 可以通過onChanged獲取輸入的內容,也可以監聽文字變化 // onChanged: (context){ //print(context); // }, // 比如通過controller獲取輸入的內容,監聽文字變化,除了這兩種功能,還可以設定預設值、選擇文字 controller: _selectionControlle, ), TextField( decoration: InputDecoration( labelText: "密碼", hintText: "您的登陸密碼", prefixIcon: Icon(Icons.lock), ), obscureText: true, ), ], ), ), ); } } // 表單Form測試 class FormTestRoute extends StatefulWidget { @override _FormTestRouteState createState() => new _FormTestRouteState(); } class _FormTestRouteState extends State<FormTestRoute> { TextEditingController _unameController = new TextEditingController(); TextEditingController _pwdController = new TextEditingController(); GlobalKey _formKey = new GlobalKey<FormState>(); @override Widget build(BuildContext context) { // 此處不能使用PageScaffold 嘗試普通佈局 // return PageScaffold( // ...... // ); return new Scaffold( appBar: AppBar(title: Text('表單Form')), body: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), child: Form( key: _formKey, autovalidate: true, child: Column( children: <Widget>[ TextFormField( autofocus: true, controller: _unameController, decoration: InputDecoration( labelText: "使用者名稱", hintText: '使用者名稱或郵箱', icon: Icon(Icons.person), ), // 校驗使用者名稱 validator: (v) { return v.trim().length > 0 ? null : "使用者名稱不能為空"; }, ), TextFormField( controller: _pwdController, decoration: InputDecoration( labelText: "密碼", hintText: '您的登陸密碼', icon: Icon(Icons.lock), ), obscureText: true, validator: (v) { return v.trim().length > 5 ? null : "密碼不能少於6位"; }, ), // 登陸按鈕 Padding( padding: const EdgeInsets.only(top: 28.0), child: Row( children: <Widget>[ Expanded( child: RaisedButton( padding: EdgeInsets.all(15.0), child: Text('登陸'), color: Theme.of(context).primaryColor, textColor: Colors.white, onPressed: () { if ((_formKey.currentState as FormState) .validate()) { //驗證通過提交資料 } }, ), ), ], ), ), ], )), ), ); } } // 佈局類Widgets 學習 class RowAndColumnRoute extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text("佈局類Widgets 學習")), body: Column( children: <Widget>[ RaisedButton( child: Text('ROw縱向佈局'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'row_page'), ), RaisedButton( child: Text('Flex彈性佈局'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'flex_page'), ), RaisedButton( child: Text('Wrap流式佈局'), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'wrap_page'), ), RaisedButton( child: Text("Stack層疊佈局"), textColor: Colors.blue, onPressed: () => Navigator.pushNamed(context, 'stack_page'), ), ], )); } } // Row縱向佈局 class NewRow extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('縱軸Row')), body: Column( //測試Row對齊方式,排除Column預設居中對齊的干擾 crossAxisAlignment: CrossAxisAlignment.start, // Widge子陣列 children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[Text('水平方向對齊方式.'), Text('現在是居中對齊.')], ), Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(" hello world "), Text(" I am Jack "), ], ), Row( mainAxisAlignment: MainAxisAlignment.end, textDirection: TextDirection.rtl, children: <Widget>[ Text(" hello world "), Text(" I am Jack "), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, verticalDirection: VerticalDirection.up, children: <Widget>[ Text( " hello world ", style: TextStyle(fontSize: 30.0), ), Text(" I am Jack "), ], ), ], )); } } // 彈性佈局Flex class NewFlex extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('Flex彈性佈局')), body: new Column( children: <Widget>[ Flex( //Flex的兩個子widget按1:2來佔據水平空間 direction: Axis.horizontal, children: <Widget>[ Expanded( flex: 1, child: Container( height: 30.0, color: Colors.red, )), Expanded( flex: 2, child: Container( height: 30.0, color: Colors.green, )), ], ), Padding( padding: const EdgeInsets.only(top: 20), child: SizedBox( height: 100.0, child: Flex( //Flex的三個子widget,在 垂直 方向按2:1:1來佔用100畫素的空間 direction: Axis.vertical, children: <Widget>[ Expanded( flex: 1, child: Container( height: 30.0, color: Colors.red, ), ), // 一個Flex的簡單包裝 Spacer( flex: 1, ), Expanded( flex: 1, child: Container( height: 30.0, color: Colors.green, ), ), ], ), ), ), ], )); } } // 流式佈局Wrap、Flow class NewWrap extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('流式佈局Wrap')), body: Wrap( // 主軸水平方向間距 spacing: 8.0, // 縱軸垂直方向間距 runSpacing: 4.0, // 沿主軸方向居中 alignment: WrapAlignment.center, children: <Widget>[ new Chip( avatar: new CircleAvatar( backgroundColor: Colors.blue, child: Text('A')), label: new Text('Hamilton')), new Chip( avatar: new CircleAvatar( backgroundColor: Colors.blue, child: Text('M')), label: new Text('Lafayette'), ), new Chip( avatar: new CircleAvatar( backgroundColor: Colors.blue, child: Text('H')), label: new Text('Mulligan'), ), new Chip( avatar: new CircleAvatar( backgroundColor: Colors.blue, child: Text('J')), label: new Text('Laurens'), ), ], ), ); } } // 層疊佈局 Stack、Positioned class NewStack extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar(title: Text('層疊佈局Stack')), body: new ConstrainedBox( constraints: BoxConstraints.expand(), child: Stack( // 指定未定位或部分定位widget的對齊方式 alignment: Alignment.center, children: <Widget>[ Container( child: Text('Hello world', style: TextStyle( color: Colors.white, )), color: Colors.red, ), Positioned( left: 18.0, child: Text("I'm Jack"), ), Positioned( top: 18.0, child: Text("Your Friend"), ), ], )), ); } } class CuoertinoTestRoute extends StatelessWidget { @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text("Cupertino Demo"), ), child: Center( child: CupertinoButton( color: CupertinoColors.activeBlue, child: Text("Press"), onPressed: () {}, ), ), ); } } class RandomWordsWidget extends StatelessWidget { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new Padding( padding: const EdgeInsets.all(8.0), child: new Text(wordPair.toString())); } } class Echo extends StatelessWidget { const Echo({ Key key, @required this.text, this.backgroundColor: Colors.grey, }) : super(key: key); final String text; final Color backgroundColor; @override Widget build(BuildContext context) { return Center( child: Container( color: backgroundColor, child: Text(text), )); } } 複製程式碼
以下是Demo的總頁面和分頁面及效果圖:













Summary
逐漸構建,思考,記錄完善學習過程,希望後續能夠將整個學習的過程整合為一個app,這樣既能學習到知識,又能夠記錄下來。