Flutter学习之认知基础组件
一、前言
前一天,學習了Dart語法,對Dart的語法和特性有了更深一步的了解。今天,來學習Flutter的基礎(chǔ)控件,身為Android開發(fā)者都知道,一開始入坑Android就要熟悉學習其控件,如:TextView,ImageView,Button,ListView,RecycleView等。為什么要學習呢?因為平時的開發(fā)都離不開這些控件,UI的呈現(xiàn)都是有這些控件組成的,因此,其重要性就不用說了。對于Flutter來講,基礎(chǔ)控件(widget)就更加重要了。Flutter和Android有所不一樣,Android布局包含布局(RelativeLayout,LinearLayout,ConstrainLayou)和組件。Flutter的一切都是Widget,包括最頂層布局也是Widget,一個頁面有很多很多的Widget組合而成,Widget也稱為裝飾品,窗口小部件。
二、Widget簡介
在Flutter里,UI控件就是Widget,Widget根據(jù)不同的功能可以分為結(jié)構(gòu)元素(如按鈕或菜單),文本樣式(字體或者顏色方案),布局屬性(如填充,對齊,居中),可以這么理解,一個flutter的頁面是有一棵樹型的Widget組成,包括根節(jié)點,樹枝和樹葉,全都是Widget,只是Widget嵌套Widget,那就可以用下面這張圖來表示:
在Flutter中,Widget是一切的基礎(chǔ),作為響應(yīng)式渲染,屬于MVVM的實現(xiàn)機制,通過修改數(shù)據(jù),再用setState設(shè)置數(shù)據(jù),Flutter會自動通過綁定的數(shù)據(jù)更新Widget,所以在平時開發(fā)中,開發(fā)者需要的就是實現(xiàn)Widget界面,和數(shù)據(jù)綁定起來。在平時,用的最多就是StatelessWidget和StatefulWidget這兩種Widget,StatelessWidget表示無狀態(tài)的,StatefulWidget表示有狀態(tài)的。這里怎么理解呢?在Flutter中每個頁面都是一幀,無狀態(tài)就是保持在那一幀,總而言之就是不能跟用戶交互,當有狀態(tài)的Widget當數(shù)據(jù)更新時,其實是繪制了新的Widget,也就是UI發(fā)生了變化,只是State實現(xiàn)了跨幀數(shù)據(jù)同步保存。這里給大家說下,在Android Studio看源碼的兩個工具: 左邊一欄Structure結(jié)構(gòu)(看當前文件,win下的快捷鍵是(Alt+7))和右邊Hierarchy繼承關(guān)系(看當前類,win下快捷鍵是F4)都可以幫助你閱讀源碼。因為StatelessWidget和StatefulWidget用的最多,現(xiàn)在只需要用到這兩個,就先學習這兩個Widget。1.StatelessWidget
源碼StatelessWidget只有三個方法:
- const StatelessWidget({Key key}):super(key:key):初始化子類的[key]。這個key類是Widget、Element、SemanticsNode的唯一標識符,是用來控制Widget數(shù)中替換Widget的時候使用的。
- StatelessElement createElement():創(chuàng)建一個[StatelessElement]來管理這個小部件在樹中的位置,源碼解釋:子類重寫此方法是不常見的,那這個方法也不用管,只需要知道這個方法用來管理自身在Widget樹中的位置。
- Widget build(BuildContext context):描述這部件呈現(xiàn)用戶界面的部分。對于StatelessWidget,當Widget第一次插入到樹中,或者父節(jié)點更改了配置和所依賴的[InheritedWidget]改變,都會被重新調(diào)用。
這里說下如何啟動一個Flutter應(yīng)用,并使用Flutter框架:
import 'package:flutter/material.dart'; void main() {return runApp(Widget app); } 復(fù)制代碼其實就是在main()函數(shù)中調(diào)用runApp函數(shù)。下面直接直接上例子,繼承StatelessWidget,通過build方法返回一個控件:
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial風格的小控件 void main(){//運行程序runApp(MyApp(null)); } //繼承無狀態(tài)的StatelessWidget 使程序自身變?yōu)閃iget class MyApp extends StatelessWidget{//要顯示的內(nèi)容final String text;//數(shù)據(jù)內(nèi)容可以通過構(gòu)造方法傳遞進來MyApp(this.text);//重寫build方法 返回你需要的控件Widget build(BuildContext context) {// TODO: implement buildreturn Container(//紅色背景color: Colors.red,//高度 現(xiàn)在沒用 會撐滿整個屏幕height: 200,//寬度 運行效果會撐滿整個屏幕width: 200,//內(nèi)容居中alignment: Alignment.center,//Text控件child: new Text(//Dart語法中 ?? 表示如果text為空,就會返回??號的內(nèi)容text ?? "my name is Knight",textDirection: TextDirection.ltr,//需要加上這句不然報 RichText widgets require a Directionality widget ancestor.),);} } 復(fù)制代碼Widget和Widget之間通過child進行嵌套,有些Widget只能有一個child。就像上面的Container,有些Widget可以有多個child,像Colum布局。上面例子根布局是Container,Container嵌套了Text。
2.StatefulWidget
什么是有狀態(tài)的控件呢?狀態(tài)是在創(chuàng)建控件可以同步讀取信息,并且在控件的生命周期內(nèi)可以改變,當控件狀態(tài)發(fā)生改變時使用State.setState來及時更新,源碼也是只有三個方法:
前兩個方法和StatelessWidget一樣的,而createState()這個方法源碼注釋是:在Widget樹中給定的位置創(chuàng)建此可變狀態(tài)的小部件,子類應(yīng)該重寫此方法返回新建的,關(guān)聯(lián)子類的實例。當調(diào)用一個StatefulWidget,框架就會調(diào)用createState這個方法,當一個StatefulWidget從Widget樹中移除,再次插入樹中,那么會再次調(diào)用createState來創(chuàng)建一個新的State對象,這樣做簡化了State對象的生命周期。 需要創(chuàng)建管理的是主要是State,StatefulWidget用起來麻煩一些,他需要一個State,例子如下: //繼承StatefulWidget class StateWidget extends StatefulWidget{State createState(){return _StateWidget();} }class _StateWidget extends State<StateWidget>{//重寫build方法Widget build(BuildContext context){} } 復(fù)制代碼簡單觀察上面代碼,大致流程還是和StatelessWidget一樣的,build方法照樣返回Widget,不過在StatefulWidget將這個方法放在createState里面。這里細想一下,也知道為什么要這樣做,因為當狀態(tài)改變,就會回調(diào)createState方法,重新調(diào)用build方法重新創(chuàng)建UI,下面通過每兩秒改變UI這個例子來加深理解:
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial風格的小控件 import 'dart:async';//記得導(dǎo)庫 void main(){//運行程序runApp(StateWidget()); } //控件繼承State class _StateWidget extends State<StateWidget>{int Number = 0;String text;//構(gòu)造函數(shù)_StateWidget(this.text);void initState(){//初始化,這個函數(shù)在控件的生命周期內(nèi)調(diào)用一次super.initState();print("進入initState");//3秒后改變text的內(nèi)容new Future.delayed(const Duration(seconds: 3),(){setState(() {Number++;text = "已經(jīng)改變數(shù)值,數(shù)值現(xiàn)在是$Number";});});}void dispose(){//銷毀super.dispose();print('銷毀');}void didChangeDependencies(){//在initState之后調(diào)super.didChangeDependencies();print('進入didChange');}//重寫build方法Widget build(BuildContext context){return Container(//紅色背景color: Colors.red,//內(nèi)容居中alignment: Alignment.center,//Text控件child: new Text(//Dart語法中 ?? 表示如果text為空,就會返回??號的內(nèi)容text ?? "沒改變數(shù)值",textDirection: TextDirection.ltr,//需要加上這句不然報 RichText widgets require a Directionality widget ancestor.),);} } 復(fù)制代碼上面例子可以知道知道:在State可以動態(tài)更改數(shù)據(jù),在調(diào)用setState后,改變的數(shù)據(jù)會除法Widget重新構(gòu)建,上面代碼還寫了三個生命周期方法,這里簡單說一下:
- initState:初始化操作
- didChangeDependencies:在initState之后調(diào)用,可以獲取其他State
- dispose:銷毀
平時開發(fā)中在build實現(xiàn)布局的擺放,把數(shù)據(jù)添加Widget,通過setState改變數(shù)據(jù)。那如果很高頻率取改變數(shù)據(jù),性能肯定受影響,以下三點可以減少重新構(gòu)建有狀態(tài)控件的影響:
三、Flutter頁面
Flutter有顯示的Widget和完整頁面呈現(xiàn)的Widget,常見的有MaterialApp、Scaffold、Appbar、Text、Image、FlatButton,下面以表格形式簡單列一下:
下面一個個簡單上例子介紹:1.MaterialApp
import 'package:flutter/material.dart'; //使用`flutter/material.dart` 目的是使用Matrial風格的小控件 void main(){//運行程序runApp(MyApp()); }//用無狀態(tài)控件顯示 class MyApp extends StatelessWidget{Widget build(BuildContext context){return MaterialApp(//標題title:'Widget_Demo',//主題色theme:ThemeData(//設(shè)置為藍色primarySwatch: Colors.blue),//這是一個Widget對象,用來定義當前應(yīng)用打開的時候,所顯示的界面home:MyHomePage(),);} }class MyHomePage extends StatelessWidget{Widget build(BuildContext context){return Scaffold(//設(shè)置appbarappBar:new AppBar(title:new Text('This is a Demo'),),//主體body:new Center(//在屏幕中央顯示一個文本child:new Text('Hello'),),);} } 復(fù)制代碼效果如下圖:
上面可以看到MaterialApp作為了主界面入口。2.Scaffold
上面例子home:MyHomePage()這里返回了ScaffoldWidget,而這個Widget正是我們所看到的頁面,看到Scaffold包含了appBar和body,一開始說到,Scaffold也包含Drawers,下面實現(xiàn)一下:
Widget build(BuildContext context){return Scaffold(//設(shè)置appbarappBar:new AppBar(title:new Text('This is a Demo'),),//主體body:new Center(//在屏幕中央顯示一個文本child:new Text('Hello'),),//左側(cè)抽屜drawer:Drawer(//添加一個空的ListViewchild:ListView(),),);} 復(fù)制代碼效果如下:
下面往抽屜里添加點東西,就添加ListView,代碼如下: //左側(cè)抽屜drawer:Drawer(child:ListView(//設(shè)置paddingpadding:EdgeInsets.zero,children: <Widget>[//據(jù)說這里可以替換自定義的header//userHeader,ListTile(//標題內(nèi)容title: Text("This is Item_one"),//前置圖標leading: new CircleAvatar(child:new Icon(Icons.scanner),),),ListTile(//標題內(nèi)容title: Text("This is Item_two"),//前置圖標leading: new CircleAvatar(child:new Icon(Icons.list),),),ListTile(//標題內(nèi)容title: Text("This is Item_three"),//前置圖標leading: new CircleAvatar(child:new Icon(Icons.score),),),],),), 復(fù)制代碼運行效果就是抽屜里加了三行內(nèi)容的ListView。
3.AppBar
下面設(shè)置一些AppBar屬性,玩玩:
//設(shè)置appbarappBar: new AppBar(//AppBar內(nèi)容顯示title: new Text('This is a Demo'),//前置圖標leading: new Icon(Icons.home),//背景顏色 改為紅色backgroundColor: Colors.red,//設(shè)置為標題內(nèi)容居中centerTitle: true,//一個 Widget 列表,代表 Toolbar 中所顯示的菜單,// 對于常用的菜單,通常使用 IconButton 來表示;對于不常用的菜單通常使用 PopupMenuButton 來顯示為三個點,點擊后彈出二級菜單actions: <Widget>[//IconButtonnew IconButton(//圖標icon: new Icon(Icons.add_a_photo),//提示tooltip: 'Add photo',//點擊事件onPressed: () {},),//菜單彈出按鈕new PopupMenuButton<String>(itemBuilder: (BuildContext context) {return <PopupMenuItem<String>>[new PopupMenuItem<String>(value: "one", child: new Text('This one')),new PopupMenuItem<String>(value: "two", child: new Text('This two')),];},//選擇點擊事件onSelected: (String action) {switch (action) {case "one"://增加點擊邏輯break;case "two"://增加點擊邏輯break;}},),],), 復(fù)制代碼效果如下:
可以看到,上面Appbar上加了前置圖標、拍照圖標、菜單彈出按鈕、陰影。4.Text
下面用Text來展示文本,把上面例子用文本顯示中間的Hello單獨抽出來,如下:
//主體body: new Center(//在屏幕中央顯示一個文本 改為自定義樣式child: new CustomTextStyle('This is a Text'),),//單獨文本樣式 class CustomTextStyle extends StatelessWidget{String text;//構(gòu)造函數(shù) 參數(shù)外部傳進來CustomTextStyle(this.text);Widget build(BuildContext context){return Text(text ?? "Hello");} } 復(fù)制代碼下面把文本字體大小修改,字體樣式修改,背景顏色改改:
//文本 : 單獨文本樣式 class CustomTextStyle extends StatelessWidget {Paint pg = Paint();String text;//構(gòu)造函數(shù) 參數(shù)外部傳進來CustomTextStyle(this.text);Widget build(BuildContext context) {//設(shè)置畫筆顏色為黑色pg.color = Color(0xFF000000);return Text(text ?? "Hello",style: TextStyle(//顏色color: Colors.blue,//字體大小fontSize: 14,//字體加粗fontWeight: FontWeight.bold,//文本背景顏色background: pg),);} } 復(fù)制代碼上面效果是:
還有很多的屬性,根據(jù)需要去設(shè)置就行: const TextStyle({this.inherit = true,this.color,//文本樣式this.fontSize,//字體大小this.fontWeight,//繪制文本時的字體粗細this.fontStyle,//字體變體this.letterSpacing,//水平字母之間的空間間隔(邏輯像素為單位),可以負值this.wordSpacing,//單詞之間添加的空間間隔(邏輯像素為單位),可以負值this.textBaseline,//對齊文本的水平線this.height,//文本行與行的高度,作為字體代銷的倍數(shù)this.locale,//用于選擇區(qū)域定字形的語言環(huán)境this.foreground,//文本的前景色,不能與color共同設(shè)置this.background,//文本背景色this.shadows,//Flutter Decoration背景設(shè)定(邊框,圓角,陰影,漸變等)this.decoration,//繪制文本裝飾,添加上下劃線,刪除線this.decorationColor,//文本裝飾的顏色this.decorationStyle,//文本裝飾的樣式,控制畫虛線,點,波浪線this.debugLabel,String fontFamily,//使用字體的名稱String package,}) 復(fù)制代碼5.RichText
這是顯示豐富樣式的文本,這什么意思呢?Text只能顯示一種樣式的文字,如果想在一段文字中顯示多種樣式,就好像Android里面的SpannableString,就需要使用RichText,直接上例子:
//富文本樣式 class RichWidget extends StatelessWidget {Widget build(BuildContext context) {return RichText(text: TextSpan(text: 'This is RichText',style: new TextStyle(//false的時候不顯示inherit: true,//字體大小fontSize: 16,//黑色color: Colors.black),children: <TextSpan>[new TextSpan(text: 'Android藝術(shù)探索',style: new TextStyle(color: Colors.redAccent,//字體粗細fontWeight: FontWeight.bold,),),new TextSpan(text: '第一行代碼'),new TextSpan(text: 'Android進階之光',style: new TextStyle(color: Colors.indigo,//字體樣式fontSize: 20,),)],));} }//屏幕中間改為富文本widget//主體body: new Center(//Text在屏幕中央顯示一個文本 改為自定義樣式//child: new CustomTextStyle('This is a Text'),//富文本child:new RichWidget()), 復(fù)制代碼效果如下:
6.TextField
下面看看文本輸入框,文本輸入框平時會經(jīng)常用到:
body: new Center(//Text在屏幕中央顯示一個文本 改為自定義樣式//child: new CustomTextStyle('This is a Text'),//富文本//child:new RichWidget()//文本輸入框child:new TextFieldWidget() ),//文本輸入框 class TextFieldWidget extends StatelessWidget{Widget build(BuildContext context){return TextField();} } 復(fù)制代碼上面例子只能輸入文本內(nèi)容,如果想要獲取輸入框內(nèi)容,就要添加一個controller,通過這個controller添加通知來獲取TextField的值,我們一般點擊按鈕或者需要跟后臺交互就要讀取controller.text的值:
class MyHomePage extends StatelessWidget {//獲取TextEditingControllerfinal editController = TextEditingController();//IconButtonnew IconButton(//圖標icon: new Icon(Icons.add_a_photo),//提示tooltip: 'Add photo',//點擊事件onPressed: () {//輸出print('text inputted: ${editController.text}');//ToastFluttertoast.showToast(msg:'text inputted: ${editController.text}',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);},),....//主體body: new Center(//Text在屏幕中央顯示一個文本 改為自定義樣式//child: new CustomTextStyle('This is a Text'),//富文本//child:new RichWidget()//文本輸入框 以構(gòu)造函數(shù)傳遞controllerchild:new TextFieldWidget(editController)), } //文本輸入框 class TextFieldWidget extends StatelessWidget{final controller;//構(gòu)造函數(shù)傳值TextFieldWidget(this.controller);Widget build(BuildContext context){return TextField(controller: controller,);} } 復(fù)制代碼注意上面用到了Toast,Toast庫這里很簡單需要兩步:
重新運行即可,熱重載可能會出現(xiàn)異常。運行在iOS模擬器需要裝brew和CocoaPods,有問題運行flutter doctor,它真是如名字一樣,就是幫你診斷有沒有錯誤信息,會顯示具體信息。效果如下:
下面改一下樣式: return TextField(controller: controller,//最大長度,右下角會顯示一個輸入數(shù)量的字符串maxLength: 26,//最大行數(shù)maxLines: 1,//是否自動更正autocorrect: true,//是否自動對焦autofocus: true,//設(shè)置密碼 true:是密碼 false:不是秘密obscureText: true,//文本對齊樣式textAlign: TextAlign.center,); 復(fù)制代碼效果如下:
7.Image
Image很好理解就是在界面上區(qū)域顯示一張圖片,而這張圖片的來源可以是:本地,網(wǎng)絡(luò),資源圖片等。下面一一演示一下:
7.1.項目圖片資源
首先新建一個資源目錄:
在pubspec.yaml中配置圖片路徑,來識別應(yīng)用程序所需的assets: class MyHomePage extends StatelessWidget {//主體body: new Center(.....//圖片加載child:new ImageWidget()), } //圖片 class ImageWidget extends StatelessWidget{Widget build(BuildContext context){//項目資源圖片 方式一return Image(image: new AssetImage('images/Image_fluttericon.jpeg'),);//項目資源圖片 方式二 // return Image.asset('images/Image_fluttericon.jpeg');} } 復(fù)制代碼效果如下:
7.2.網(wǎng)絡(luò)圖片加載
下面進行網(wǎng)絡(luò)圖片加載,也是很簡單:
class MyHomePage extends StatelessWidget {//圖片路徑String image_url = "https://ws1.sinaimg.cn/large/0065oQSqgy1fze94uew3jj30qo10cdka.jpg";//主體body: new Center(.....//圖片加載child:new ImageWidget(image_url)), } //圖片 class ImageWidget extends StatelessWidget{String image_url;ImageWidget(this.image_url);Widget build(BuildContext context){return Image.network(image_url);} } 復(fù)制代碼效果如下:
下面用一個庫來加載和緩存網(wǎng)絡(luò)圖像,也可以與占位符和錯誤小部件一起使用,在pubspec.yaml添加依賴cached_network_image: ^0.4.1+1,在Dart文件導(dǎo)入這個庫import 'package:cached_network_image/cached_network_image.dart'; //圖片 class ImageWidget extends StatelessWidget{String image_url;ImageWidget(this.image_url);Widget build(BuildContext context){return new CachedNetworkImage(imageUrl: image_url,//占位符placeholder: new CircularProgressIndicator(),//加載錯誤時顯示的圖片errorWidget: new Icon(Icons.error),//寬高width:200,height: 200,);} } 復(fù)制代碼當圖片還沒加載出來的時候會顯示占位符,當如果加載出錯會顯示errorWidget的圖片。
7.3.聲明分辨率相關(guān)的圖片
另外Flutter可以為當前設(shè)備添加合適其分辨率的圖像,其實對于Android原生來說,就是在不同分辨率目錄下放置不同分辨率的圖片,只不過flutter并不是創(chuàng)建drawable-xxdpi文件,而是創(chuàng)建以下文件夾:
.../logo.png .../Mx/logo.png .../Nx/logo.png 復(fù)制代碼其中M和N是數(shù)字標識符,對應(yīng)于其中包含的圖像分辨率,它們指定不同素設(shè)備像比例的圖片,主資源默認對應(yīng)于1.0倍的分辨率圖片。看下面例子:
在設(shè)備像素比率為1.8的設(shè)備上,images/2.0x/logo.png 將被選擇。對于2.7的設(shè)備像素比率,images/3.0x/logo.png將被選擇。如果未在Image控件上指定渲染圖像的寬度和高度,以便它將占用與主資源相同的屏幕空間量(并不是相同的物理像素),只是分辨率更高。 也就是說,如果images/logo.png是72px乘72px,那么images/3.0x/logo.png應(yīng)該是216px乘216px; 但如果未指定寬度和高度,它們都將渲染為72像素×72像素(以邏輯像素為單位)。pubspec.yaml中asset部分中的每一項都應(yīng)與實際文件相對應(yīng),但主資源項除外。當主資源缺少某個資源時,會按分辨率從低到的順序去選擇,也就是說1.0x中沒有的話會在2.0x中找,2.0x中還沒有的話就在3.0x中找。 return Image(// 系統(tǒng)會根據(jù)分辨率自動選擇不同大小的圖片image: AssetImage('images/logo.png'),// ...), 復(fù)制代碼8.FlatButton
Flutter預(yù)先定義了一些按鈕控件,如FlatButton,RaisedButton,OutlineButton,IconButton。
下面看看FlatButton,其他的只是樣式稍微不一樣,大致用法一樣。
//按鈕 class FlatButtonWidget extends StatelessWidget{Widget build(BuildContext context){return FlatButton(onPressed: (){Fluttertoast.showToast(msg:'你點擊了FlatButton',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);},child: Text('FlatButton'),color: Colors.blue,//按鈕背景色textColor: Colors.white,//文字的顏色onHighlightChanged: (bool b){//水波紋變化回調(diào)},disabledColor: Colors.black,//按鈕禁用時的顯示的顏色disabledTextColor: Colors.black38,//按鈕被禁用的時候文字顯示的顏色splashColor: Colors.white,//水波紋的顏色);} } 復(fù)制代碼上面也設(shè)置了一些屬性,效果圖如下:
四、Flutter布局
Flutter中擁有30多種預(yù)定義的布局widget,常用的有Container、Padding、Center、Flex、Row、Colum、ListView、GridView。用一個表格列出它們的特性和使用。
下面一一介紹簡單用法:1.Container
一個擁有繪制、定位、調(diào)整大小的widget,示意圖如下:
下面直接上例子: class MyHomePage extends StatelessWidget {....body:new ContainWidget(),... } //Container布局 class ContainWidget extends StatelessWidget{Widget build(BuildContext context){return Container(child:Text("My name is Knight"),color: Colors.indigo,width:200,//寬height:200,//高margin:EdgeInsets.fromLTRB(5,5,5,5),//設(shè)置外邊距padding:EdgeInsets.all(30),//內(nèi)邊距);} } 復(fù)制代碼下面設(shè)置邊框,添加圓角:
//Container布局 class ContainWidget extends StatelessWidget{Widget build(BuildContext context){return Container(....padding:EdgeInsets.all(30),//內(nèi)邊距decoration: BoxDecoration(//設(shè)置邊框//背景色color:Colors.redAccent,//圓角borderRadius: BorderRadius.circular(6),),);} } 復(fù)制代碼運行效果如下:
2.Padding
一個Widget,會給其子Widget添加指定的填充,示意圖如下:
class MyHomePage extends StatelessWidget {....body: new PaddingWidget(),... } //Padding布局 class PaddingWidget extends StatelessWidget{Widget build(BuildContext context){return Padding(//設(shè)置左上右下內(nèi)邊距為4,10,6,8padding:EdgeInsets.fromLTRB(4, 10, 6, 8),child: Text('My name is Knight'),);} } 復(fù)制代碼效果圖如下:
下面實現(xiàn)Container嵌套Padding: //Container嵌套Padding class ContainPaddWidget extends StatelessWidget{Widget build(BuildContext context){return Container(width:200,//寬height:200,//高child: Padding(padding:EdgeInsets.fromLTRB(4, 10, 6, 8),child: Text("My name is Knight"),),decoration: BoxDecoration(//設(shè)置邊框//背景色color:Colors.redAccent,//圓角borderRadius: BorderRadius.circular(6),),);} } 復(fù)制代碼效果圖如下:
3.Center
將其子widget居中顯示在自身內(nèi)部的widget,示意圖:
//Center class CenterWidget extends StatelessWidget{Widget build(BuildContext context){return Container(width:200,//寬height:200,//高child: Center(child: Text("My name is Knight"),),decoration: BoxDecoration(//設(shè)置邊框//背景色color:Colors.redAccent,//圓角borderRadius: BorderRadius.circular(6),),);} } 復(fù)制代碼運行效果如下:
Center作為Container的孩子,Text所以在布局的中間。4.Stack
可以允許其子Widget簡單的堆疊在一起,層疊布局,示意圖:
下面直接上代碼,把之前的布局全部用上試試: class MyHomePage extends StatelessWidget {....body:new Center(child:new StackWidget()),... } //層疊布局 class StackWidget extends StatelessWidget{Widget build(BuildContext context){return Stack(children: <Widget>[new Image.network('https://ws1.sinaimg.cn/large/0065oQSqgy1fze94uew3jj30qo10cdka.jpg',width:300.0,//寬height:300.0,//高),new Opacity(opacity: 0.6,//不透明度child:new Container(width:100.0,height:100.0,color:Colors.redAccent,),),new Opacity(opacity: 0.6,child:new Container(width: 200.0,height:200.0,color:Colors.indigo,),),],);} } 復(fù)制代碼運行效果:
可以看到控件都按Stack左上角對齊,疊在一起,下面改一下顯示位置: //層疊布局 class StackWidget extends StatelessWidget{Widget build(BuildContext context){return Stack(//Aliginment的范圍是[-1,1],中心是[0,0].注釋有寫//和Android一樣,左移的取值是往1取,右移是往-1取//這里注意,它是取stack里范圍最大的布局為基準,下面是以Container為//基準對齊alignment: new Alignment(-0.6, -0.6),...);} } 復(fù)制代碼運行效果圖:
5.Colum
在垂直方向上排列子Widget,示意圖如下:
直接上代碼: class MyHomePage extends StatelessWidget {...body:new ColumnWidget(),....} //Column布局 class ColumnWidget extends StatelessWidget {Widget build(BuildContext context) {return Column(children: <Widget>[Container(color:Colors.blue,width: 50,height: 50,),Container(color:Colors.black,width:50,height:50,),Container(color:Colors.green,width:50,height:50,),],);} } 復(fù)制代碼運行效果:
下面簡單設(shè)置一下排列方式屬性: return Column(//設(shè)置垂直方向的對齊方式mainAxisAlignment: MainAxisAlignment.spaceEvenly,...); 復(fù)制代碼運行效果如下:
垂直方向(主軸上)屬性:下面列一下水平方向(交叉軸)的屬性:
6.Row
在水平方向上排列子widget的列表,示意圖:
直接上代碼: class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } //Row class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[Container(color:Colors.blue,width: 50.0,height:50.0,),Container(color:Colors.black,width:50.0,height:50.0,),Container(color:Colors.green,width:50.0,height:50.0,),],);} } 復(fù)制代碼效果圖:
下面簡單設(shè)置一些屬性,和Column沒多大差別: return Row(//把剩余空間平分n+1份,然后平分所有的空間mainAxisAlignment: MainAxisAlignment.spaceEvenly,...); 復(fù)制代碼效果圖:
水平方向上(主軸上)屬性:7.Expanded
Expanded組件可以使Row、Column、Fiex等子組件在其主軸上方向展開并填充可用的空間,這里注意:Expanded組件必須用在Row、Column、Fiex內(nèi),并且從Expanded到封裝它的Row、Column、Flex的路徑必須只包括StatelessWidgets或者StatefulWidgets(不能是其他類型的組件,像RenderObjectWidget,它是渲染對象,不再改變尺寸,因此Expanded不能放進RenderObjectWidget),示意圖如下:
class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[new RaisedButton(onPressed: (){},color:Colors.green,child:new Text('綠色按鈕1')),new Expanded(child:new RaisedButton(onPressed: (){},color:Colors.yellow,child:new Text('黃色按鈕2')),),new RaisedButton(onPressed:(){},color:Colors.red,child:new Text('黑色按鈕3')),],);} } 復(fù)制代碼運行效果如下:
class MyHomePage extends StatelessWidget {....body:new RowWidget(),... } class RowWidget extends StatelessWidget{Widget build(BuildContext context){return Row(children: <Widget>[Expanded(child:Container(color:Colors.green,padding:EdgeInsets.all(8),height: 40.0,),flex:1,),Expanded(child:Container(color:Colors.yellow,padding:EdgeInsets.all(8),height: 40.0,),flex:2,),Expanded(child:Container(color:Colors.red,padding:EdgeInsets.all(8),height: 40.0,),),],);} } 復(fù)制代碼上面代碼設(shè)置了flex,將一行的寬度分成四等分,第一、三child占1/4的區(qū)域,第二個child占1/2區(qū)域。 效果如下:
8.ListView
我相信這個布局在平時開發(fā)會經(jīng)常用到,這是可滾動的列表控件,ListView是最常用的滾動widget,它在滾動方向上一個接一個地顯示它的孩子。在縱軸上,孩子沒被要求填充ListView,并且內(nèi)置ListTitle,示意圖如下:
class MyHomePage extends StatelessWidget {....body: new ListViewWidget(new List<String>.generate(1000,(i){return 'Item &i';}),),... } //ListView class ListViewWidget extends StatelessWidget {final List<String> items;ListViewWidget(this.items);Widget build(BuildContext context) {return new ListView.builder(itemCount: items.length,itemBuilder: (context, index) {return new ListTile(title: new Text('This is $index'),);},);} } 復(fù)制代碼效果圖如下:
下面設(shè)置水平的ListView: class MyHomePage extends StatelessWidget {....body: new ListViewWidget( new List<String>.generate(1000, (i) { return 'Item &i'; }), ), ... }Widget build(BuildContext context) { return new ListView.builder( itemCount: items.length, //設(shè)置水平方向 scrollDirection:Axis.horizontal, //豎直時:確定每一個item的高度 //水平時:確定每一個item的寬度 得要設(shè)置 不然不顯示 itemExtent: 110.0, itemBuilder: (context, index) { return new ListTile( title: new Text('This is $index'),); }, ); 復(fù)制代碼效果如下:
9.GridView
GridView是一個網(wǎng)格布局的列組件。GridView繼承至CustomScrollView,示意圖如下:
直接豎直上例子: //GridView class GridViewWidget extends StatelessWidget{ Widget build(BuildContext context){ return new GridView.count( crossAxisCount: 3, //3列 children: List.generate(40, (i){ return Card( child: Center( child:Text('This is $i'), ), ); }) ); } } 復(fù)制代碼 下面上水平例子: return new GridView.count( //3行 crossAxisCount: 3, //設(shè)置水平 scrollDirection: Axis.horizontal,children: List.generate(40, (i) {return Card( child: Center( child: Text('This is $i'), ), ); }), ); 復(fù)制代碼效果圖如下:
10.TabBar
移動開發(fā)中tab切換是一個很常用的功能,那么Flutter有沒有提供這個Widget呢?答案是有的,Flutter通過Material庫提供了很方便的API來使用tab切換。
10.1.創(chuàng)建TabController
TabBarView和TabBar都有一個TabController的參數(shù),TabbarView和TabBar就是由TabController來控制同步,點擊某個Tab后,要同步顯示對應(yīng)的TabBarView,創(chuàng)建TabController有兩種方式:
下面就列一下第一種方式:
Widget build(BuildContext context) {return new DefaultTabController();} 復(fù)制代碼10.2.構(gòu)建Tab數(shù)據(jù)
final List<Tab> myTabs = <Tab>[new Tab(text: 'Android'),new Tab(text: 'IOS'),new Tab(text: 'Flutter'),new Tab(text: 'RN'),new Tab(text: 'Java'),new Tab(text: 'C'),new Tab(text: 'C++'),new Tab(text: 'Go'),];復(fù)制代碼10.3.創(chuàng)建TabBar
TabBar在哪里都可以創(chuàng)建,在AppBar里有一個bottom參數(shù)可以接受TabBar,就放在AppBar下:
//設(shè)置appbarappBar: new AppBar(//底部bottom: new TabBar(indicatorColor: Colors.red, //指示器顏色 如果和標題欄顏色一樣會白色tabs: myTabs,//綁定數(shù)據(jù)isScrollable: true, //是否可以滑動),), 復(fù)制代碼10.4.綁定TabBar和TabBarView
class MyHomePage extends StatelessWidget { final List<Tab> myTabs = <Tab>[new Tab(text: 'Android'),new Tab(text: 'IOS'),new Tab(text: 'Flutter'),new Tab(text: 'RN'),new Tab(text: 'Java'),new Tab(text: 'C'),new Tab(text: 'C++'),new Tab(text: 'Go'),];Widget build(BuildContext context) {return new DefaultTabController(length: myTabs.length, //Tab長度child: new Scaffold(//設(shè)置appbarappBar: new AppBar(//底部bottom: new TabBar(indicatorColor: Colors.red, //指示器顏色 如果和標題欄顏色一樣會白色tabs: myTabs,//綁定數(shù)據(jù)isScrollable: true, //是否可以滑動),....),body: new TabBarView(//選中哪個Tabs,body就會顯示children: myTabs.map((Tab tab) {return new Center(child: new Text(tab.text));}).toList(),),....);} } 復(fù)制代碼效果如下圖:
11.BottomNavigationBar
BottomNavigationBar即是底部導(dǎo)航欄控件,顯示在頁面底部的設(shè)計控件,用于在試圖切換,底部導(dǎo)航欄包含多個標簽、圖標或者兩者搭配的形式,簡而言之提供了頂級視圖之間的快速導(dǎo)航。
11.1.構(gòu)建底部標簽
//底部數(shù)據(jù)final Map bottomMap ={"首頁":Icon(Icons.home),"朋友圈":Icon(Icons.camera),"信息":Icon(Icons.message),"其他":Icon(Icons.devices_other),}; 復(fù)制代碼11.2.創(chuàng)建導(dǎo)航欄
因為點擊導(dǎo)航欄需要對應(yīng)的字體顯示,所以MyHomePage需要繼承StatefulWidget,增加State,
//用無狀態(tài)控件顯示 class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(//主題色theme: ThemeData(//設(shè)置為紅色primarySwatch: Colors.red),//這是一個Widget對象,用來定義當前應(yīng)用打開的時候,所顯示的界面home: MyHomePageWidget(),);} }class MyHomePageWidget extends StatefulWidget{State<StatefulWidget> createState(){return new MyHomePage();} } class MyHomePage extends State<MyHomePageWidget> {//底部數(shù)據(jù)final Map bottomMap ={"首頁":Icon(Icons.home),"朋友圈":Icon(Icons.camera),"信息":Icon(Icons.message),"其他":Icon(Icons.devices_other),};int _index = 0;bottomNavigationBar: BottomNavigationBar(items: (){var items = <BottomNavigationBarItem>[];bottomMap.forEach((k,v){items.add(BottomNavigationBarItem(title:Text(k),//取map的值icon : v,//取map的圖標backgroundColor:Colors.red,//背景紅色));});return items;}(),currentIndex: _index,//選中第幾個onTap:(position){Fluttertoast.showToast(msg: 'text inputted: $position',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);setState(() {_index = position;//狀態(tài)更新});}),} 復(fù)制代碼最終效果如下:
五、實踐
下面實踐Flutter中文網(wǎng)的例子:
先上布局分析圖:1.實現(xiàn)圖像
再說一下如何配置圖像
class MyApp extends StatelessWidget{Widget build(BuildContext context){return MaterialApp(home:new MyHomeWidget(),);} }class MyHomeWidget extends StatelessWidget{Widget build(BuildContext context){return new Scaffold(//設(shè)置標題欄appBar: new AppBar(title:new Text('Flutter Demo'),),//主體用ListViewbody:new ListView(children: <Widget>[//圖片new Image.asset('images/lake.jpg',width:600.0,height:240.0,//順便設(shè)置圖片屬性fit:BoxFit.cover,)],),);} } 復(fù)制代碼2.實現(xiàn)標題欄
//實現(xiàn)標題欄Widget titleWidget = new Container(//內(nèi)邊距padding:const EdgeInsets.all(30.0),//整體是一個水平的布局child:new Row(//只有一個孩子children: <Widget>[//用Expanded 會占用icon之外剩余空間new Expanded(//垂直布局 放置兩個文本child: new Column(//設(shè)置文本一起始端對齊crossAxisAlignment: CrossAxisAlignment.start,//有兩個孩子children: <Widget>[new Container(//底部內(nèi)邊距padding:const EdgeInsets.only(bottom:10.0),//孩子 設(shè)置字體樣式child:new Text('Oeschinen Lake Campground',style: new TextStyle(fontWeight: FontWeight.bold),),),new Text('Kandersteg, Switzerland',style: new TextStyle(color:Colors.grey[450],//設(shè)置顏色透明度),)],),),new Icon(Icons.star,color:Colors.red[400],),new Text('41'),],),); 復(fù)制代碼3.實現(xiàn)按鈕行
因為三個按鈕樣式都是一樣的,所以抽取公共部分:
/*** 抽取button行的代碼復(fù)用**/Column getText(IconData icon,String text){return new Column(//聚集widgetsmainAxisSize:MainAxisSize.min,//child居中mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new Icon(icon,color:Colors.blue[500]),new Container(//上部外邊距margin: const EdgeInsets.only(top:8.0),//Text內(nèi)容樣式設(shè)定child:new Text(text,style:new TextStyle(color:Colors.blue[500],),),)],);}/*** 按鈕實現(xiàn)*/Widget buttonWidget = new Container(//三列child:new Row(//用MainAxisAlignment.spaceEvenly平均分配子空間mainAxisAlignment: MainAxisAlignment.spaceEvenly,//孩子們children: <Widget>[getText(Icons.call, "CALL"),getText(Icons.near_me, "ROUTE"),getText(Icons.share, "SHARE"),],),); 復(fù)制代碼4.實現(xiàn)文本
/*** 文本實現(xiàn)*/Widget textWidget = new Container(alignment: Alignment.center,//設(shè)置內(nèi)邊距padding:const EdgeInsets.all(10.0),child:new Text('Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, ''it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, ''followed by a half-hour walk through pastures and pine forest, ''leads you to the lake, which warms to 20 degrees Celsius in the summer. ''Activities enjoyed here include rowing, and riding the summer toboggan run.',// softWrap: true,//屬性表示文本是否應(yīng)在軟換行符(例如句點或逗號)之間斷開。// textAlign: TextAlign.center,),); 復(fù)制代碼5.整合
return new Scaffold(//設(shè)置標題欄appBar: new AppBar(title:new Text('Flutter Demo'),),//主體用ListViewbody:new ListView(children: <Widget>[//圖片new Image.asset('images/lake.jpg',width:600.0,height:240.0,//順便設(shè)置圖片屬性fit:BoxFit.cover,),//標題欄titleWidget,//按鈕欄buttonWidget,//文本欄textWidget,],),);復(fù)制代碼運行效果圖:
六、總結(jié)
Flutter還有很多Widget上面沒有說到,就只能自己有空再去學習了,下面直接上一張圖,今天學到的內(nèi)容:
學習鏈接:flutterchina.club/widgets/
如有不正之處歡迎大家批評指正~
轉(zhuǎn)載于:https://juejin.im/post/5c5c1f21e51d457fcc5a9f9f
總結(jié)
以上是生活随笔為你收集整理的Flutter学习之认知基础组件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Room是怎样和LiveData结合使用
- 下一篇: 每日 30 秒 ⏱ 唯一的数据集