上一篇主要介紹了
Dart
語言的語法基礎,從這一篇開始就要真正涉及到Flutter的開發了,但願本身在寫做的過程當中能溫故知新,同時給Flutter初學者帶來一些幫助。html
還記得在上一篇中,咱們使用Android Studio建立了一個Flutter項目嗎?新建立的Flutter項目自動爲咱們生成了一些代碼,代碼在/lib/main.dart
文件中,這裏咱們先清空/lib/main.dart
文件中的代碼,用下面的代碼代替:android
// main.dart文件內容
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('First App')
),
body: new Center(
child: new Text('Hello world'),
),
),
);
}
}
複製代碼
爲了在手機上跑起咱們的App來,首先咱們得運行一個模擬器(固然你也能夠用真機調試)。若是你的電腦上Flutter開發環境配置得沒有問題,該裝的都裝了(運行flutter doctor
命令檢查依賴是否安裝,AndroidStudio的dart和flutter插件也必須安裝),那麼在Android Studio的工具欄上,應該能夠看到以下圖的圖標: ios
這裏因爲個人電腦上尚未任何運行的Android / iOS模擬器,因此這裏顯示的是<no devices>
,點擊該按鈕,選擇Open iOS Simulator
便可啓動一個iOS模擬器(確保你的電腦上安裝了Xcode)以下圖所示:git
若是你想建立Android模擬器,必須先確保你有可用的Android模擬器,在AndroidStudio的工具欄上找到AVD Manager
圖標,以下圖:github
點擊打開Android模擬器管理對話框,以下圖: bash
Create Virtual Devices...
建立模擬器便可。
第一步中咱們已經寫好了代碼,第二步中咱們的模擬器也啓動了,點擊AndroidStudio工具欄中的Run
按鈕便可運行Flutter項目到咱們的模擬器中了,Run
按鈕在下圖所示位置: markdown
能夠看到,咱們僅僅用了20多行代碼,就完成了一個精美的Demo App(雖然沒有實現任何功能,可是對比下若是要用Android或iOS原生開發方式,能夠作到這麼簡單實現嗎),這一切都歸功於Flutter爲咱們提供的Widgets,下面的篇幅裏會針對經常使用的Widgets作一些講解。網絡
新建立的Flutter項目的結構以下圖所示: app
各個目錄/文件說明以下:框架
.
├── README.md ---markdown項目描述文件
├── android ---Android源代碼目錄
├── build ---項目構建後輸出的相關文件目錄
├── flutter_app.iml ---項目相關的配置文件
├── flutter_app_android.iml ---Android相關的配置文件
├── ios ---iOS源代碼目錄
├── lib ---Dart源碼目錄
├── pubspec.lock ---安裝鎖定文件
├── pubspec.yaml ---flutter依賴配置文件,相似Android中的build.grale
└── test ---測試代碼目錄
複製代碼
咱們開發的代碼主要存放在lib/
目錄下,項目的入口文件main.dart
也在lib/
目錄下。
關於一個Flutter App,你須要瞭解以下幾個點:
在移動開發中,咱們常常會跟按鈕、文本輸入框、圖片等打交道,Flutter中也不例外,使用Flutter開發的App,界面上的每個UI元素都是一個Widget,經過不一樣的Widget組合造成一整個頁面。除了按鈕、輸入框、圖片等Widget外,Flutter還給咱們提供了不少功能強大,界面美觀的Widget,好比在本文最開始的一段代碼:
// main.dart文件內容
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('First App')
),
body: new Center(
child: new Text('Hello world'),
),
),
);
}
}
複製代碼
在上面的代碼中,MyApp
是咱們自定義的一個類,它繼承自StatelessWidget,表明它是一個無狀態的組件,UI不會發生改變。build
方法是父類的一個方法,被MyApp
類重寫了,繼承自StatelessWidget的類必須實現build
方法並返回一個Widget對象。因此在上面的代碼中,MaterialApp
也是一個Widget,若是你用AndroidStudio查看源碼,會發現MaterialApp的參數home
也是一個Widget對象,因此上面的Scaffold也是一個Widget。
StatefulWidget和StatelessWidget是Flutter中全部Widget的兩個分類,StatefulWidget的內部保存有狀態,當狀態發生改變時,Widget的界面也會隨之改變(這點跟React相似);StatelessWidget的內部沒有保存狀態,它的界面也不會發生改變。上面的代碼中已經展現了定義一個無狀態Widget的步驟:繼承StatelessWidget並實現build方法便可。若是是定義一個有狀態的Widget,代碼會稍微多一點,以下代碼所示:
import 'package:flutter/material.dart';
void main() => runApp(new MyStatefulWidget());
// 定義一個有狀態的組件
class MyStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new MyStatefulWidgetState();
}
}
// 定義一個有狀態的組件時,必須爲該組件建立一個狀態類,這個類繼承自State類
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String text = "Click Me!";
changeText() {
if (text == "Click Me!") {
setState(() {
text = "Hello World!";
});
} else {
setState(() {
text = "Click Me!";
});
}
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Center(
// InkWell是Flutter內置的一個Widget,用於給其餘Widget添加點擊事件,而且在點擊時會有水波紋擴散效果
child: new InkWell(
child: new Text(text),
onTap: () {
this.changeText();
},
),
),
),
);
}
}
複製代碼
上面的代碼運行後,會在頁面中央顯示文本,點擊該文本時,文本內容會在"Click Me!"和"Hello World!"間切換,以下圖所示:
createState
方法,注意,這裏跟StatelessWidget不一樣了,不是實現build
方法。createState
方法返回的是一個狀態State。createState
方法有返回值,還須要建立一個狀態類繼承自State類,State類是個泛型類,你須要將第一步中建立的類傳給State。build
方法,並返回你所須要的Widget。text
變量保存組件的文本值,當點擊按鈕時,經過調用State組件的setState()
方法,從新爲text
變量賦值,從而達到改變文本的目的。若是你瞭解Reactjs,那麼對於Flutter的這種狀態機制確定也不陌生。React中也是經過一個state對象保存Component的狀態,當狀態須要改變時,調用setState()方法修改狀態,組件就會自動刷新。
MaterialApp和Scaffold是Flutter提供的兩個Widget,其中:
在基於Flutter的開源中國客戶端App中,我也使用到了MaterialApp和Scaffold兩個組件,下面是部分代碼:
@override
Widget build(BuildContext context) {
initData();
return new MaterialApp(
theme: new ThemeData(
// 設置主題顏色
primaryColor: const Color(0xFF63CA6C)
),
home: new Scaffold(
// 設置App頂部的AppBar
appBar: new AppBar(
// AppBar的標題
title: new Text(appBarTitles[_tabIndex],
// 標題文本的顏色
style: new TextStyle(color: Colors.white)),
// AppBar上的圖標的顏色
iconTheme: new IconThemeData(color: Colors.white)
),
body: _body,
// 頁面底部的導航欄
bottomNavigationBar: new CupertinoTabBar(
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: getTabIcon(0),
title: getTabTitle(0)),
new BottomNavigationBarItem(
icon: getTabIcon(1),
title: getTabTitle(1)),
new BottomNavigationBarItem(
icon: getTabIcon(2),
title: getTabTitle(2)),
new BottomNavigationBarItem(
icon: getTabIcon(3),
title: getTabTitle(3)),
],
currentIndex: _tabIndex,
// 底部Tab的點擊事件處理
onTap: (index) {
setState((){
_tabIndex = index;
});
},
),
// 側滑菜單,這裏的MyDrawer是自定義的Widget
drawer: new MyDrawer(),
),
);
}
複製代碼
Text組件是很是經常使用的組件,任何須要顯示文本的地方基本都會用到。經過查看Text類的源碼,能夠發現Text是一個無狀態的組件,下面的代碼演示瞭如何修改Text組件的字號、顏色,給字體加粗、設置下劃線、設置斜體等:
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new Text(
"Hello Flutter",
style: new TextStyle(
color: Colors.red, // 或者用這種寫法:const Color(0xFF6699FF) 必須使用AARRGGBB
fontSize: 20.0, // 字號
fontWeight: FontWeight.bold, // 字體加粗
fontStyle: FontStyle.italic, // 斜體
decoration: new TextDecoration.combine([TextDecoration.underline]) // 文本加下劃線
),
),
),
),
));
複製代碼
注意:
TextFiled組件用於文本的輸入,示例代碼以下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test")
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
maxLines: 8, // 設置輸入框顯示的最大行數(不是可輸入的最大行數)
maxLength: 30, // 設置輸入框中最多可輸入的字符數
decoration: new InputDecoration( // 給輸入框添加樣式
hintText: "Input something...", // 輸入框中placeholder文本
border: new OutlineInputBorder( // 輸入框的邊框
borderRadius: const BorderRadius.all(Radius.circular(1.0))
)
),
)
)
),
);
}
}
複製代碼
在模擬器中運行界面以下圖:
這兩個組件放到一塊兒說,是由於在處理組件的點擊事件時,會常常用到它們。 好比某個列表的item的點擊事件,某個圖標的點擊事件等等。Flutter有專門設計MaterialDesign風格的按鈕,可是更多時候咱們但願自定義按鈕樣式或者爲某個組件添加點擊事件,因此在處理點擊事件時,最多見的作法是,用InkWell或者GestureDetector將某個組件包起來。
InkWell的使用方法以下:
new InkWell(
child: new Text("Click me!"),
onTap: () {
// 單擊
},
onDoubleTap: () {
// 雙擊
},
onLongPress: () {
// 長按
}
);
複製代碼
GestureDetector用法與InkWell相似,不過GestureDetector有更多處理手勢的方法,這裏暫時不作介紹(其實我也用得很少)。
Flutter提供了幾種類型的按鈕組件:RaisedButton
FloatingActionButton
FlatButton
IconButton
PopupMenuButton
,下面用一段代碼說明這幾種按鈕的用法:
import 'package:flutter/material.dart';
main() {
runApp(new MyApp());
}
enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test')
),
body: new Column(
children: <Widget>[
new RaisedButton(
child: new Text("Raised Button"),
onPressed: (){},
),
new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: (){},
),
new FlatButton(
onPressed: (){},
child: new Text("Flat Button")
),
new IconButton(
icon: new Icon(Icons.list),
onPressed: (){}
),
new PopupMenuButton<WhyFarther>(
onSelected: (WhyFarther result) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
const PopupMenuItem<WhyFarther>(
value: WhyFarther.harder,
child: const Text('Working a lot harder'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.smarter,
child: const Text('Being a lot smarter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.selfStarter,
child: const Text('Being a self-starter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.tradingCharter,
child: const Text('Placed in charge of trading charter'),
),
],
)
],
)
)
);
}
}
複製代碼
在模擬器中上面的代碼運行效果以下圖所示:
Flutter提供了兩種類型的對話框:SimpleDialog和AlertDialog。SimpleDialog是一個能夠顯示附加的提示或操做的簡單對話框,AlertDialog則是一個會中斷用戶操做的對話框,須要用戶確認的對話框,下面用代碼來講明其用法:
import 'package:flutter/material.dart';
main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test')
),
// body: new MyAlertDialogView()
body: new MySimpleDialogView(),
),
);
}
}
class MyAlertDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('顯示AlertDialog'),
onPressed: () {
showDialog<Null>(
context: context,
barrierDismissible: false, // 不能點擊對話框外關閉對話框,必須點擊按鈕關閉
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('提示'),
content: new Text('微軟重申Windows 7將在2020年1月到達支持終點,公司但願利用這個機會說服用戶在最新更新發布以前升級到Windows 10。'),
actions: <Widget>[
new FlatButton(
child: new Text('明白了'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
);
}
}
class MySimpleDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('顯示SimpleDialog'),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return new SimpleDialog(
title: new Text('這是SimpleDialog'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('肯定'),
),
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('取消'),
),
],
);
}
);
},
);
}
}
複製代碼
上面的代碼分別展現了SimpleDialog和AlertDialog的基本用法。須要注意的是,這裏並無直接將按鈕和顯示對話框的邏輯寫到MyApp類中,而是分兩個StatelessWidget來寫的,若是你直接將按鈕及顯示對話框的邏輯寫到MyApp的build
方法裏,是會報錯的,具體報錯信息爲:
Navigator operation requested with a context that does not include a Navigator.
複製代碼
意思是導航操做須要一個不包含Navigator的上下文對象,而若是咱們將showDialog的邏輯寫到MyApp的build
方法中時,使用的是MaterialApp的上下文對象,這個上下文對象是包含Navigator的,因此就會報錯。上面的代碼在模擬器中運行效果以下圖:
Image組件用於顯示一張圖片,能夠加載本地(項目中或手機存儲中)或網絡圖片。
使用下面的方法加載一張項目中的圖片:
new Image.asset(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
複製代碼
其中path是項目中的圖片目錄。
加載項目中的圖片必定要注意編輯pubspec.yaml文件:
假設當前咱們在跟
lib/
同級的目錄下建立了images/
目錄,在images/
目錄下存放了若干圖片供項目使用,那麼必定要記得在項目根目錄下(也是跟images/
同級的目錄)編輯pubspec.yaml文件,打開pubspec.yaml文件,默認狀況下assets是被註釋了的,這裏咱們要取消註釋assets並添加images/目錄下的每一個圖片的路徑,以下圖所示:
在上圖中咱們配置了文件路徑images/ic_nav_news_normal.png
,因此能夠用下面的代碼來加載圖片了:
new Image.asset('images/ic_nav_news_normal.png', width: 20.0, height: 20.0, fit: BoxFit.cover)
複製代碼
width
和height
是圖片長寬,爲double類型,若是你傳整型20
則會報錯。 若是要加載手機存儲中的圖片,使用下面的方法:
new Image.file(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
複製代碼
fit屬性指定了圖片顯示的不一樣方式,有以下幾個值:
contain
的狀況相同,不然它與沒有同樣。
加載網絡圖片使用下面的方法:
new Image.network(imgUrl, width: 20.0, height: 20.0, fit: BoxFit.cover)
複製代碼
ListView組件用於顯示一個列表,在基於Flutter的開源中國客戶端App中,新聞列表、動彈列表等都須要用到ListView,一個最簡單的ListView能夠用以下代碼實現:
import 'package:flutter/material.dart';
void main() {
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
items.add(new Text("List Item $i"));
}
runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new ListView(children: items)
),
),
));
}
複製代碼
運行上面的代碼,結果以下圖所示:
這樣的ListView顯示不是咱們須要的,太難看,每一個item沒有邊距並且沒有分割線,因此咱們用下面的代碼改造一下:
import 'package:flutter/material.dart';
void main() {
// 裝有ListView中全部item的集合
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
var text = new Text("List Item $i");
// Padding也是一個Widget,是一個有內邊距的容器,能夠裝其餘Widget
items.add(new Padding(
// 內邊距設置爲15.0,上下左右四邊都是15.0
padding: const EdgeInsets.all(15.0),
// Padding容器中裝的是Text組件
child: text
));
}
runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
// build是ListView提供的靜態方法,用於建立ListView
child: new ListView.builder(
// itemCount是ListView的item個數,這裏之因此是items.length * 2是由於將分割線也算進去了
itemCount: items.length * 2,
itemBuilder: (context, index) {
// 若是index爲奇數,則返回分割線
if (index.isOdd) {
return new Divider(height: 1.0);
}
// 這裏index爲偶數,爲了根據下標取items中的元素,須要對index作取整
index = index ~/ 2;
return items[index];
},
)
)
),
));
}
複製代碼
此時再次運行上面的代碼,UI就好看多了:
關於ListView的用法,上面的代碼中已有相關注釋,更詳細的用法會在後面的篇幅中介紹,好比ListView中的item實現不一樣的佈局,下拉刷新,加載更多等等。
關於Flutter經常使用的部分Widget,在上面已有相關示例代碼和說明,你還能夠在Flutter中文網上查看更多組件及其用法。下一篇中我將記錄Flutter中的佈局,任何移動開發,甚至Web開發和桌面端應用開發中都不可避免的須要瞭解佈局的知識。
上一篇 | 下一篇 |
---|---|
從0開始寫一個基於Flutter的開源中國客戶端(2) ——Dart語法基礎 |
從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 |