• Mdc-104: Material Advanced Components (Flutter)
  • The original author: codelabs.developers.google.com
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: DevMcryYu
  • Proofreader: Iceytea

1. Introduction

The Material Component (MDC) helps developers implement Material Design. MDC was created by the Google team of engineers and UX designers to provide a number of beautiful and useful UI components for Android, iOS, the Web and Flutter.

In the MDC-103 tutorial, customize the Material component (MDC) colors, heights, typography, and shapes to style your application.

Components in the Material Design system perform some predefined work and have certain characteristics, such as a button. However, a button is not just for the user to perform actions, it can express a visual experience with its shape, size and color, letting the user know that it is interactive and that something can happen when touched or clicked.

The Material Design guide describes components from a designer’s perspective. They describe the basic capabilities available across platforms and the basic elements that make up each component. For example, a background contains a back layer of content, the front layer of content and its own content, motion rules, and display options. Each component can be customized according to the requirements, use cases, and content of each application, including traditional views, controls, and the functionality of your platform’S SDK.

The Material Design guide names a number of components, but not all of them are well reusable and therefore cannot be found in MDC. You can shape the experience yourself by using traditional code to customize your application style.

You’re going to build one

In this tutorial, you will modify the UI of the Shrine application to a two-level display called “Background”. It contains a menu that lists optional categories for filtering products displayed in asymmetrical grids. In this tutorial you will use the following Flutter components:

  • Shape
  • Motion (Motion)
  • The Flutter widget (used in previous tutorials)

This is the last of four tutorials that will guide you through building an app called Shrine. We recommend that you read each tutorial and follow the progress through the project.

Tutorials can be found here:

  • Mdc-101: Material Component (MDC) foundation
  • Mdc-102: Material Design structure and layout.
  • Mdc-103: Material Design Theming color, shape, height and type

The MDC-FLUTTER component in this tutorial

  • Shape

You will need to

  • Flutter SDK
  • Install the Flutter plugin in Android Studio, or your favorite code editor
  • The sample code

To build and run the Flutter application on iOS, you need to meet the following requirements:

  • A computer running macOS
  • Xcode 9 or later
  • IOS emulators, or iOS physical devices

To build and run the Flutter application on Android, you need to meet the following requirements:

  • A computer running macOS, Windows, or Linux
  • Android Studio
  • Android emulator (shipped with Android Studio) or Android physical device

2. Install the Flutter environment

The premise condition

To start developing mobile apps with Flutter, you need to:

  • Flutter SDK
  • IntelliJ IDE with the Flutter plugin, or your favorite code editor

Flutter’s IDE tools are available for Android Studio, IntelliJ IDEA Community (free) and IntelliJ IDEA Ultimate.

To build and run the Flutter application on iOS, you need to meet the following requirements:

  • A computer running macOS
  • Xcode 9 or later
  • IOS emulators, or iOS physical devices

To build and run the Flutter application on Android, you need to meet the following requirements:

  • A computer running macOS, Windows, or Linux
  • Android Studio
  • Android emulator (shipped with Android Studio) or Android physical device

Get detailed Flutter installation information

Important: If the Allow USB Debugging dialog box appears on your Android phone connected to your computer, enable the Always Allow from this computer option, and then click OK.

Before continuing with this tutorial, make sure your SDK is in the right state. If you have previously installed the Flutter SDK, use The Flutter Upgrade to ensure that the SDK is up to date.

flutter upgrade
Copy the code

Running flutter Upgrade will automatically run flutter Doctor. If this is the first time that a Flutter is installed and no upgrade is required, run the Flutter Doctor manually. View all the ✓ markers displayed. This will download any missing SDK files you need and make sure your computer is properly configured to develop Flutter.

flutter doctor
Copy the code

3. Download the tutorial starter application

Continue from MDC-103?

If you have completed MDC-103, the code for this tutorial should be in place. Jump to: Add Background menu.

From scratch?

Download the starter program

The initial program is located in the material-components-flutter-codelabs-104-starter_and_103-complete/ mdC_100_series directory.

. Or clone it from GitHub

Clone the project from GitHub and run the following command:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs
git checkout 104-starter\_and\_103-complete
Copy the code

More help: Clone a repository from GitHub

Right branch

