創建 Flutter app
Android Studio上直接Flie->new->new Flutter Project創建一個應用
創建出來的項目結構如圖所示
在lib文件夾下,已經自動創建一個main.dart文件,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.red,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'添加button按鈕',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
運行main.dart後,出現
這樣一個簡單的flutter應用就創建好了。
接下來,嘗試開始自己寫一些代碼。
創建一個Hello world
在lib文件夾下新建一個dart文件,命名爲hello.dart
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {//widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
return new MaterialApp(
title: 'Hello,World',
home: new Scaffold(//提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性
appBar: new AppBar(
title: new Text('我的第一個Flutter'),
),
body: new Center(
child: new Text('Hello,World!'),//Center widget可以將其子widget樹對其到屏幕中心。
),
),
);
}
}
運行hello.dart後,出現
分析
- 本示例創建一個Material APP。Material是一種標準的移動端和web端的視覺設計語言。 Flutter提供了一套豐富的Material widgets。
- main函數使用了(=>)符號, 這是Dart中單行函數或方法的簡寫。
- 該應用程序繼承了 StatelessWidget,這將會使應用本身也成爲一個widget。 在Flutter中,大多數東西都是widget,包括對齊(alignment)、填充(padding)和佈局(layout)
- Scaffold 是 Material library 中提供的一個widget, 它提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性。widget樹可以很複雜。
- widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
- 本示例中的body的widget樹中包含了一個Center widget, Center widget又包含一個 Text 子widget。 Center widget可以將其子widget樹對其到屏幕中心。
使用外部包(package)
打開 pubspec.yaml 文件,在dependencies節點下添加english_words: ^3.1.0
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
english_words: ^3.1.0
點擊右上方Package get,加載依賴包。
在lib文件夾下,創建test1.dart文件
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main()=>runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final word = WordPair.random();
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
//child: new Text('Hello World'),
child: new Text(word.asPascalCase),
),
),
);
}
}
運行後出現
添加一個 有狀態的部件(Stateful widget)
Stateless widgets 是不可變的, 這意味着它們的屬性不能改變 - 所有的值都是最終的.
Stateful widgets 持有的狀態可能在widget生命週期中發生變化. 實現一個 stateful widget 至少需要兩個類:
1.一個 StatefulWidget類。
2.一個 State類。 StatefulWidget類本身是不變的,但是 State類在widget生命週期中始終存在.
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
return new MaterialApp(
title: 'Hello,World',
home: new Scaffold(
//提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性
appBar: new AppBar(
title: new Text('我的第一個Flutter'),
),
body: new Center(
child: new RandomWords(), //Center widget可以將其子widget樹對其到屏幕中心。
),
),
);
}
}
class RandomWords extends StatefulWidget {
@override
State createState() {
return new RandomWordsState();
}
}
class RandomWordsState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
final wordPair = new WordPair.random();
return new Text(wordPair.asPascalCase);
}
}
這樣每次熱加載都會展示不同的文字
創建一個無限滾動ListView
擴展(繼承)RandomWordsState類,以生成並顯示單詞對列表。 當用戶滾動時,ListView中顯示的列表將無限增長。 ListView的builder工廠構造函數允許您按需建立一個懶加載的列表視圖。
1.向RandomWordsState類中添加一個_suggestions列表以保存建議的單詞對。 該變量以下劃線(_)開頭,在Dart語言中使用下劃線前綴標識符,會強制其變成私有的。
另外,添加一個biggerFont變量來增大字體大小
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
...
}
2.向RandomWordsState類添加一個 _buildSuggestions() 函數. 此方法構建顯示建議單詞對的ListView。
ListView類提供了一個builder屬性,itemBuilder 值是一個匿名回調函數, 接受兩個參數- BuildContext和行迭代器i。迭代器從0開始, 每調用一次該函數,i就會自增1,對於每個建議的單詞對都會執行一次。該模型允許建議的單詞對列表在用戶滾動時無限增長。
class RandomWordsState extends State<RandomWords> {
...
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
// 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
// 在偶數行,該函數會爲單詞對添加一個ListTile row.
// 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
// 注意,在小屏幕上,分割線看起來可能比較吃力。
itemBuilder: (context, i) {
// 在每一列之前,添加一個1像素高的分隔線widget
if (i.isOdd) return new Divider();
// 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
// 時,結果爲0, 1, 1, 2, 2, 這可以計算出ListView中減去分隔線後的實際單詞對數量
final index = i ~/ 2;
// 如果是建議列表中最後一個單詞對
if (index >= _suggestions.length) {
// ...接着再生成10個單詞對,然後添加到建議列表
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
}
);
}
}
3.對於每一個單詞對,_buildSuggestions函數都會調用一次_buildRow。 這個函數在ListTile中顯示每個新詞對,這使您在下一步中可以生成更漂亮的顯示行
在RandomWordsState中添加一個_buildRow函數:
class RandomWordsState extends State<RandomWords> {
...
Widget _buildRow(WordPair pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
}
4.更新RandomWordsState的build方法以使用_buildSuggestions(),而不是直接調用單詞生成庫。
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListView"),
),
body: _buildSuggestions(),
);
}
5.更新MyApp的build方法。從MyApp中刪除Scaffold和AppBar實例。 這些將由RandomWordsState管理,這使得用戶在下一步中從一個屏幕導航到另一個屏幕時, 可以更輕鬆地更改導航欄中的的路由名稱。
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
}
總體代碼:
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main()=>runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
}
class RandomWords extends StatefulWidget{
@override
State createState() {
return new RandomWordsState();
}
}
class RandomWordsState extends State<RandomWords>{
final _suggestion = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListView"),
),
body: _buildSuggestions(),
);
}
Widget _buildSuggestions(){
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
// 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
// 在偶數行,該函數會爲單詞對添加一個ListTile row.
// 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
// 注意,在小屏幕上,分割線看起來可能比較吃力。
itemBuilder: (context,i){
// 在每一列之前,添加一個1像素高的分隔線widget
if (i.isOdd) return new Divider();
// 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
// 時,結果爲0, 1, 1, 2, 2, 這可以計算出ListView中減去分隔線後的實際單詞對數量
final index = i~/2;
// 如果是建議列表中最後一個單詞對
if(index >= _suggestion.length){
// ...接着再生成10個單詞對,然後添加到建議列表
_suggestion.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestion[index]);
},
);
}
Widget _buildRow(WordPair pair){
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
}
效果如圖:
添加交互
爲每一行添加一個可點擊的心形 ❤️ 圖標。當用戶點擊列表中的條目,切換其“收藏”狀態時,將該詞對添加到或移除出“收藏夾”。
1.添加一個 _saved Set(集合) 到RandomWordsState。這個集合存儲用戶喜歡(收藏)的單詞對。 在這裏,Set比List更合適,因爲Set中不允許重複的值。
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = new Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18.0);
...
}
2.在 _buildRow 方法中添加 alreadySaved來檢查確保單詞對還沒有添加到收藏夾中。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
...
}
3.同時在 _buildRow()中, 添加一個心形 ❤️ 圖標到 ListTiles以啓用收藏功能。接下來,你就可以給心形 ❤️ 圖標添加交互能力了。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
);
}
4.重新啓動應用。你現在可以在每一行看到心形❤️圖標️,但它們還沒有交互。
5.在 _buildRow中讓心形❤️圖標變得可以點擊。如果單詞條目已經添加到收藏夾中, 再次點擊它將其從收藏夾中刪除。當心形❤️圖標被點擊時,函數調用setState()通知框架狀態已經改變。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
);
}
導航到新頁面
添加一個顯示收藏夾內容的新頁面(在Flutter中稱爲路由(route))
在Flutter中,導航器管理應用程序的路由棧。將路由推入(push)到導航器的棧中,將會顯示更新爲該路由頁面。 從導航器的棧中彈出(pop)路由,將顯示返回到前一個路由。
1.在RandomWordsState的build方法中爲AppBar添加一個列表圖標。當用戶點擊列表圖標時,包含收藏夾的新路由頁面入棧顯示。
將該圖標及其相應的操作添加到build方法中:
class RandomWordsState extends State<RandomWords> {
...
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Startup Name Generator'),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
],
),
body: _buildSuggestions(),
);
}
...
}
2.向RandomWordsState類添加一個 _pushSaved() 方法.
class RandomWordsState extends State<RandomWords> {
...
void _pushSaved() {
}
}
熱重載應用,列表圖標將會出現在導航欄中。現在點擊它不會有任何反應,因爲 _pushSaved 函數還是空的。
3.當用戶點擊導航欄中的列表圖標時,建立一個路由並將其推入到導航管理器棧中。此操作會切換頁面以顯示新路由。
新頁面的內容在在MaterialPageRoute的builder屬性中構建,builder是一個匿名函數。
添加Navigator.push調用,這會使路由入棧(以後路由入棧均指推入到導航管理器的棧)
void _pushSaved() {
Navigator.of(context).push(
);
}
4.添加MaterialPageRoute及其builder。 現在,添加生成ListTile行的代碼。ListTile的divideTiles()方法在每個ListTile之間添加1像素的分割線。 該 divided 變量持有最終的列表項。
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
final tiles = _saved.map(
(pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
},
),
);
}
5.builder返回一個Scaffold,其中包含名爲“Saved Suggestions”的新路由的應用欄。 新路由的body由包含ListTiles行的ListView組成; 每行之間通過一個分隔線分隔。
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
final tiles = _saved.map(
(pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return new Scaffold(
appBar: new AppBar(
title: new Text('Saved Suggestions'),
),
body: new ListView(children: divided),
);
},
),
);
}
6.熱重載應用程序。收藏一些選項,並點擊應用欄中的列表圖標,在新路由頁面中顯示收藏的內容。 請注意,導航器會在應用欄中添加一個“返回”按鈕。你不必顯式實現Navigator.pop。點擊後退按鈕返回到主頁路由。
使用主題更改UI
使用主題。主題控制您應用程序的外觀和風格。您可以使用默認主題,該主題取決於物理設備或模擬器,也可以自定義主題以適應您的品牌。
1.可以通過配置ThemeData類輕鬆更改應用程序的主題。 您的應用程序目前使用默認主題,下面將更改primary color顏色爲白色。
將應用程序的主題更改爲白色:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new RandomWords(),
);
}
}
2.熱重載應用。 請注意,整個背景將會變爲白色,包括應用欄。
使用 ThemeData 來改變UI的其他方面。 Material library中的 Colors類提供了許多可以使用的顏色常量, 可以使用熱重載來快速簡單地嘗試、實驗