Flutter 構建完整應用手冊-設計基礎知識

這本食譜包含演示如何在寫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

若是咱們不想繼承任何應用程序的顏色或字體樣式,咱們能夠建立一個新的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),
        ),
      ),
    );
  }
}

顯示SnackBars

在某些狀況下,當發生某些操做時能夠方便地向用戶簡單通知。 例如,當用戶在列表中刪除消息時,咱們可能想通知他們消息已被刪除。 咱們甚至可能想給他們一個撤消行爲的選擇!

在Material Design中,這是SnackBar的工做。

路線

  1. 建立一個Scaffold
  2. 顯示一個SnackBar
  3. 提供額外的操做

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包含建立選項卡布局做爲材料庫的一部分的便捷方式。

路線

  1. 建立一個TabController

  2. 建立選項卡

  3. 爲每一個選項卡建立內容

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使用自定義字體開箱即用。 咱們能夠將字體應用到整個應用程序或個別小部件。

路線

  • 導入字體文件
  • pubspec.yaml中聲明該字體
  • 將字體設置爲默認值
  • 在特定的部件中使用字體

1.導入字體文件

爲了處理字體,咱們須要將字體文件導入到項目中。 將字體文件放在Flutter項目的根目錄下的fontsassets文件夾中是很常見的作法。

例如,若是咱們想將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包中。

單個字體能夠引用具備不一樣輪廓重量和樣式的許多不一樣文件:

  • weight屬性指定文件中輪廓線的權重爲100到900之間的整數倍。這些值對應於FontWeight,可用於TextStyle對象的fontWeight屬性。
  • style屬性指定文件中的輪廓是italic仍是normal。 這些值對應於FontStyle,可用於TextStyle對象的fontStyle屬性。

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。

在這種狀況下,咱們須要建立一個帶DrawerScaffold

new Scaffold(
  drawer: // We'll add our Drawer here in the next step!
);

2.添加一個Drawer

咱們如今能夠爲咱們的Scaffold增長一個DrawerDrawer能夠是任何部件,但一般最好使用材質庫中的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);
              },
            ),
          ],
        ),
      ),
    );
  }
}

相關文章
相關標籤/搜索