Tutorials MDC-101 through MDC-104 continue to build on the previous one. The complete code for MDC-103 will be the original code for MDC-104. The code is split into branches. To list branches in GitHub, use the following command:

git branch --list

To see the complete code, switch to the 104-complete branch.

Build your project

The default for the following steps is Android Studio (IntelliJ).

Create a project

  1. In terminal, navigate to the Material – Components -flutter- Codelabs

  2. Run the FLUTTER create MDC_100_series

Open the project

  1. Open Android Studio.

  2. If you see the welcome page, click to open your existing Android Studio project.

  1. Navigate to thematerial-components-flutter-codelabs/mdc_100_seriesDirectory and click Open, which will open the project.

You can ignore any errors you see in the analysis until you build the project once.

  1. In the project panel on the left, if you see the test file../test/widget_test.dartDelete it.

  1. If the prompt shown above, all platforms and plug-in installation update or FlutterRunConfigurationType, and then restart the Android Studio.

Tip: Make sure you have the Flutter and Dart plug-ins installed.

Run the initial program

The following steps are performed by default on an Android emulator or a real device. If you have Xcode installed, you can also test it on an iOS emulator or device.

  1. Select device or emulator

If the Andorid emulator is not already running, choose Tools -> Android -> AVD Manager to create and run an emulator device. If the AVD already exists, you can start the emulator directly from IntelliJ’s device selector, as shown in the next step.

(For iOS emulators, if it is not already running, start it on your development Device by selecting Flutter Device Selection -> Open iOS Simulator.)

  1. Start Flutter application:
  • Look for the Flutter Device Selection drop-down menu at the top of your editor window and select the Device (for example, iPhone SE/Android SDK built for < Version >).
  • Click on therunIcon (

    ).

If you cannot run the application successfully, stop to resolve your development environment issues. Try to navigate to the Material – Components -flutter-codelabs; If you download the.zip file in terminal, navigate to the material-components-flutter-codelabs-… Then run the FLUTTER create MDC_100_series.

Success! The Shrine login page from the previous tutorial should now be running in your emulator. You can see the logo of Shrine and the name “Shrine” underneath it.

If the app is not updated, click the “Play” button again, or click the “Stop” after “Play”.

4. Add the background menu

The background appears after all other content and components. It consists of two layers: a back layer (displaying actions and filters) and a front layer (displaying content). You can use backgrounds to display interactive information and actions, such as navigation or content filtering.

Remove the Home app bar

The widget of a HomePage will become the content of the previous layer. Now it has an app bar. We moved the app bar to the back so that our HomePage will only contain an AsymmetricView.

In home.dart, modify the build() method so that it returns only an AsymmetricView:

/ / TODO: returns a AsymmetricView (104) return AsymmetricView (products: ProductsRepository loadProducts (Category. All));Copy the code

Add a background widget

Create a widget named Backdrop that contains both frontLayer and backLayer.

BackLayer contains a menu that allows you to select a category to filter the list (currentCategory). Because we want the menu choices to remain the same, we inherit StatefulWidget from Backdrop.

Add a file named broadflow.dart under /lib:

    import 'package:flutter/material.dart';
    import 'package:meta/meta.dart';

    import 'model/product.dart'; // Class data extends StatefulWidget {final Category currentCategory; final Widget frontLayer; final Widget backLayer; final Widget frontTitle; final Widget backTitle; const Backdrop({ @required this.currentCategory, @required this.frontLayer, @required this.backLayer, @required this.frontTitle, @required this.backTitle, }) : assert(currentCategory ! = null), assert(frontLayer ! = null), assert(backLayer ! = null), assert(frontTitle ! = null), assert(backTitle ! = null); @override _BackdropState createState() => _BackdropState(); // TODO: add _FrontLayer class (104) // TODO: Add _BackdropTitle class (104) // TODO: add _BackdropState class (104)Copy the code

Import the meta package to add the @required tag. It is used to remind you not to miss when a property in a constructor has no default value and cannot be empty. Notice that we once again declare after the constructor that the value passed in is indeed not NULL.

Add the _BackdropState class to the Backdrop class:

