前一天,學習了Dart
語法,對Dart
的語法和特性有了更深一步的瞭解。今天,來學習Flutter
的基礎控件,身爲Android
開發者都知道,一開始入坑Android
就要熟悉學習其控件,如:TextView
,ImageView
,Button
,ListView
,RecycleView
等。爲何要學習呢?由於平時的開發都離不開這些控件,UI
的呈現都是有這些控件組成的,所以,其重要性就不用說了。對於Flutter
來說,基礎控件(widget
)就更加劇要了。Flutter
和Android
有所不同,Android
佈局包含佈局(RelativeLayout,LinearLayout,ConstrainLayou)和組件。Flutter
的一切都是Widget
,包括最頂層佈局也是Widget
,一個頁面有不少不少的Widget
組合而成,Widget
也稱爲裝飾品,窗口小部件。java
在Flutter
裏,UI
控件就是Widget
,Widget
根據不一樣的功能能夠分爲結構元素(如按鈕或菜單),文本樣式(字體或者顏色方案),佈局屬性(如填充,對齊,居中),能夠這麼理解,一個flutter
的頁面是有一棵樹型的Widget
組成,包括根節點,樹枝和樹葉,全都是Widget
,只是Widget
嵌套Widget
,那就能夠用下面這張圖來表示:git
Flutter
中,
Widget
是一切的基礎,做爲響應式渲染,屬於
MVVM
的實現機制,經過修改數據,再用
setState
設置數據,
Flutter
會自動經過綁定的數據更新
Widget
,因此在平時開發中,開發者須要的就是實現
Widget
界面,和數據綁定起來。在平時,用的最多就是
StatelessWidget
和
StatefulWidget
這兩種
Widget
,
StatelessWidget
表示無狀態的,
StatefulWidget
表示有狀態的。這裏怎麼理解呢?在
Flutter
中每一個頁面都是一幀,無狀態就是保持在那一幀,總而言之就是不能跟用戶交互,當有狀態的
Widget
當數據更新時,實際上是繪製了新的
Widget
,也就是UI發生了變化,只是
State
實現了
跨幀數據同步保存。這裏給你們說下,在Android Studio看源碼的兩個工具:
Structure
結構(看當前文件,win下的快捷鍵是(
Alt+7
))和右邊
Hierarchy
繼承關係(看當前類,win下快捷鍵是
F4
)均可以幫助你閱讀源碼。由於
StatelessWidget
和
StatefulWidget
用的最多,如今只須要用到這兩個,就先學習這兩個
Widget
。
源碼StatelessWidget
只有三個方法:github
Widget
、Element
、SemanticsNode
的惟一標識符,是用來控制Widget
數中替換Widget
的時候使用的。Widget
樹中的位置。StatelessWidget
,當Widget
第一次插入到樹中,或者父節點更改了配置和所依賴的[InheritedWidget]改變,都會被從新調用。這裏說下如何啓動一個Flutter
應用,並使用Flutter
框架:緩存
import 'package:flutter/material.dart';
void main() {
return runApp(Widget app);
}
複製代碼
其實就是在main()
函數中調用runApp
函數。下面直接直接上例子,繼承StatelessWidget
,經過build
方法返回一個控件:bash
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial風格的小控件
void main(){
//運行程序
runApp(MyApp(null));
}
//繼承無狀態的StatelessWidget 使程序自身變爲Wiget
class MyApp extends StatelessWidget{
//要顯示的內容
final String text;
//數據內容能夠經過構造方法傳遞進來
MyApp(this.text);
//重寫build方法 返回你須要的控件
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
//紅色背景
color: Colors.red,
//高度 如今沒用 會撐滿整個屏幕
height: 200,
//寬度 運行效果會撐滿整個屏幕
width: 200,
//內容居中
alignment: Alignment.center,
//Text控件
child: new Text(
//Dart語法中 ?? 表示若是text爲空,就會返回??號的內容
text ?? "my name is Knight",
textDirection: TextDirection.ltr,//須要加上這句否則報 RichText widgets require a Directionality widget ancestor.
),
);
}
}
複製代碼
Widget
和Widget
之間經過child
進行嵌套,有些Widget
只能有一個child
。就像上面的Container
,有些Widget
能夠有多個child
,像Colum
佈局。上面例子根佈局是Container
,Container
嵌套了Text
。網絡
什麼是有狀態的控件呢?狀態是在建立控件能夠同步讀取信息,而且在控件的生命週期內能夠改變,當控件狀態發生改變時使用State.setState
來及時更新,源碼也是隻有三個方法:app
StatelessWidget
同樣的,而
createState()
這個方法源碼註釋是:在
Widget
樹中給定的位置建立此可變狀態的小部件,子類應該重寫此方法返回新建的,關聯子類的實例。當調用一個
StatefulWidget
,框架就會調用
createState
這個方法,當一個
StatefulWidget
從
Widget
樹中移除,再次插入樹中,那麼會再次調用
createState
來建立一個新的
State
對象,這樣作簡化了
State
對象的生命週期。 須要建立管理的是主要是
State
,
StatefulWidget
用起來麻煩一些,他須要一個
State
,例子以下:
//繼承StatefulWidget
class StateWidget extends StatefulWidget{
@override
State createState(){
return _StateWidget();
}
}
class _StateWidget extends State<StateWidget>{
//重寫build方法
@override
Widget build(BuildContext context){
}
}
複製代碼
簡單觀察上面代碼,大體流程仍是和StatelessWidget
同樣的,build
方法照樣返回Widget
,不過在StatefulWidget
將這個方法放在createState
裏面。這裏細想一下,也知道爲何要這樣作,由於當狀態改變,就會回調createState
方法,從新調用build
方法從新建立UI,下面經過每兩秒改變UI這個例子來加深理解:框架
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial風格的小控件
import 'dart:async';//記得導庫
void main(){
//運行程序
runApp(StateWidget());
}
//控件繼承State
class _StateWidget extends State<StateWidget>{
int Number = 0;
String text;
//構造函數
_StateWidget(this.text);
@override
void initState(){
//初始化,這個函數在控件的生命週期內調用一次
super.initState();
print("進入initState");
//3秒後改變text的內容
new Future.delayed(const Duration(seconds: 3),(){
setState(() {
Number++;
text = "已經改變數值,數值如今是$Number";
});
});
}
@override
void dispose(){
//銷燬
super.dispose();
print('銷燬');
}
@override
void didChangeDependencies(){
//在initState以後調
super.didChangeDependencies();
print('進入didChange');
}
//重寫build方法
@override
Widget build(BuildContext context){
return Container(
//紅色背景
color: Colors.red,
//內容居中
alignment: Alignment.center,
//Text控件
child: new Text(
//Dart語法中 ?? 表示若是text爲空,就會返回??號的內容
text ?? "沒改變數值",
textDirection: TextDirection.ltr,//須要加上這句否則報 RichText widgets require a Directionality widget ancestor.
),
);
}
}
複製代碼
上面例子能夠知道知道:在State
能夠動態更改數據,在調用setState
後,改變的數據會除法Widget
從新構建,上面代碼還寫了三個生命週期方法,這裏簡單說一下:less
initState
以後調用,能夠獲取其餘State
平時開發中在build
實現佈局的擺放,把數據添加Widget
,經過setState
改變數據。那若是很高頻率取改變數據,性能確定受影響,如下三點能夠減小從新構建有狀態控件的影響:async
build
方法所建立的節點數量和控件數量。const
修飾控件。 怎麼去選擇有狀態和無狀態,最簡單就是能夠跟用戶進行交互應該使用StatefulWidget
,例如:點擊,滑動屏幕信息流數據更新,若是隻是僅僅顯示數據,那就能夠選擇使用StatelessWidget
建立一個無狀態控件。Flutter有顯示的Widget
和完整頁面呈現的Widget
,常見的有MaterialApp
、Scaffold
、Appbar
、Text
、Image
、FlatButton
,下面以表格形式簡單列一下:
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial風格的小控件
void main(){
//運行程序
runApp(MyApp());
}
//用無狀態控件顯示
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
//標題
title:'Widget_Demo',
//主題色
theme:ThemeData(
//設置爲藍色
primarySwatch: Colors.blue
),
//這是一個Widget對象,用來定義當前應用打開的時候,所顯示的界面
home:MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Scaffold(
//設置appbar
appBar:new AppBar(
title:new Text('This is a Demo'),
),
//主體
body:new Center(
//在屏幕中央顯示一個文本
child:new Text('Hello'),
),
);
}
}
複製代碼
效果以下圖:
MaterialApp
做爲了主界面入口。
上面例子home:MyHomePage()
這裏返回了Scaffold
Widget,而這個Widget
正是咱們所看到的頁面,看到Scaffold
包含了appBar
和body
,一開始說到,Scaffold
也包含Drawers
,下面實現一下:
@override
Widget build(BuildContext context){
return Scaffold(
//設置appbar
appBar:new AppBar(
title:new Text('This is a Demo'),
),
//主體
body:new Center(
//在屏幕中央顯示一個文本
child:new Text('Hello'),
),
//左側抽屜
drawer:Drawer(
//添加一個空的ListView
child:ListView(),
),
);
}
複製代碼
效果以下:
ListView
,代碼以下:
//左側抽屜
drawer:Drawer(
child:ListView(
//設置padding
padding:EdgeInsets.zero,
children: <Widget>[
//聽說這裏能夠替換自定義的header
//userHeader,
ListTile(
//標題內容
title: Text("This is Item_one"),
//前置圖標
leading: new CircleAvatar(child:new Icon(Icons.scanner),),
),
ListTile(
//標題內容
title: Text("This is Item_two"),
//前置圖標
leading: new CircleAvatar(child:new Icon(Icons.list),),
),
ListTile(
//標題內容
title: Text("This is Item_three"),
//前置圖標
leading: new CircleAvatar(child:new Icon(Icons.score),),
),
],
),
),
複製代碼
運行效果就是抽屜里加了三行內容的ListView
。
下面設置一些AppBar
屬性,玩玩:
//設置appbar
appBar: new AppBar(
//AppBar內容顯示
title: new Text('This is a Demo'),
//前置圖標
leading: new Icon(Icons.home),
//背景顏色 改成紅色
backgroundColor: Colors.red,
//設置爲標題內容居中
centerTitle: true,
//一個 Widget 列表,表明 Toolbar 中所顯示的菜單,
// 對於經常使用的菜單,一般使用 IconButton 來表示;對於不經常使用的菜單一般使用 PopupMenuButton 來顯示爲三個點,點擊後彈出二級菜單
actions: <Widget>[
//IconButton
new 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;
}
},
),
],
),
複製代碼
效果以下:
Appbar
上加了前置圖標、
拍照圖標
、菜單彈出按鈕、陰影。
下面用Text
來展現文本,把上面例子用文本顯示中間的Hello
單獨抽出來,以下:
//主體
body: new Center(
//在屏幕中央顯示一個文本 改成自定義樣式
child: new CustomTextStyle('This is a Text'),
),
//單獨文本樣式
class CustomTextStyle extends StatelessWidget{
String text;
//構造函數 參數外部傳進來
CustomTextStyle(this.text);
@override
Widget build(BuildContext context){
return Text(text ?? "Hello");
}
}
複製代碼
下面把文本字體大小修改,字體樣式修改,背景顏色改改:
//文本 : 單獨文本樣式
class CustomTextStyle extends StatelessWidget {
Paint pg = Paint();
String text;
//構造函數 參數外部傳進來
CustomTextStyle(this.text);
@override
Widget build(BuildContext context) {
//設置畫筆顏色爲黑色
pg.color = Color(0xFF000000);
return Text(
text ?? "Hello",
style: TextStyle(
//顏色
color: Colors.blue,
//字體大小
fontSize: 14,
//字體加粗
fontWeight: FontWeight.bold,
//文本背景顏色
background: pg),
);
}
}
複製代碼
上面效果是:
const TextStyle({ this.inherit = true, this.color,//文本樣式 this.fontSize,//字體大小 this.fontWeight,//繪製文本時的字體粗細 this.fontStyle,//字體變體 this.letterSpacing,//水平字母之間的空間間隔(邏輯像素爲單位),能夠負值 this.wordSpacing,//單詞之間添加的空間間隔(邏輯像素爲單位),能夠負值 this.textBaseline,//對齊文本的水平線 this.height,//文本行與行的高度,做爲字體代銷的倍數 this.locale,//用於選擇區域定字形的語言環境 this.foreground,//文本的前景色,不能與color共同設置 this.background,//文本背景色 this.shadows,//Flutter Decoration背景設定(邊框,圓角,陰影,漸變等) this.decoration,//繪製文本裝飾,添加上下劃線,刪除線 this.decorationColor,//文本裝飾的顏色 this.decorationStyle,//文本裝飾的樣式,控制畫虛線,點,波浪線 this.debugLabel, String fontFamily,//使用字體的名稱 String package, }) 複製代碼
這是顯示豐富樣式的文本,這什麼意思呢?Text
只能顯示一種樣式的文字,若是想在一段文字中顯示多種樣式,就好像Android
裏面的SpannableString
,就須要使用RichText
,直接上例子:
//富文本樣式
class RichWidget extends StatelessWidget {
@override
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藝術探索',
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()
),
複製代碼
效果以下:
下面看看文本輸入框,文本輸入框平時會常常用到:
body: new Center(
//Text在屏幕中央顯示一個文本 改成自定義樣式
//child: new CustomTextStyle('This is a Text'),
//富文本
//child:new RichWidget()
//文本輸入框
child:new TextFieldWidget()
),
//文本輸入框
class TextFieldWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return TextField();
}
}
複製代碼
上面例子只能輸入文本內容,若是想要獲取輸入框內容,就要添加一個controller
,經過這個controller
添加通知來獲取TextField
的值,咱們通常點擊按鈕或者須要跟後臺交互就要讀取controller.text
的值:
class MyHomePage extends StatelessWidget {
//獲取TextEditingController
final editController = TextEditingController();
//IconButton
new IconButton(
//圖標
icon: new Icon(Icons.add_a_photo),
//提示
tooltip: 'Add photo',
//點擊事件
onPressed: () {
//輸出
print('text inputted: ${editController.text}');
//Toast
Fluttertoast.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()
//文本輸入框 以構造函數傳遞controller
child:new TextFieldWidget(editController)
),
}
//文本輸入框
class TextFieldWidget extends StatelessWidget{
final controller;
//構造函數傳值
TextFieldWidget(this.controller);
@override
Widget build(BuildContext context){
return TextField(
controller: controller,
);
}
}
複製代碼
注意上面用到了Toast
,Toast庫這裏很簡單須要兩步:
pubspec.yaml
添加依賴庫fluttertoast: ^2.1.1
import 'package:fluttertoast/fluttertoast.dart';
從新運行便可,熱重載可能會出現異常。運行在iOS模擬器須要裝brew
和CocoaPods
,有問題運行flutter doctor
,它真是如名字同樣,就是幫你診斷有沒有錯誤信息,會顯示具體信息。效果以下:
return TextField(
controller: controller,
//最大長度,右下角會顯示一個輸入數量的字符串
maxLength: 26,
//最大行數
maxLines: 1,
//是否自動更正
autocorrect: true,
//是否自動對焦
autofocus: true,
//設置密碼 true:是密碼 false:不是祕密
obscureText: true,
//文本對齊樣式
textAlign: TextAlign.center,
);
複製代碼
效果以下:
Image
很好理解就是在界面上區域顯示一張圖片,而這張圖片的來源能夠是:本地,網絡,資源圖片等。下面一一演示一下:
首先新建一個資源目錄:
pubspec.yaml
中配置圖片路徑,來識別應用程序所需的assets:
class MyHomePage extends StatelessWidget {
//主體
body: new Center(
.....
//圖片加載
child:new ImageWidget()
),
}
//圖片
class ImageWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
//項目資源圖片 方式一
return Image(
image: new AssetImage('images/Image_fluttericon.jpeg'),
);
//項目資源圖片 方式二
// return Image.asset('images/Image_fluttericon.jpeg');
}
}
複製代碼
效果以下:
下面進行網絡圖片加載,也是很簡單:
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);
@override
Widget build(BuildContext context){
return Image.network(image_url);
}
}
複製代碼
效果以下:
pubspec.yaml
添加依賴
cached_network_image: ^0.4.1+1
,在
Dart
文件導入這個庫
import 'package:cached_network_image/cached_network_image.dart';
//圖片
class ImageWidget extends StatelessWidget{
String image_url;
ImageWidget(this.image_url);
@override
Widget build(BuildContext context){
return new CachedNetworkImage(
imageUrl: image_url,
//佔位符
placeholder: new CircularProgressIndicator(),
//加載錯誤時顯示的圖片
errorWidget: new Icon(Icons.error),
//寬高
width:200,
height: 200,
);
}
}
複製代碼
當圖片還沒加載出來的時候會顯示佔位符,當若是加載出錯會顯示errorWidget
的圖片。
另外Flutter能夠爲當前設備添加合適其分辨率的圖像,其實對於Android原生來講,就是在不一樣分辨率目錄下放置不一樣分辨率的圖片,只不過flutter並非建立drawable-xxdpi
文件,而是建立如下文件夾:
.../logo.png
.../Mx/logo.png
.../Nx/logo.png
複製代碼
其中M和N是數字標識符,對應於其中包含的圖像分辨率,它們指定不一樣素設備像比例的圖片,主資源默認對應於1.0倍的分辨率圖片。看下面例子:
images/2.0x/logo.png
將被選擇。對於2.7的設備像素比率,
images/3.0x/logo.png
將被選擇。若是未在Image控件上指定渲染圖像的寬度和高度,以便它將佔用與主資源相同的屏幕空間量(並非相同的物理像素),只是分辨率更高。 也就是說,若是
images/logo.png
是72px乘72px,那麼
images/3.0x/logo.png
應該是216px乘216px; 但若是未指定寬度和高度,它們都將渲染爲72像素×72像素(以邏輯像素爲單位)。
pubspec.yaml
中asset部分中的每一項都應與實際文件相對應,但主資源項除外。當主資源缺乏某個資源時,會按分辨率從低到的順序去選擇,也就是說1.0x中沒有的話會在2.0x中找,2.0x中尚未的話就在3.0x中找。
return Image(
// 系統會根據分辨率自動選擇不一樣大小的圖片
image: AssetImage('images/logo.png'),
// ...
),
複製代碼
Flutter
預先定義了一些按鈕控件,如FlatButton
,RaisedButton
,OutlineButton
,IconButton
。
MaterialButton
MaterialButton
MaterialButton
StatelessWidget
下面看看FlatButton
,其餘的只是樣式稍微不同,大體用法同樣。
//按鈕
class FlatButtonWidget extends StatelessWidget{
@override
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){//水波紋變化回調
},
disabledColor: Colors.black,//按鈕禁用時的顯示的顏色
disabledTextColor: Colors.black38,//按鈕被禁用的時候文字顯示的顏色
splashColor: Colors.white,//水波紋的顏色
);
}
}
複製代碼
上面也設置了一些屬性,效果圖以下:
Flutter
中擁有30多種預約義的佈局widget
,經常使用的有Container
、Padding
、Center
、Flex
、Row
、Colum
、ListView
、GridView
。用一個表格列出它們的特性和使用。
一個擁有繪製、定位、調整大小的widget
,示意圖以下:
class MyHomePage extends StatelessWidget {
....
body:new ContainWidget(),
...
}
//Container佈局
class ContainWidget extends StatelessWidget{
@override
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),//設置外邊距
padding:EdgeInsets.all(30),//內邊距
);
}
}
複製代碼
下面設置邊框,添加圓角:
//Container佈局
class ContainWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
....
padding:EdgeInsets.all(30),//內邊距
decoration: BoxDecoration(//設置邊框
//背景色
color:Colors.redAccent,
//圓角
borderRadius: BorderRadius.circular(6),
),
);
}
}
複製代碼
運行效果以下:
一個Widget
,會給其子Widget
添加指定的填充,示意圖以下:
class MyHomePage extends StatelessWidget {
....
body: new PaddingWidget(),
...
}
//Padding佈局
class PaddingWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Padding(
//設置左上右下內邊距爲4,10,6,8
padding:EdgeInsets.fromLTRB(4, 10, 6, 8),
child: Text('My name is Knight'),
);
}
}
複製代碼
效果圖以下:
Container
嵌套
Padding
:
//Container嵌套Padding
class ContainPaddWidget extends StatelessWidget{
@override
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(//設置邊框
//背景色
color:Colors.redAccent,
//圓角
borderRadius: BorderRadius.circular(6),
),
);
}
}
複製代碼
效果圖以下:
將其子widget
居中顯示在自身內部的widget
,示意圖:
//Center
class CenterWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
width:200,//寬
height:200,//高
child: Center(
child: Text("My name is Knight"),
),
decoration: BoxDecoration(//設置邊框
//背景色
color:Colors.redAccent,
//圓角
borderRadius: BorderRadius.circular(6),
),
);
}
}
複製代碼
運行效果以下:
Center
做爲
Container
的孩子,
Text
因此在佈局的中間。
能夠容許其子Widget
簡單的堆疊在一塊兒,層疊佈局,示意圖:
class MyHomePage extends StatelessWidget {
....
body:new Center(
child:new StackWidget()
),
...
}
//層疊佈局
class StackWidget extends StatelessWidget{
@override
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,
),
),
],
);
}
}
複製代碼
運行效果:
Stack
左上角對齊,疊在一塊兒,下面改一下顯示位置:
//層疊佈局
class StackWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Stack(
//Aliginment的範圍是[-1,1],中心是[0,0].註釋有寫
//和Android同樣,左移的取值是往1取,右移是往-1取
//這裏注意,它是取stack裏範圍最大的佈局爲基準,下面是以Container爲//基準對齊
alignment: new Alignment(-0.6, -0.6),
...
);
}
}
複製代碼
運行效果圖:
在垂直方向上排列子Widget
,示意圖以下:
class MyHomePage extends StatelessWidget {
...
body:new ColumnWidget(),
....
}
//Column佈局
class ColumnWidget extends StatelessWidget {
@override
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,
),
],
);
}
}
複製代碼
運行效果:
return Column(
//設置垂直方向的對齊方式
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
...
);
複製代碼
運行效果以下:
widget
的數量,而後把其中一份空間分紅2份,放在第一個child的前面,和最後一個child的後面,也就是子widget
的以前以後之間均勻分割空閒的一半空間widget
以前以後之間均勻的分割空閒的空間下面列一下水平方向(交叉軸)的屬性:
child
填充滿布局textBaseline
一塊兒使用在水平方向上排列子widget
的列表,示意圖:
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
//Row
class RowWidget extends StatelessWidget{
@override
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,
),
],
);
}
}
複製代碼
效果圖:
Column
沒多大差異:
return Row(
//把剩餘空間平分n+1份,而後平分全部的空間
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
...
);
複製代碼
效果圖:
widget
的數量,而後把其中一份空間分紅2份,放在第一個child的前面,和最後一個child的後面,也就是子widget
的以前以後之間均勻分割空閒的一半空間widget
以前以後之間均勻的分割空閒的空間 而交叉軸(垂直方向)的屬性:child
填充滿布局textBaseline
一塊兒使用Expanded
組件可使Row
、Column
、Fiex
等子組件在其主軸上方向展開並填充可用的空間,這裏注意:Expanded
組件必須用在Row
、Column
、Fiex
內,而且從Expanded
到封裝它的Row
、Column
、Flex
的路徑必須只包括StatelessWidgets
或者StatefulWidgets
(不能是其餘類型的組件,像RenderObjectWidget
,它是渲染對象,再也不改變尺寸,所以Expanded
不能放進RenderObjectWidget
),示意圖以下:
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
class RowWidget extends StatelessWidget{
@override
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')),
],
);
}
}
複製代碼
運行效果以下:
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
class RowWidget extends StatelessWidget{
@override
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,
),
),
],
);
}
}
複製代碼
上面代碼設置了flex
,將一行的寬度分紅四等分,第1、三child
佔1/4的區域,第二個child
佔1/2區域。 效果以下:
我相信這個佈局在平時開發會常常用到,這是可滾動的列表控件,ListView
是最經常使用的滾動widget
,它在滾動方向上一個接一個地顯示它的孩子。在縱軸上,孩子沒被要求填充ListView
,而且內置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);
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return new ListTile(
title: new Text('This is $index'),
);
},
);
}
}
複製代碼
效果圖以下:
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,
//設置水平方向
scrollDirection:Axis.horizontal,
//豎直時:肯定每個item的高度
//水平時:肯定每個item的寬度 得要設置 否則不顯示
itemExtent: 110.0,
itemBuilder: (context, index) {
return new ListTile(
title: new Text('This is $index'),
);
},
);
複製代碼
效果以下:
GridView
是一個網格佈局的列組件。GridView
繼承至CustomScrollView
,示意圖以下:
//GridView
class GridViewWidget extends StatelessWidget{
@override
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'),
),
);
})
);
}
}
複製代碼
return new GridView.count(
//3行
crossAxisCount: 3,
//設置水平
scrollDirection: Axis.horizontal,
children: List.generate(40, (i) {
return Card(
child: Center(
child: Text('This is $i'),
),
);
}),
);
複製代碼
效果圖以下:
移動開發中tab
切換是一個很經常使用的功能,那麼Flutter
有沒有提供這個Widget
呢?答案是有的,Flutter
經過Material
庫提供了很方便的API來使用tab
切換。
TabBarView
和TabBar
都有一個TabController
的參數,TabbarView
和TabBar
就是由TabController
來控制同步,點擊某個Tab
後,要同步顯示對應的TabBarView
,建立TabController
有兩種方式:
DefaultTabController
,在Scaffold
套一層DefaultTabController
,這種方式TabBarView
會自動查找這個tabController
。TabController
,實現SingleTickerProviderStateMixin
下面就列一下第一種方式:
@override
Widget build(BuildContext context) {
return new DefaultTabController();
}
複製代碼
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'),
];
複製代碼
TabBar
在哪裏均可以建立,在AppBar
裏有一個bottom
參數能夠接受TabBar
,就放在AppBar
下:
//設置appbar
appBar: new AppBar(
//底部
bottom: new TabBar(
indicatorColor: Colors.red, //指示器顏色 若是和標題欄顏色同樣會白色
tabs: myTabs,//綁定數據
isScrollable: true, //是否能夠滑動
),
),
複製代碼
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'),
];
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: myTabs.length, //Tab長度
child: new Scaffold(
//設置appbar
appBar: new AppBar(
//底部
bottom: new TabBar(
indicatorColor: Colors.red, //指示器顏色 若是和標題欄顏色同樣會白色
tabs: myTabs,//綁定數據
isScrollable: true, //是否能夠滑動
),
....
),
body: new TabBarView(
//選中哪一個Tabs,body就會顯示
children: myTabs.map((Tab tab) {
return new Center(child: new Text(tab.text));
}).toList(),
),
....
);
}
}
複製代碼
效果以下圖:
BottomNavigationBar
便是底部導航欄控件,顯示在頁面底部的設計控件,用於在試圖切換,底部導航欄包含多個標籤、圖標或者二者搭配的形式,簡而言之提供了頂級視圖之間的快速導航。
//底部數據
final Map bottomMap ={
"首頁":Icon(Icons.home),
"朋友圈":Icon(Icons.camera),
"信息":Icon(Icons.message),
"其餘":Icon(Icons.devices_other),
};
複製代碼
由於點擊導航欄須要對應的字體顯示,因此MyHomePage
須要繼承StatefulWidget
,增長State
,
//用無狀態控件顯示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//主題色
theme: ThemeData(
//設置爲紅色
primarySwatch: Colors.red),
//這是一個Widget對象,用來定義當前應用打開的時候,所顯示的界面
home: MyHomePageWidget(),
);
}
}
class MyHomePageWidget extends StatefulWidget{
@override
State<StatefulWidget> createState(){
return new MyHomePage();
}
}
class MyHomePage extends State<MyHomePageWidget> {
//底部數據
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;//狀態更新
});
}
),
}
複製代碼
最終效果以下:
下面實踐Flutter
中文網的例子:
再說一下如何配置圖像
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
home:new MyHomeWidget(),
);
}
}
class MyHomeWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Scaffold(
//設置標題欄
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主體用ListView
body:new ListView(
children: <Widget>[
//圖片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//順便設置圖片屬性
fit:BoxFit.cover,
)
],
),
);
}
}
複製代碼
//實現標題欄
Widget titleWidget = new Container(
//內邊距
padding:const EdgeInsets.all(30.0),
//總體是一個水平的佈局
child:new Row(
//只有一個孩子
children: <Widget>[
//用Expanded 會佔用icon以外剩餘空間
new Expanded(
//垂直佈局 放置兩個文本
child: new Column(
//設置文本一塊兒始端對齊
crossAxisAlignment: CrossAxisAlignment.start,
//有兩個孩子
children: <Widget>[
new Container(
//底部內邊距
padding:const EdgeInsets.only(bottom:10.0),
//孩子 設置字體樣式
child:new Text(
'Oeschinen Lake Campground',
style: new TextStyle(fontWeight: FontWeight.bold),
),
),
new Text(
'Kandersteg, Switzerland',
style: new TextStyle(
color:Colors.grey[450],//設置顏色透明度
),
)
],
),
),
new Icon(
Icons.star,
color:Colors.red[400],
),
new Text('41'),
],
),
);
複製代碼
由於三個按鈕樣式都是同樣的,因此抽取公共部分:
/** * 抽取button行的代碼複用 * */
Column getText(IconData icon,String text){
return new Column(
//彙集widgets
mainAxisSize: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內容樣式設定
child:new Text(
text,
style:new TextStyle(
color:Colors.blue[500],
),
),
)
],
);
}
/** * 按鈕實現 */
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"),
],
),
);
複製代碼
/** * 文本實現 */
Widget textWidget = new Container(
alignment: Alignment.center,
//設置內邊距
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,//屬性表示文本是否應在軟換行符(例如句點或逗號)之間斷開。
// textAlign: TextAlign.center,
),
);
複製代碼
return new Scaffold(
//設置標題欄
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主體用ListView
body:new ListView(
children: <Widget>[
//圖片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//順便設置圖片屬性
fit:BoxFit.cover,
),
//標題欄
titleWidget,
//按鈕欄
buttonWidget,
//文本欄
textWidget,
],
),
);
複製代碼
運行效果圖:
Flutter
還有不少Widget
上面沒有說到,就只能本身有空再去學習了,下面直接上一張圖,今天學到的內容:
學習連接:flutterchina.club/widgets/
若有不正之處歡迎你們批評指正~