這本食譜包含演示如何在寫Flutter應用程序時解決常見問題的食譜。 每一個配方都是獨立的,能夠做爲參考幫助您構建應用程序。html
爲了在整個應用中共享顏色和字體樣式,咱們能夠利用主題。定義主題有兩種方式:應用程序範圍或使用Theme小部件來定義應用程序特定部分的顏色和字體樣式。事實上,應用程序範圍的主題只是由MaterialApp在應用程序根部建立的主題小部件!java
在咱們定義一個主題後,咱們能夠在本身的部件中使用它。另外,Flutter提供的Material Widgets將使用咱們的主題爲AppBars,Buttons,Checkboxes等設置背景顏色和字體樣式。web
爲了在整個應用程序中共享包含顏色和字體樣式的主題,咱們能夠將ThemeData提供給MaterialApp構造函數。編程
若是沒有提供Theme,Flutter將在後臺建立一個後備主題。app
new MaterialApp( title: title, theme: new ThemeData( brightness: Brightness.dark, primaryColor: Colors.lightBlue[800], accentColor: Colors.cyan[600], ), );
請參閱ThemeData文檔以查看您能夠定義的全部顏色和字體。less
若是咱們想在咱們的應用程序的一部分中覆蓋應用程序範圍的主題,咱們能夠將咱們的應用程序的一部分包裝在Theme小部件中。ide
有兩種方法能夠解決這個問題:建立惟一的ThemeData,或者擴展父主題。函數
若是咱們不想繼承任何應用程序的顏色或字體樣式,咱們能夠建立一個新的ThemeData()實例並將其傳遞給Theme部件。工具
new Theme( // Create a unique theme with "new ThemeData" data: new ThemeData( accentColor: Colors.yellow, ), child: new FloatingActionButton( onPressed: () {}, child: new Icon(Icons.add), ), );
擴展父主題佈局
擴展父主題一般是有意義的,而不是覆蓋全部。 咱們能夠經過使用copyWith方法來實現這一點。
new Theme( // Find and Extend the parent theme using "copyWith". Please see the next // section for more info on `Theme.of`. data: Theme.of(context).copyWith(accentColor: Colors.yellow), child: new FloatingActionButton( onPressed: null, child: new Icon(Icons.add), ), );
如今咱們已經定義了一個主題,咱們能夠使用Theme.of(context)函數在咱們的部件build方法中使用它!
Theme.of(context)將查找部件樹並返回樹中最近的Theme。 若是咱們的部件上方定義了獨立的Theme,則返回該Theme。 若是不是,則返回應用程序範圍Theme。
實際上,FloatingActionButton使用這種精確的技術來查找accentColor!
new Container( color: Theme.of(context).accentColor, child: new Text( 'Text with a background color', style: Theme.of(context).textTheme.title, ), );
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final appName = 'Custom Themes'; return new MaterialApp( title: appName, theme: new ThemeData( brightness: Brightness.dark, primaryColor: Colors.lightBlue[800], accentColor: Colors.cyan[600], ), home: new MyHomePage( title: appName, ), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({Key key, @required this.title}) : super(key: key); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(title), ), body: new Center( child: new Container( color: Theme.of(context).accentColor, child: new Text( 'Text with a background color', style: Theme.of(context).textTheme.title, ), ), ), floatingActionButton: new Theme( data: Theme.of(context).copyWith(accentColor: Colors.yellow), child: new FloatingActionButton( onPressed: null, child: new Icon(Icons.add), ), ), ); } }
在某些狀況下,當發生某些操做時能夠方便地向用戶簡單通知。 例如,當用戶在列表中刪除消息時,咱們可能想通知他們消息已被刪除。 咱們甚至可能想給他們一個撤消行爲的選擇!
在Material Design中,這是SnackBar的工做。
1.建立一個Scaffold
在建立遵循材質設計指南的應用程序時,咱們但願爲咱們的應用程序提供一致的可視化結構。 在這種狀況下,咱們須要在屏幕底部顯示SnackBar,而不會與其它重要的部件重疊,例如FloatingActionButton!
材料庫中的Scaffold部件爲咱們建立了這個視覺結構,並確保重要的部件不會重疊!
new Scaffold( appBar: new AppBar( title: new Text('SnackBar Demo'), ), body: new SnackBarPage(), // We'll fill this in below! );
2.顯示一個SnackBar
使用Scaffold,咱們能夠展現SnackBar! 首先,咱們須要建立一個SnackBar,而後使用Scaffold顯示它。
final snackBar = new SnackBar(content: new Text('Yay! A SnackBar!')); // Find the Scaffold in the Widget tree and use it to show a SnackBar Scaffold.of(context).showSnackBar(snackBar);
3.提供額外的操做
在某些狀況下,咱們可能但願在顯示SnackBar時向用戶提供額外的操做。 例如,若是他們意外刪除了一條消息,咱們能夠提供撤消該更改的操做。
爲了達到這個目的,咱們能夠爲SnackBar部件提供額外的action。
final snackBar = new SnackBar( content: new Text('Yay! A SnackBar!'), action: new SnackBarAction( label: 'Undo', onPressed: () { // Some code to undo the change! }, ), );
注意:在本例中,咱們將在用戶點擊按鈕時顯示SnackBar。 有關處理用戶輸入的更多信息,請參閱食譜的處理手勢部分。
import 'package:flutter/material.dart'; void main() => runApp(new SnackBarDemo()); class SnackBarDemo extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'SnackBar Demo', home: new Scaffold( appBar: new AppBar( title: new Text('SnackBar Demo'), ), body: new SnackBarPage(), ), ); } } class SnackBarPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Center( child: new RaisedButton( onPressed: () { final snackBar = new SnackBar( content: new Text('Yay! A SnackBar!'), action: new SnackBarAction( label: 'Undo', onPressed: () { // Some code to undo the change! }, ), ); // Find the Scaffold in the Widget tree and use it to show a SnackBar! Scaffold.of(context).showSnackBar(snackBar); }, child: new Text('Show SnackBar'), ), ); } }
使用選項卡是遵循Material Design指南的應用程序中的常見模式。 Flutter包含建立選項卡布局做爲材料庫的一部分的便捷方式。
建立一個TabController
建立選項卡
爲每一個選項卡建立內容
1.建立一個TabController
爲了讓選項卡工做,咱們須要保持所選選項卡和內容部分的同步。 這是TabController的工做。
咱們能夠手動建立TabController或使用DefaultTabController部件。 使用DefaultTabController是最簡單的選擇,由於它會爲咱們建立一個TabController並使其可供全部後代控件使用。
new DefaultTabController( // The number of tabs / content sections we need to display length: 3, child: // See the next step! );
2.建立選項卡
既然咱們有一個TabController能夠使用,咱們能夠使用TabBar部件建立咱們的選項卡。 在這個例子中,咱們將建立一個帶有3個Tab小部件的TabBar,並將其放置在AppBar中。
new DefaultTabController( length: 3, child: new Scaffold( appBar: new AppBar( bottom: new TabBar( tabs: [ new Tab(icon: new Icon(Icons.directions_car)), new Tab(icon: new Icon(Icons.directions_transit)), new Tab(icon: new Icon(Icons.directions_bike)), ], ), ), ), );
默認狀況下,TabBar查找最近的DefaultTabController的部件樹。 若是你手動建立一個TabController,你須要將它傳遞給TabBar。
3.爲每一個選項卡建立內容
如今咱們有了選項卡,咱們但願在選擇標籤時顯示內容。 爲此,咱們將使用TabBarView部件。
注意:順序很重要,必須與TabBar中的選項卡順序相對應!
new TabBarView( children: [ new Icon(Icons.directions_car), new Icon(Icons.directions_transit), new Icon(Icons.directions_bike), ], );
import 'package:flutter/material.dart'; void main() { runApp(new TabBarDemo()); } class TabBarDemo extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( home: new DefaultTabController( length: 3, child: new Scaffold( appBar: new AppBar( bottom: new TabBar( tabs: [ new Tab(icon: new Icon(Icons.directions_car)), new Tab(icon: new Icon(Icons.directions_transit)), new Tab(icon: new Icon(Icons.directions_bike)), ], ), title: new Text('Tabs Demo'), ), body: new TabBarView( children: [ new Icon(Icons.directions_car), new Icon(Icons.directions_transit), new Icon(Icons.directions_bike), ], ), ), ), ); } }
雖然Android和iOS提供高質量的系統字體,但設計師最多見的要求之一是使用自定義字體! 例如,咱們可能會從咱們的設計人員那裏得到一個定製的字體,或者從谷歌字體中下載一種字體。
Flutter使用自定義字體開箱即用。 咱們能夠將字體應用到整個應用程序或個別小部件。
1.導入字體文件
爲了處理字體,咱們須要將字體文件導入到項目中。 將字體文件放在Flutter項目的根目錄下的fonts或assets文件夾中是很常見的作法。
例如,若是咱們想將Raleway和Roboto Mono字體文件導入到咱們的項目中,那麼文件夾結構以下所示:
awesome_app/ fonts/ Raleway-Regular.ttf Raleway-Italic.ttf RobotoMono-Regular.ttf RobotoMono-Bold.ttf
2.在pubspec.yaml中聲明該字體
如今咱們有一個能夠使用的字體,咱們須要告訴Flutter在哪裏找到它。 咱們能夠經過在pubspec.yaml中包含一個字體定義來實現。
flutter: fonts: - family: Raleway fonts: - asset: fonts/Raleway-Regular.ttf - asset: fonts/Raleway-Italic.ttf style: italic - family: RobotoMono fonts: - asset: fonts/RobotoMono-Regular.ttf - asset: fonts/RobotoMono-Bold.ttf weight: 700
pubspec.yaml選項定義
family決定字體的名稱,咱們能夠在TextStyle對象的fontFamily屬性中使用它。
asset是相對於pubspec.yaml文件的字體文件的路徑。 這些文件包含字體中字形的輪廓。 在構建咱們的應用程序時,這些文件包含在咱們應用程序的asset包中。
單個字體能夠引用具備不一樣輪廓重量和樣式的許多不一樣文件:
3.將字體設置爲默認值
對於如何將字體應用於文本,咱們有兩種選擇:做爲默認字體或僅在特定的小部件中。
要使用字體做爲默認字體,咱們能夠將fontFamily屬性設置爲應用theme的一部分。 咱們提供給fontFamily的值必須與pubspec.yaml中聲明的family相匹配。
new MaterialApp( title: 'Custom Fonts', // Set Raleway as the default app font theme: new ThemeData(fontFamily: 'Raleway'), home: new MyHomePage(), );
有關主題的更多信息,請查看「使用主題共享顏色和字體樣式」配方。
4.在特定的部件中使用字體
若是咱們想將字體應用於特定的部件,好比Text部件,咱們能夠向部件提供一個TextStyle。
在這個例子中,咱們將RobotoMono字體應用於單個Text部件。fontFamily再一次必須與咱們在pubspec.yaml中聲明的family相匹配。
new Text( 'Roboto Mono sample', style: new TextStyle(fontFamily: 'RobotoMono'), );
TextStyle
若是TextStyle對象指定沒有確切字體文件的權重或樣式,則該引擎使用該字體的更通用文件之一,並嘗試針對所請求的權重和樣式推斷輪廓。
Fonts
Raleway和RobotoMono字體是從谷歌字體下載的。
pubspec.yaml
name: custom_fonts description: An example of how to use custom fonts with Flutter dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: fonts: - family: Raleway fonts: - asset: fonts/Raleway-Regular.ttf - asset: fonts/Raleway-Italic.ttf style: italic - family: RobotoMono fonts: - asset: fonts/RobotoMono-Regular.ttf - asset: fonts/RobotoMono-Bold.ttf weight: 700 uses-material-design: true
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: 'Custom Fonts', // Set Raleway as the default app font theme: new ThemeData(fontFamily: 'Raleway'), home: new MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( // The AppBar will use the app-default Raleway font appBar: new AppBar(title: new Text('Custom Fonts')), body: new Center( // This Text Widget will use the RobotoMono font child: new Text( 'Roboto Mono sample', style: new TextStyle(fontFamily: 'RobotoMono'), ), ), ); } }
咱們能夠將字體聲明爲單獨程序包的一部分,而不是將字體聲明爲咱們的應用程序的一部分。 這是一種方便的方式,能夠跨幾個不一樣的項目共享相同的字體,也能夠將包發佈到 pub website。
1.將字體添加到包中
要從包中導出字體,咱們須要將字體文件導入到咱們包項目的lib文件夾中。 咱們能夠將字體文件直接放在lib文件夾或子目錄中,好比lib/fonts。
在這個例子中,咱們假設咱們有一個名爲awesome_package的Flutter庫,其中的字體位於lib/fonts文件夾中。
awesome_package/ lib/ awesome_package.dart fonts/ Raleway-Regular.ttf Raleway-Italic.ttf
2.將包和字體添加到咱們的應用程序
咱們如今能夠使用該包並使用它提供的字體。 這涉及到更新應用根目錄中的pubspec.yaml。
將該包添加到項目中
dependencies: awesome_package: <latest_version>
聲明字體assets
如今咱們已經導入了包,咱們須要告訴Flutter從awesome_package中找到哪些字體。
要聲明包字體,咱們必須用packages/awesome_package前綴到字體的路徑。 這將告訴Flutter查看包的字體的lib文件夾。
flutter: fonts: - family: Raleway fonts: - asset: packages/awesome_package/fonts/Raleway-Regular.ttf - asset: packages/awesome_package/fonts/Raleway-Italic.ttf style: italic
3.使用字體
咱們能夠使用TextStyle來改變文本的外觀。 要使用包字體,咱們不只須要聲明咱們想要使用哪一種字體,還須要聲明字體所屬的package。
new Text( 'Using the Raleway font from the awesome_package', style: new TextStyle( fontFamily: 'Raleway', package: 'awesome_package', ), );
Font
Raleway和RobotoMono字體是從谷歌字體下載的。
pubspec.yaml
name: package_fonts description: An example of how to use package fonts with Flutter dependencies: awesome_package: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: fonts: - family: Raleway fonts: - asset: packages/awesome_package/fonts/Raleway-Regular.ttf - asset: packages/awesome_package/fonts/Raleway-Italic.ttf style: italic uses-material-design: true
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: 'Package Fonts', home: new MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( // The AppBar will use the app-default Raleway font appBar: new AppBar(title: new Text('Package Fonts')), body: new Center( // This Text Widget will use the RobotoMono font child: new Text( 'Using the Raleway font from the awesome_package', style: new TextStyle( fontFamily: 'Raleway', package: 'awesome_package', ), ), ), ); } }
在採用Material Design的應用中,導航有兩個主要選項:選項卡和抽屜。 當沒有足夠的空間來支持標籤時,抽屜提供了一個方便的選擇。
在Flutter中,咱們能夠將Drawer小工具與Scaffold結合使用,以建立帶有材質設計Drawer的佈局!
1.建立一個Scaffold
爲了將Drawer添加到咱們的應用程序中,咱們須要將其包裝在Scaffold部件中。 Scaffold部件爲遵循Material Design Guidelines的應用程序提供了一致的可視化結構。 它還支持特殊的Material Design組件,例如Drawers,AppBars和SnackBars。
在這種狀況下,咱們須要建立一個帶Drawer的Scaffold:
new Scaffold( drawer: // We'll add our Drawer here in the next step! );
2.添加一個Drawer
咱們如今能夠爲咱們的Scaffold增長一個Drawer。 Drawer能夠是任何部件,但一般最好使用材質庫中的Drawer部件,該材質庫遵照材質設計規範。
new Scaffold( drawer: new Drawer( child: // We'll populate the Drawer in the next step! ) );
3.用條目填充Drawer
如今咱們有了一個Drawer,咱們能夠添加內容! 在這個例子中,咱們將使用一個ListView。 儘管咱們能夠使用Column部件,但ListView在這種狀況下很方便,由於若是內容佔用的空間比屏幕支持的更多,它將容許用戶滾動抽屜。
咱們將用一個DrawerHeader和兩個ListTile部件填充ListView。 有關使用列表的更多信息,請參閱列表配方。
new Drawer( // Add a ListView to the drawer. This ensures the user can scroll // through the options in the Drawer if there isn't enough vertical // space to fit everything. child: new ListView( // Important: Remove any padding from the ListView. padding: EdgeInsets.zero, children: <Widget>[ new DrawerHeader( child: new Text('Drawer Header'), decoration: new BoxDecoration( color: Colors.blue, ), ), new ListTile( title: new Text('Item 1'), onTap: () { // Update the state of the app // ... }, ), new ListTile( title: new Text('Item 2'), onTap: () { // Update the state of the app // ... }, ), ], ), );
4.以編程方式關閉Drawer
用戶點擊物品後,咱們常常想要關閉抽屜。 咱們怎樣才能作到這一點? 使用Navigator!
當用戶打開抽屜時,Flutter會將抽屜添加到引擎蓋下的導航堆棧中。 所以,要關閉抽屜,咱們能夠調用Navigator.pop(context)。
new ListTile( title: new Text('Item 1'), onTap: () { // Update the state of the app // ... // Then close the drawer Navigator.pop(context); }, ),
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { final appTitle = 'Drawer Demo'; @override Widget build(BuildContext context) { return new MaterialApp( title: appTitle, home: new MyHomePage(title: appTitle), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({Key key, this.title}) : super(key: key); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: new Text(title)), body: new Center(child: new Text('My Page!')), drawer: new Drawer( // Add a ListView to the drawer. This ensures the user can scroll // through the options in the Drawer if there isn't enough vertical // space to fit everything. child: new ListView( // Important: Remove any padding from the ListView. padding: EdgeInsets.zero, children: <Widget>[ new DrawerHeader( child: new Text('Drawer Header'), decoration: new BoxDecoration( color: Colors.blue, ), ), new ListTile( title: new Text('Item 1'), onTap: () { // Update the state of the app // ... // Then close the drawer Navigator.pop(context); }, ), new ListTile( title: new Text('Item 2'), onTap: () { // Update the state of the app // ... // Then close the drawer Navigator.pop(context); }, ), ], ), ), ); } }