/ / TODO: Add _BackdropState class (104) class _BackdropState extends the State < Backdrop > with SingleTickerProviderStateMixin {final GlobalKey _backdropKey = GlobalKey(debugLabel:'Backdrop'); // TODO: Add the AnimationController Widget (104) // TODO: add BuildContext and BoxConstraints parameters (104) widgets for _buildStack_buildStack() {
        returnStack( key: _backdropKey, children: <Widget>[ widget.backLayer, widget.frontLayer, ], ); } @override Widget build(BuildContext context) { var appBar = AppBar( brightness: Brightness.light, elevation: 0.0, titleSpacing: // TODO: // TODO: // TODO: // TODO: Create title (104) leading: Icon(icons.menu), title: Text('SHRINE'[// TODO: add a shortcut from the tail icon to the landing page (104) IconButton(icon: icon (Icons. Search, semanticLabel:'search',), onPressed: () {// TODO: open login (104)},), IconButton(icon: icon (Icons. Tune, semanticLabel:'filter',), onPressed: () {/ / TODO: open the login (104)},),,);returnScaffold(appBar: appBar, // TODO: returns a LayoutBuilder widget (104) body: _buildStack(),); }}Copy the code

The build() method returns a Scaffold with an app bar like a HomePage. But the backbone of that Scaffold is a Stack. The children of the Stack can overlap. Each child’s size and position are specified relative to the parent of the Stack.

Now add a Backdrop instance to the ShrineApp.

/ / Setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup setup

    import 'backdrop.dart'; // Add code import'colors.dart';
    import 'home.dart';
    import 'login.dart';
    import 'model/product.dart'; // Add code import'supplemental/cut_corners_border.dart';
Copy the code

Modify the build() method of ShrineApp in app.dart. Change home: to gorgeous Backdrop with HomePage as frontLayer.

// HomePage frontLayer gorgeous Backdrop (104) home: // Add/add/add/add/add/add/add/add All, // TODO: Pass _currentCategory (104) to frontLayer: HomePage(), // TODO: change backLayer to CategoryMenuPage (104) backLayer: Container(color: kShrinePink100), frontTitle: Text('SHRINE'),
          backTitle: Text('MENU'),),Copy the code

If you click the Run button, you’ll see the home page and the App bar already appear:

BackLayer inserts a new pink background behind the frontLayer’s home page.

You can use the Flutter Inspector to verify that there is a container behind the main page in the Stack. Something like this:

Now you can adjust the design and content of both layers.

5. Add Shape

In this section, you will style the frontLayer to add a slice in its upper left corner.

Material Design refers to such customizations as shapes. The Material surface can have any shape. Shapes add focus and style to surfaces that can be used to express brand identity. Ordinary rectangular shapes can be customized to have curved or angled corners and edges, as well as any number of edges. They can be symmetrical or irregular.

Add a Shape to the front Layer

The angled Shrine logo inspires the shape story of the Shrine app. Shape stories are a common use of shapes applied in applications. For example, the logo shape is echoed in the login page element to which the shape is applied. In this section, you will style the front layer with a slanted slice in the upper left corner.

Dart, add a new _FrontLayer class:

// TODO: add _FrontLayer (104) class _FrontLayer extends StatelessWidget {// TODO: Add on-tap callback (104) const _FrontLayer({Key Key, this.child,}) : super(Key: Key); final Widget child; @override Widget build(BuildContext context) {returnMaterial(elevation: 16.0, Shape: RectangleBorder(borderRadius: borderRadius. Only (topLeft: Radius. Circular (46.0)),), the child: the Column (crossAxisAlignment: crossAxisAlignment. Stretch, the children: <Widget>[// TODO: Add GestureDetector (104) Expanded(Child: child,),],); }}Copy the code

Then wrap the front layer inside _FrontLayer in the _buildStack() method of BackdropState:

      Widget _buildStack() {// TODO: Create a RelativeRectTween animation.returnStack( key: _backdropKey, children: <Widget>[widget.backLayer, // TODO: Add PositionedTransition (104) // TODO: Wrap frontLayer in _FrontLayer(104) _FrontLayer(Child: Widget.frontLayer),],); }Copy the code

Overloading.

We customized a shape for the main surface of Shrine. Due to the height of the surface, the user can see something behind the white front layer. Let’s add an action so that the user can see the background layer of the background.

6. Add Motion

Action is one way to make your app more realistic. It can be big and exaggerated, small and subtle, or somewhere in between. However, it is important to note that the form of action must be appropriate to the use of the situation. Repetitive, regular movements need to be subtle so they don’t distract the user or take up too much time. In appropriate cases, such as when a user opens the app for the first time, long actions can be more noticeable, and some animations can help users understand how to use your app.

Add display actions for menu buttons

At the top of the gorge. dart, along with the other class functions, add a constant to indicate how fast we want the animation to execute:

// TODO: const double kflingvelocity = 2.0;Copy the code

Add the AnimationController widget to _BackdropState, instantiate it in the initState() function, and deploy it in the Dispose () function of state:

      // TODO:添加 AnimationController 部件(104)
      AnimationController _controller;

      @override
      void initState() { super.initState(); _controller = AnimationController(duration: duration (milliseconds: 300), value: 1.0, vsync: this,); } // TODO: override didUpdateWidget (104) @override voiddispose() { _controller.dispose(); super.dispose(); } // TODO: Add functions to determine and change front Layer visibility (104)Copy the code

Component life cycle

The initState() method is called only once before the widget becomes part of its render tree. The Dispose () method is called once only when the part is removed from the tree.

The AnimationController works with the Animation and provides apis for playing, reversing, and stopping the Animation. Now we need to use some method to move it.

Add functions to determine and change the visibility of the front layer:

/ / TODO: Bool get _frontLayerVisible {final AnimationStatus status = _controller.status;return status == AnimationStatus.completed ||
            status == AnimationStatus.forward;
      }

      void _toggleBackdropLayerVisibility() {
        _controller.fling(
            velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
      }
Copy the code

Wrap the backLayer in the ExcludeSemantics part. When the backLayer is not visible, this widget removes the backLayer menu item from the semantic tree.

        returnStack( key: _backdropKey, children: <Widget>[// TODO: ExcludeSemantics ExcludeSemantics(child: Widget. backLayer, excluding: _frontLayerVisible, ), ...Copy the code

Modify the _buildStack() method to hold a BuildContext and BoxConstraints. Also includes a PositionedTransition using a RelativeRectTween animation:

      // TODO:为 _buildStack 添加 BuildContext 和 BoxConstraints 参数(104)
      Widget _buildStack(BuildContext context, BoxConstraints constraints) {
        const double layerTitleHeight = 48.0;
        final Size layerSize = constraints.biggest;
        final double layerTop = layerSize.height - layerTitleHeight;

        // TODO:创建一个 RelativeRectTween 动画(104)
        Animation<RelativeRect> layerAnimation = RelativeRectTween(
          begin: RelativeRect.fromLTRB(
              0.0, layerTop, 0.0, layerTop - layerSize.height),
          end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
        ).animate(_controller.view);

        returnStack( key: _backdropKey, children: <Widget>[ ExcludeSemantics( child: widget.backLayer, excluding: _frontLayerVisible,), // TODO: Add a PositionedTransition(104) PositionedTransition(rect: layerAnimation, child: _FrontLayer(// TODO: implement onTap property on _BackdropState (104) Child: widget.frontLayer,),),],); }Copy the code

Finally, return a LayoutBuilder widget that uses _buildStack as its Builder instead of calling _buildStack for the Scaffold body:

        returnScaffold(appBar: appBar, // TODO: returns a LayoutBuilder(104) body: LayoutBuilder(Builder: _buildStack),);Copy the code

We used LayoutBuilder to delay the build of the front/back stack until the layout stage so that we could merge the actual overall height of the background. LayoutBuilder is a special widget whose builder callbacks provide size constraints.

LayoutBuilder

Part trees organize the layout by traversing leaf nodes. The constraint is passed down the tree, but the size is usually not computed until the leaf node returns its size based on the constraint. Leaf dot cannot know the size of its parent because it has not yet been calculated.

LayoutBuilder comes in handy when the part must know the size of its parent in order to lay it out on its own (and the parent size does not depend on the child). It uses a method to return the part.

For more information, see the LayoutBuilder class documentation.

In the Build () method, convert the leading menu icon in the application bar to an IconButton and use it to toggle the visibility of the Front Layer when the button is clicked.

// TODO: replace leading menu icon (104) leading: IconButton(icon: icon (icon.menu), onPressed: _toggleBackdropLayerVisibility, ),Copy the code

Reload in the emulator and click the menu button.

The front layer is sliding down. But if you look down, you get red errors and overflow errors. This is because the AsymmetricView is squeezed and shrunk by the animation, which in turn makes the Column smaller. Finally, columns cannot arrange themselves in a given space and cause errors. If we replace Column with ListView, the size of the Column remains the same as we move it.

Wrap product list items in the ListView

In Supplemental /product_columns. Dart, replace the Column for OneProductCardColumn with ListView:

class OneProductCardColumn extends StatelessWidget { OneProductCardColumn({this.product}); final Product product; @override Widget build(BuildContext context) {TODO: replace Column (104) with ListViewreturn ListView(
          reverse: true, children: <Widget>[SizedBox(height: 40.0,), ProductCard(product: product,),],); }}Copy the code

Column contains mainAxisalignment.end. To make the layout start from the bottom, use reverse: true. The order of their children will be reversed to make up for the change.

Reload and click the menu button.

The gray overflow warning on the OneProductCardColumn has disappeared! Now let’s fix another problem.

In supplemental/product_columns. Dart change imageAspectRatio to ListView

/ / TODO: Modify imageAspectRatio calculation (104) double imageAspectRatio = (heightOfImages > = 0.0 && constraints. The biggest. Width > heightOfImages) ? constraints.biggest.width / heightOfImages : 33 / 49; // TODO: replace Column (104) with ListViewreturnThe ListView (children: < widgets > [Padding (Padding: EdgeInsetsDirectional. Only (start: 28.0), the child: top != null ? ProductCard( imageAspectRatio: imageAspectRatio, product: top, ) : SizedBox( height: HeightOfCards,),), SizedBox (height: spacerHeight), Padding (Padding: EdgeInsetsDirectional. Only (end: 28.0), the child: ProductCard( imageAspectRatio: imageAspectRatio, product: bottom, ), ), ], ); });Copy the code

We have also added some security for imageAspectRatio.

Overloading. Then click the menu button.

There is no overflow now.

7. Add a menu on the Back Layer

A menu is a list of clickable text items that notify listeners when a click event occurs. In this section, you will add a category filter menu.

Add menu

Add menus at the front layer and interactive buttons at the back Layer.

Create a new file named lib/category_menu_page.dart:

    import 'package:flutter/material.dart';
    import 'package:meta/meta.dart';

    import 'colors.dart';
    import 'model/product.dart'; class CategoryMenuPage extends StatelessWidget { final Category currentCategory; final ValueChanged<Category> onCategoryTap; final List<Category> _categories = Category.values; const CategoryMenuPage({ Key key, @required this.currentCategory, @required this.onCategoryTap, }) : assert(currentCategory ! = null), assert(onCategoryTap ! = null); Widget _buildCategory(Category category, BuildContext context) { final categoryString = category.toString().replaceAll('Category.'.' ').toUpperCase();
        final ThemeData theme = Theme.of(context);

        returnGestureDetector( onTap: () => onCategoryTap(category), child: category == currentCategory ? Column(children: <Widget>[SizedBox(height: 16.0), Text(categoryString, style: theme.textTheme. Body2, textAlign: Center, SizedBox(height: 14.0), Container(width: 70.0, height: 2.0, color: kShrinePink400,),) : Padding(Padding: EdgeInsets. Symmetric (vertical: 16.0), Child: Text(categoryString, style: theme.textTheme.body2.copyWith( color: kShrineBrown900.withAlpha(153) ), textAlign: TextAlign.center, ), ), ); } @override Widget build(BuildContext context) {returnCenter(Child: Container(padding: EdgeInsets. Only (top: 40.0), color: kShrinePink100, Child: ListView(children: _categories .map((Category c) => _buildCategory(c, context)) .toList()), ), ); }}Copy the code

It is a GestureDetector that contains a Column whose child is the category name. The underline is used to indicate the selected category.

ShrineApp was converted from Stateless to Stateful in app.dart.

  1. The highlightedShrineApp.
  2. Press Alt (option) + Enter
  3. Select “Convert to StatefulWidget”.
  4. Change the ShrineAppState class to private (_ShrineAppState). To do this from the IDE main menu, select Refactor > Rename. Or in the code, you can highlight the class name ShrineAppState, then right-click and select Refactor > Rename. The input_ShrineAppStateTo make the class private.

In app.dart, add a variable _ShrineAppState for the selected category and a callback when clicked:

/ / TODO: Class _ShrineAppState extends State<ShrineApp> {Category _currentCategory = ejbateful (104) class _ShrineAppState extends State<ShrineApp> {Category _currentCategory = Category.all; void _onCategoryTap(Category category) {setState(() {
          _currentCategory = category;
        });
      }
Copy the code

Then change the back Layer to CategoryMenuPage.

Introduce CategoryMenuPage in app.dart:

    import 'backdrop.dart';
    import 'colors.dart';
    import 'home.dart';
    import 'login.dart';
    import 'category_menu_page.dart';
    import 'model/product.dart';
    import 'supplemental/cut_corners_border.dart';
Copy the code

In the Build () method, change the Backlayer field to CategoryMenuPage and let the currentCategory field hold the instance variable.

// add data to the currentCategory field. Then add data to the currentCategory field. _currentCategory, // TODO: Pass _currentCategory (104) frontLayer: HomePage(), // TODO: CategoryMenuPage backLayer: CategoryMenuPage(currentCategory: categoryname) _currentCategory, onCategoryTap: _onCategoryTap, ), frontTitle: Text('SHRINE'),
            backTitle: Text('MENU'),),Copy the code

Reload and click the menu button.

You click on the menu option and nothing happens… Let’s fix it.

In home.dart, add a variable to the Category and pass it to AsymmetricView.

    import 'package:flutter/material.dart';

    import 'model/products_repository.dart';
    import 'model/product.dart';
    import 'supplemental/asymmetric_view.dart'; Class HomePage extends StatelessWidget {// TODO: Adds a variable to a Category (104) final Category Category; const HomePage({this.category: Category.all}); Override Widget build(BuildContext context) {// TODO: Add a variable to the Category and pass it to AsymmetricView (104)returnAsymmetricView(products: ProductsRepository.loadProducts(category)); }}Copy the code

Pass _currentCategory to frontLayer in app.dart:

// TODO: pass _currentCategory (104) frontLayer: HomePage(category: _currentCategory)Copy the code

Overloading. Click the menu button in the emulator and select a category.

Click the menu icon to view the product. They are filtered!

Close the Front Layer after selecting the menu item

In context context.dart, override the didUpdateWidget() method for BackdropState:

// TODO: Add a override method for didUpdateWidget() @override void didUpdateWidget(Backdrop data) {super.didupDateWidget (old);if(widget.currentCategory ! = old.currentCategory) { _toggleBackdropLayerVisibility(); }else if (!_frontLayerVisible) {
          _controller.fling(velocity: _kFlingVelocity);
        }
      }
Copy the code

Hot reload, then click on the menu icon and select a category. The menu should close automatically and you will see the items of the selected category. Now add this functionality to the Front Layer as well.

Switch the front layer

Dart Add an on-tap callback to the gorgeous water layer:

Class _FrontLayer extends StatelessWidget {// TODO: Add on-tap callback (104) const _FrontLayer({Key Key, this.ontap, // add code this.child,}) : super(Key: Key); final VoidCallback onTap; // Add code final Widget child;Copy the code

Then add a GestureDetector to the child node of the _FrontLayer’s child Column:

child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[// TODO: Add a GestureDetector(104) GestureDetector(Behavior: HitTestBehavior. Opaque, onTap: onTap, child: The Container (height: 40.0, alignment: AlignmentDirectional centerStart,),), Expanded (child: the child,),),),Copy the code

Then implement the new onTap property in the _BackdropState of the _buildStack() method:

PositionedTransition(rect: layerAnimation, Child: _FrontLayer(// TODO: Implement onTap in _BackdropState) onTap: _toggleBackdropLayerVisibility, child: widget.frontLayer, ), ),Copy the code

Reload and click on the top of the front Layer. Every time you click on the top of the front Layer it should be on or off.

8. Add brand ICONS

Brand portraits should also extend to familiar ICONS. Let’s customize the display icon and merge it with our title for a unique brand look.

Modify the menu button icon

Dart, create the _BackdropTitle class.

// TODO: add _BackdropTitle extends AnimatedWidget {final Function onPress; final Widget frontTitle; final Widget backTitle; const _BackdropTitle({ Key key, Listenable listenable, this.onPress, @required this.frontTitle, @required this.backTitle, }) : assert(frontTitle ! = null), assert(backTitle ! = null), super(key: key, listenable: listenable); @override Widget build(BuildContext context) { final Animation<double> animation = this.listenable;return DefaultTextStyle(
          style: Theme.of(context).primaryTextTheme.title,
          softWrap: false, Overflow: TextOverflow. Ellipsis, child: Row(children: <Widget> IconButton(padding: EdgeInsets. Only (right: 8.0), onPressed: this.onPress, icon: Stack(children: <Widget>[ Opacity( opacity: animation.value, child: ImageIcon(AssetImage('assets/slanted_menu.png')), ), FractionalTranslation( translation: Tween<Offset>( begin: Offset.zero, end: Offset(1.0, 0.0),). Evaluate (animation), child: ImageIcon(AssetImage('assets/diamond.png'),)]),),),),), // In this case, we are implementing a custom cross fade between backTitle and frontTitle // this allows smooth transitions between the two texts. Stack( children: <Widget>[ Opacity( opacity: CurvedAnimation( parent: ReverseAnimation(animation), curve: Interval(0.5, 1.0),). Value, child: FractionalTranslation(translation: Tween<Offset>(begin: Offset. Zero, end: Offset(0.5, 0.0),). Evaluate (animation), child: backTitle,),), Opacity(Opacity: CurvedAnimation(parent: Animation, curve: Interval(0.5, 1.0),). Value, child: FractionalTranslation(translation: Tween<Offset>(begin: Offset(-0.25, 0.0), end: Offset. Zero,).evaluate(animation), child: frontTitle,),],); }}Copy the code

_BackdropTitle is a custom widget that replaces the Text widget for the title parameter in AppBar. It has an animated menu icon and animated transitions between before and after titles. Animated menu ICONS will use the new resource. A reference to the new slanted_menu.png must therefore be added to pubspec.yaml.

    assets:
        - assets/diamond.png
        - assets/slanted_menu.png
        - packages/shrine_images/0-0.jpg
Copy the code

Remove the leading attribute in AppBar Builder. This allows the custom brand icon to be displayed in the location of the original Leading widget. The onPress processing for listEnable animations and brand ICONS will be passed to _BackdropTitle. FrontTitle and backTitle are also passed to show them in the background title. The title parameter for AppBar looks like this:

// TODO: create title (104) with _BackdropTitle(listEnable: _controller.view, onPress: _toggleBackdropLayerVisibility, frontTitle: widget.frontTitle, backTitle: widget.backTitle, ),Copy the code

The brand icon is created in _BackdropTitle. It contains a set of animated ICONS: a slanted menu and a diamond, which is wrapped in the IconButton so that it can be pressed. The IconButton is then wrapped in a SizedBox to make room for icon horizontal motion.

Flutter’s “Everything is a widget” architecture allows you to change the layout of the default AppBar without creating entirely new custom AppBar widgets. The title parameter is originally a Text widget that can be replaced with the more complex _BackdropTitle. Since _BackdropTitle also contains custom ICONS, it replaces the leading attribute and can now be omitted. This simple part replacement is done without changing any other parameters, such as action ICONS, and they continue to run.

Add a shortcut to return to the login screen

Dart, add a shortcut to the login screen from the two trailing ICONS in the application bar: change the semanticLabel of the icon to reflect its new use.

// TODO: add a shortcut from the bottom icon to the landing page (104) IconButton(icon: icon (Icons. Search, semanticLabel:'login', // Add code), onPressed: () {// TODO: open Navigator. Push (context, MaterialPageRoute(Builder: (BuildContext context) => LoginPage()), ); }, ), IconButton( icon: Icon( Icons.tune, semanticLabel:'login', // Add code), onPressed: () {// TODO: Open Navigator. Push (context, MaterialPageRoute(Builder: (BuildContext context) => LoginPage()), ); },),Copy the code

You will receive an error message if you try to reload. Import login.dart to fix errors:

    import 'login.dart';
Copy the code

Reload the app and click the Search or Adjust button to return to the login screen.

9. To summarize

Through four tutorials, you’ve learned how to use Material components to build a unique, elegant user experience that expresses a brand’s personality and style.

The full MDC-104 application can be found in the 104-Complete branch.

You can test your application with versions in this branch.

The next step

Mdc-104 is now complete. You can visit the Flutter Widgets directory to explore more components in MDC-FLUTTER.

For more advanced goals, try replacing the brand icon with AnimatedIcon.

To learn how to connect an application to Firebase for back-end support, see Firebase in Flutter.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.