这本食谱包含演示如何在写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); }, ), ], ), ), ); } }