Flutter-Docs-Development-User interface - Notes

Flutter-Docs-Development-User interface - Notes

Flutter-Docs-Development-User interface : https://docs.flutter.dev/development/ui

Introduction to widgets

Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

The minimal Flutter app simply calls the runApp() function with a widget:

import 'package:flutter/material.dart'
    
void main() {
    runApp(
    	const Center(
        	child: Text('Hello world'),
            textDirection: TextDirection.ltr,
        ),
    ),
}

The runApp() function takes the given Widget and makes it the root of the widget tree. The framework forces the root widget to cover the screen.

author /ˈɔːθə/ n作者vt编写(书,代码) in terms of依据 bottom out 降到最低点 underlie /ˌʌndəˈlaɪ/ vt是…的原因,是…的基础 underlying /ˌʌndəˈlaɪɪŋ/ a潜在的,下面的,根本的 geometry /dʒɪˈɒmɪtrɪ/ n几何学,几何图形 a run of a大量的 rectangular /rɛkˈtæŋɡjʊlə/ a长方形的 matrix /ˈmeɪtrɪks/ n发源地,(数学)矩阵 anti-aliased n(图像处理)反锯齿,平滑处理 alias /ˈeɪlɪəs/ n别名,vi/n失真 inset /ˈɪnset/ n大图中的小图 entry n入口,进入,条目

When writing an app, you’ll commonly author new widgets that are subclasses of either StatelessWidget or StatefulWidget, depending on whether your widget manages any state. A widget’s main job is to implement a build() function, which describes the widget in terms of other, lower-level widgets. The framework builds those widgets in turn until the process bottoms out in widgets that represent the underlying RenderObject, which computes and describes the geometry of the widget.

Basic widgets

  • Text

    The Text widget lets you create a run of styled text within your application.

  • Row, Column

    These flex widgets let you create flexible layouts in both the horizontal (Row) and vertical (Column) directions. The design of these objects is based on the web’s flexbox layout model.

  • Stack

    Instead of being linearly oriented (either horizontally or vertically), a Stack widget lets you place widgets on top of each other in paint order. You can then use the Positioned widget on children of a Stack to position them relative to the top, right, bottom, or left edge of the stack. Stacks are based on the web’s absolute positioning layout model.

  • Container

    The Container widget lets you create a rectangular visual element. A container can be decorated with a BoxDecoration, such as a background, a border, or a shadow. A Container can also have margins, padding, and constraints applied to its size. In addition, a Container can be transformed in three dimensional space using a matrix.

Below are some simple widgets that combine these and other widgets:

Many Material Design widgets need to be inside of a MaterialApp to display properly, in order to inherit theme data. Therefore, run the application with a MaterialApp.

The middle child, the title widget, is marked as Expanded, which means it expands to fill any remaining available space that hasn’t been consumed by the other children. You can have multiple Expanded children and determine the ratio in which they consume the available space using the flex argument to Expanded.

Using Material Components

A Material app starts with the MaterialApp widget, which builds a number of useful widgets at the root of your app, including a Navigator, which manages a stack of widgets identified by strings, also known as “routes”. The Navigator lets you transition smoothly between screens of your application.

Material is one of the 2 bundled designs included with Flutter. To create an iOS-centric design, see the Cupertino components package, which has its own versions of CupertinoApp, and CupertinoNavigationBar.

cupertino /ˌkuːpərˈtiːnoʊ/ n库比蒂诺(苹果电脑的全球总公司所在地,位于美国旧金山) derive /dɪˈraɪv/ vt获得,vi/vt衍生 encapsulate /ɪnˈkæpsjʊˌleɪt/ vt概括,封装 hypothetical /ˌhaɪpəˈθɛtɪkəl/ a假设的 hypothesis /haɪˈpɒθɪsɪs/ n假设 persist /pəˈsɪst/ vi继续存在 reflect vt反映,显示,反射,反思 retrieve /rɪˈtriːv/ vt取回,读取(电脑信息)

Handling gestures

The GestureDetector widget doesn’t have a visual representation but instead detects gestures made by the user. When the user taps the Container, the GestureDetector calls its onTap() callback, in this case printing a message to the console. You can use GestureDetector to detect a variety of input gestures, including taps, drags, and scales.

Many widgets use a GestureDetector to provide optional callbacks for other widgets. For example, the IconButton, ElevatedButton, and FloatingActionButton widgets have onPressed() callbacks that are triggered when the user taps the widget.

For more information, see Gestures in Flutter.

Changing widgets in response to input

Stateless widgets receive arguments from their parent widget, which they store in final member variables. When a widget is asked to build(), it uses these stored values to derive new arguments for the widgets it creates.

StatefulWidgets are special widgets that know how to generate State objects, which are then used to hold state.

You might wonder why StatefulWidget and State are separate objects. In Flutter, these two types of objects have different life cycles. Widgets are temporary objects, used to construct a presentation of the application in its current state. State objects, on the other hand, are persistent between calls to build(), allowing them to remember information.

The example above accepts user input and directly uses the result in its build() method.

Notice the creation of two new stateless widgets, cleanly separating the concerns of displaying the counter (CounterDisplay) and changing the counter (CounterIncrementor). Although the net result is the same as the previous example, the separation of responsibility allows greater complexity to be encapsulated in the individual widgets, while maintaining simplicity in the parent.

Bringing it all together

What follows is a more complete example that brings together these concepts: A hypothetical shopping application displays various products offered for sale, and maintains a shopping cart for intended purchases. Start by defining the presentation class, ShoppingListItem:

When the parent receives the onCartChanged callback, the parent updates its internal state, which triggers the parent to rebuild and create a new instance of ShoppingListItem with the new inCart value. Although the parent creates a new instance of ShoppingListItem when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying RenderObject.

The ShoppingList class extends StatefulWidget, which means this widget stores mutable state. When the ShoppingList widget is first inserted into the tree, the framework calls the createState() function to create a fresh instance of _ShoppingListState to associate with that location in the tree. (Notice that subclasses of State are typically named with leading underscores to indicate that they are private implementation details.) When this widget’s parent rebuilds, the parent creates a new instance of ShoppingList, but the framework reuses the _ShoppingListState instance that is already in the tree rather than calling createState again.

To access properties of the current ShoppingList, the _ShoppingListState can use its widget property. If the parent rebuilds and creates a new ShoppingList, the _ShoppingListState rebuilds with the new widget value. If you wish to be notified when the widget property changes, override the didUpdateWidget() function, which is passed an oldWidget to let you compare the old widget with the current widget.

When handling the onCartChanged callback, the _ShoppingListState mutates its internal state by either adding or removing a product from _shoppingCart. To signal to the framework that it changed its internal state, it wraps those calls in a setState() call. Calling setState marks this widget as dirty and schedules it to be rebuilt the next time your app needs to update the screen. If you forget to call setState when modifying the internal state of a widget, the framework won’t know your widget is dirty and might not call the widget’s build() function, which means the user interface might not update to reflect the changed state. By managing state in this way, you don’t need to write separate code for creating and updating child widgets. Instead, you simply implement the build function, which handles both situations.

Responding to widget lifecycle events

After calling createState() on the StatefulWidget, the framework inserts the new state object into the tree and then calls initState() on the state object. A subclass of State can override initState to do work that needs to happen just once. For example, override initState to configure animations or to subscribe to platform services. Implementations of initState are required to start by calling super.initState.

When a state object is no longer needed, the framework calls dispose() on the state object. Override the dispose function to do cleanup work. For example, override dispose to cancel timers or to unsubscribe from platform services. Implementations of dispose typically end by calling super.dispose.

For more information, see State.

Keys

Use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build according to their runtimeType and the order in which they appear. With keys, the framework requires that the two widgets have the same key as well as the same runtimeType.

Keys are most useful in widgets that build many instances of the same type of widget. For example, the ShoppingList widget, which builds just enough ShoppingListItem instances to fill its visible region:

  • Without keys, the first entry in the current build would always sync with the first entry in the previous build, even if, semantically, the first entry in the list just scrolled off screen and is no longer visible in the viewport.
  • By assigning each entry in the list a “semantic” key, the infinite list can be more efficient because the framework syncs entries with matching semantic keys and therefore similar (or identical) visual appearances. Moreover, syncing the entries semantically means that state retained in stateful child widgets remains attached to the same semantic entry rather than the entry in the same numerical position in the viewport.

For more information, see the Key API.

Global keys

Use global keys to uniquely identify child widgets. Global keys must be globally unique across the entire widget hierarchy, unlike local keys which need only be unique among siblings. Because they are globally unique, a global key can be used to retrieve the state associated with a widget.

For more information, see the GlobalKey API.

Layouts in Flutter

Widgets are classes used to build UIs. Widgets are used for both layout and UI elements. Compose simple widgets to build complex widgets.

The core of Flutter’s layout mechanism is widgets. In Flutter, almost everything is a widget—even layout models are widgets. The images, icons, and text that you see in a Flutter app are all widgets. But things you don’t see are also widgets, such as the rows, columns, and grids that arrange, constrain, and align the visible widgets.

Note: Most of the screenshots in this tutorial are displayed with debugPaintSizeEnabled set to true so you can see the visual layout. For more information, see Debugging layout issues visually, a section in Using the Flutter inspector.

Container is a widget class that allows you to customize its child widget. Use a Container when you want to add padding, margins, borders, or background color, to name some of its capabilities.

The rest of the UI in this example is controlled by properties. Set an Icon’s color using its color property. Use the Text.style property to set the font, its color, weight, and so on. Columns and rows have properties that allow you to specify how their children are aligned vertically or horizontally, and how much space the children should occupy.

Lay out a widget

Choose from a variety of layout widgets based on how you want to align or constrain the visible widget, as these characteristics are typically passed on to the contained widget.

This example uses Center which centers its content horizontally and vertically.

All layout widgets have either of the following:

  • A child property if they take a single child—for example, Center or Container
  • A children property if they take a list of widgets—for example, Row, Column, ListView, or Stack.

instantiate /ɪnˈstænʃɪˌeɪt/ vt实例化 exclusive /ɪkˈskluːsɪv/ a独有的,排斥的 mimic /ˈmɪmɪk/ vt模仿imitate meringue /məˈræŋ/ 蛋白和糖混合物 in turn 轮流,反过来 nest vt嵌套embed pavlova /pævˈləʊvə/ n奶油水果蛋白饼 overwhelm /ˌəʊvəˈwɛlm/ vt打败 overlap vi/vt重叠 liberal /ˈlɪbərəl/ a开明的,自由的,大量的 fabricate /ˈfæbrɪˌkeɪt/ vt伪造,制造 extent /ɪkˈstent/ n程度,范围

A Flutter app is itself a widget, and most widgets have a build() method. Instantiating and returning a widget in the app’s build() method displays the widget.

For a Material app, you can use a Scaffold widget; it provides a default banner, background color, and has API for adding drawers, snack bars, and bottom sheets. Then you can add the Center widget directly to the body property for the home page.

By default a non-Material app doesn’t include an Appbar, title, or background color.

Lay out multiple widgets vertically and horizontally

You can use a Row widget to arrange widgets horizontally, and a Column widget to arrange widgets vertically.

For example, instead of Row you might prefer ListTile, an easy-to-use widget with properties for leading and trailing icons, and up to 3 lines of text. Instead of Column, you might prefer ListView, a column-like layout that automatically scrolls if its content is too long to fit the available space. For more information, see Common layout widgets.

You control how a row or column aligns its children using the mainAxisAlignment and crossAxisAlignment properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.

The MainAxisAlignment and CrossAxisAlignment enums offer a variety of constants for controlling alignment.

When you add images to your project, you need to update the pubspec.yaml file to access them—this example uses Image.asset to display the images. For more information, see this example’s pubspec.yaml file or Adding assets and images. You don’t need to do this if you’re referencing online images using Image.network.

Setting the main axis alignment to spaceEvenly divides the free horizontal space evenly between, before, and after each image.

When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. Here is an example of a row that is too wide:

Widgets can be sized to fit within a row or column by using the Expanded widget. To fix the previous example where the row of images is too wide for its render box, wrap each image with an Expanded widget.

Perhaps you want a widget to occupy twice as much space as its siblings. For this, use the Expanded widget flex property, an integer that determines the flex factor for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2.

Packing widgets

By default, a row or column occupies as much space along its main axis as possible, but if you want to pack the children closely together, set its mainAxisSize to MainAxisSize.min. The following example uses this property to pack the star icons together.

MainAxisSize on your Column or Row will determine the height for Column and the width for Row.

Nesting rows and columns

To minimize the visual confusion that can result from heavily nested layout code, implement pieces of the UI in variables and functions.

You can embed an image from the net using Image.network() but, for this example, the image is saved to an images directory in the project, added to the pubspec file, and accessed using Images.asset(). For more information, see Adding assets and images.

Common layout widgets

For information on other available widgets, refer to the Widget catalog, or use the Search box in the API reference docs. The following widgets fall into two categories: standard widgets from the widgets library, and specialized widgets from the Material library.

Standard widgets

  • Container: Adds padding, margins, borders, background color, or other decorations to a widget.
  • GridView: Lays widgets out as a scrollable grid.
  • ListView: Lays widgets out as a scrollable list.
  • Stack: Overlaps a widget on top of another.

Material widgets

  • Card: Organizes related info into a box with rounded corners and a drop shadow.
  • ListTile: Organizes up to 3 lines of text, and optional leading and trailing icons, into a row.

Container

Many layouts make liberal use of Containers to separate widgets using padding, or to add borders or margins. You can change the device’s background by placing the entire layout into a Container and changing its background color or image.

Summary (Container): Adding padding, margins, borders. Change background color or image. Contains a single child widget, but that child can be a Row, Column or even the root of a widget tree.

GridView

Use GridView to lay widgets out as a two-dimensional list. GridView provides two pre-fabricated lists, or you can build your own custom grid. When a GridView detects that its contents are too long to fit the render box, it automatically scrolls.

Summary (GridView)
  • Lays widgets out in a grid
  • Detects when the column content exceeds the render box and automatically provides scrolling
  • Build your own custom grid, or use one of the provided grids:
    • GridView.count allows you to specify the number of columns
    • GridView.extent allows you to specify the maximum pixel width of a tile

Note: When displaying a two-dimensional list where it’s important which row and column a cell occupies (for example, it’s the entry in the “calorie” column for the “avocado” row), use Table or DataTable.

calorie /ˈkælərɪ/ n卡路里 avocado /ˌævəˈkɑːdəʊ/ n鳄梨 nugget /ˈnʌɡɪt/ 小块东西 disperse /dɪˈspɜːs/ vi/vt分散

Uses GridView.extent to create a grid with tiles a maximum 150 pixels wide. Uses GridView.count to create a grid that’s 2 tiles wide in portrait mode, and 3 tiles wide in landscape mode. The titles are created by setting the footer property for each GridTile.

ListView, a column-like widget, automatically provides scrolling when its content is too long for its render box.

Summary (ListView)
  • A specialized Column for organizing a list of boxes
  • Can be laid out horizontally or vertically
  • Detects when its content won’t fit and provides scrolling
  • Less configurable than Column, but easier to use and supports scrolling

Uses ListView to display a list of businesses using ListTiles. A Divider separates the theaters from the restaurants. Uses ListView to display the Colors from the Material Design palette for a particular color family.

Stack

Use Stack to arrange widgets on top of a base widget—often an image. The widgets can completely or partially overlap the base widget.

Summary (Stack)
  • Use for widgets that overlap another widget
  • The first widget in the list of children is the base widget; subsequent children are overlaid on top of that base widget
  • A Stack’s content can’t scroll
  • You can choose to clip children that exceed the render box

Uses Stack to overlay a Container (that displays its Text on a translucent black background) on top of a CircleAvatar. The Stack offsets the text using the alignment property and Alignments.

A Card, from the Material library, contains related nuggets of information and can be composed from almost any widget, but is often used with ListTile. Card has a single child, but its child can be a column, row, list, grid, or other widget that supports multiple children. By default, a Card shrinks its size to 0 by 0 pixels. You can use SizedBox to constrain the size of a card.

In Flutter, a Card features slightly rounded corners and a drop shadow, giving it a 3D effect. Changing a Card’s elevation property allows you to control the drop shadow effect. Setting the elevation to 24, for example, visually lifts the Card further from the surface and causes the shadow to become more dispersed. For a list of supported elevation values, see Elevation in the Material guidelines. Specifying an unsupported value disables the drop shadow entirely.

Summary (Card)
  • Implements a Material card
  • Used for presenting related nuggets of information
  • Accepts a single child, but that child can be a Row, Column, or other widget that holds a list of children
  • Displayed with rounded corners and a drop shadow
  • A Card’s content can’t scroll
  • From the Material library

A Card containing 3 ListTiles and sized by wrapping it with a SizedBox. A Divider separates the first and second ListTiles.

ListTile

Use ListTile, a specialized row widget from the Material library, for an easy way to create a row containing up to 3 lines of text and optional leading and trailing icons. ListTile is most commonly used in Card or ListView, but can be used elsewhere.

Summary (ListTile)
  • A specialized row that contains up to 3 lines of text and optional icons
  • Less configurable than Row, but easier to use
  • From the Material library

Constraints

To fully understand Flutter’s layout system, you need to learn how Flutter positions and sizes the components in a layout. For more information, see Understanding constraints.

Videos

The following videos, part of the Flutter in Focus series, explain Stateless and Stateful widgets.

Each episode of the Widget of the Week series focuses on a widget. Several of them includes layout widgets.

Video-How to Create Stateless Widgets - Flutter Widgets 101 Ep. 1

Widgets are the basic buiding blocks of a Flutter app. Each one is an immutable declaration of part of user interface. There are structual elements like a button or menu.stylistic elements that propagate a font or color scheme, layout-related widgets like padding, and much more.

Apps built with Flutter as a tree of widgets. Widgets are really just configurations for pieces of an app’s UI. They’re blueprints. So what are they configurations for? Elements. An element is a widget that’s been made real and mounted on screen. And it’s the element tree that represents what’s actually display on your device at any given moment.

//Column extends Flex extends MultiChildRenderObjectWidget extends RenderObjectWidget extends Widget
abstract class Widget extends DiagnosticableTree {
    Element createElement();
}
MultiChildRenderObjectWidget extends RenderObjectWidget {
    
    MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}

mount /maʊnt/ vt组织,登上,骑上vi增加 survive vi存活vt挺过 cumbersome */*ˈkʌmbərsəm/ a不方便的

Each widget class has both a corresponding element class and a method to create an instance. Stateless widget, for example, creates a stateless element. That createElement method gets called when the widget is mounted to the tree. Flutter asks the widget for an element, and then pops that element onto the element tree with a reference to the widget that created it. These widgets then creates their own elements, and they’re mounted on the element tree as well. So my app has two trees, the element tree represents what’s actually on the screen, and the widget tree holds the blueprints they were made from.

RunApp takes a widget and mounts it as the root element for the app with the height and width constraints that match the size of the screen. Flutter progresses throught all the build methods, creating widgets, using them to make elements until everything is built, mounted on screen, and ready to be laid out and rendered. Stateless widgets can’t track data over time or trigger rebuilds on their own.

image-20230408213737398

Video-How Stateful Widgets Are Used Best - Flutter Widgets 101 Ep. 2

Stateless widgets are immutable configuration or blueprint. Stateful widgets provide immutable configuration info and a state object that can change over time and trigger rebuilds of the UI. The widget is responsible for two things, holding onto that immutable name value which won’t change and creating the state object. The state object for its parts holds the count value and builds child widgets.

With a stateful widget, there’s extra step, first comes the widget when Flutter asks it to create an element, it returns a stateful element. That stateful element then goes back to the widget and asks for making a state object, which is what that createState method is for. That method kicks out a new state object and the element holds on to it. Then stateful element calls the state object’s build method.

Because the state object maintains a reference to the widget for widget’s maintaining state, so it can access values from the widget and the state object.

If I drop a gesture detector to use the setState method in the state object to make a change. setState is a way to set properties on the state object and trigger updates to the UI. You give it a function that makes the changes and the state object runs it and make sure the UI gets built afterwards.

image-20230408224759664

When setState runs, the count gets incremented plus, the state object marks its element as dirty meaning it will rebuild its children on the next frame. When the next frame rolls around, the stateful element calls the build method in the state object to rebuild the children and outputs a new text widget that shows the new count. The old widget goes away and incomes the new one. Because the new widget is the same type as the old one, the stateless element stays right where it is nad just update itself to reference the new widget.

image-20230408224819880

Stateful widgets have a long life span, then can remain attached to the element tree even when the original widget gets replaced by a new one, as long as that new one is of the same type. If the item counter widget itself were rebuilt from a change above it from in the tree, the original item counter widget goes away but since the new one is the same type of widget, the stateful element and state object stay right where they are. They survive the change in widgets and just make themselves dirty so their children get rebuilt, then the state object’s build method kicks out a new text widget using its count value, but with the new item counter widget’s name value. The older text widget goes away, the new one’s mounted, and the stateless element for the text stays right where it is.

image-20230408230844535

There’s a method in the state class called didUpdateWidget that you can override, if your state object need to know when its widget gets replaced. AnimatedContainer uses it to know when it should start animating the change from one config to another.

You will find yourself writing fewer and fewer stateful widgets. One of the reasons is many common use caes have been implemented. For example, say you have a stream of data and you want a stateful widget that rebuilds whenever the stream emits a new value. That’s a StreamBuilder, part of the flutter framework. Another reason is that if you got a bunch of stateful widgets passing data through all those build methods and constructors can get cumbersome. Fortunately, there’s a type of widget that makes it easy to access data stored near the top of the tree, even if you’re a hundred widgets down. It’s called inherited widget.

Building layouts

  • When pasting code into your app, indentation can become skewed. You can fix this in your Flutter editor using the automatic reformatting support.

  • For a faster development experience, try Flutter’s hot reload feature.

    indentation /ˌɪndenˈteɪʃn/ n凹陷,行首缩进 cascade /kæsˈkeɪd/ a大量的,倾斜 nougat /ˈnuːɡɑː/ n牛轧糖 aspect ratio 纵横比 necessitate /nəˈsesɪteɪt/ vt使…成为必需

By setting softwrap to true, text lines will fill the column width before wrapping at a word boundary.

Update the pubspec.yaml file to include an assets tag. This makes the image available to your code.

  • Note that pubspec.yaml is case sensitive, so write assets: and the image URL as shown above.
  • The pubspec file is also sensitive to white space, so use proper indentation.
  • You might need to restart the running program (either on the simulator or a connected device) for the pubspec changes to take effect.

BoxFit.cover tells the framework that the image should be as small as possible but cover its entire render box.

Creating responsive and adaptive apps

Responsive

Typically, a responsive app has had its layout tuned for the available screen size. Often this means (for example), re-laying out the UI if the user resizes the window, or changes the device’s orientation. This is especially necessary when the same app can run on a variety of devices, from a watch, phone, tablet, to a laptop or desktop computer.

Adaptive

Adapting an app to run on different device types, such as mobile and desktop, requires dealing with mouse and keyboard input, as well as touch input. It also means there are different expectations about the app’s visual density, how component selection works (cascading menus vs bottom sheets, for example), using platform-specific features (such as top-level windows), and more.

Creating a responsive Flutter app

Flutter allows you to create apps that self-adapt to the device’s screen size and orientation.

There are two basic approaches to creating Flutter apps with responsive design:

  • Use the LayoutBuilder class

    From its builder property, you get a BoxConstraints object. Examine the constraint’s properties to decide what to display. For example, if your maxWidth is greater than your width breakpoint, return a Scaffold object with a row that has a list on the left. If it’s narrower, return a Scaffold object with a drawer containing that list. You can also adjust your display based on the device’s height, the aspect ratio, or some other property. When the constraints change (for example, the user rotates the phone, or puts your app into a tile UI in Nougat), the build function runs.

  • Use the MediaQuery.of() method in your build functions

    This gives you the size, orientation, etc, of your current app. This is more useful if you want to make decisions based on the complete context rather than on just the size of your particular widget. Again, if you use this, then your build function automatically runs if the user somehow changes the app’s size.

    Other useful widgets and classes for creating a responsive UI:

Creating an adaptive Flutter app

For an excellent example of an adaptive app, check out Flutter Folio, a scrapbooking app created in collaboration with gskinner and the Flutter team: https://youtu.be/yytBENOnF0w. The Folio source code is also available on GitHub. Learn more on the gskinner blog.

reminisce /ˌrɛmɪˈnɪs/ vi缅怀往事 fire up vt发动(机器),鼓励(人) skim vt撇去,略过,略读 skim the surface vi大致了解

Building adaptive apps

There are many considerations for developing platform-adaptive apps, but they fall into three major categories:

Building adaptive layouts

One of the first things you must consider when bringing your app to multiple platforms is how to adapt it to the various sizes and shapes of the screens that it will run on.

Layout widgets

If you’ve been building apps or websites, you’re probably familiar with creating responsive interfaces. Luckily for Flutter developers, there are a large set of widgets to make this easier.

Some of Flutter’s most useful layout widgets include:

**
Single child**

  • Align—Aligns a child within itself. It takes a double value between -1 and 1, for both the vertical and horizontal alignment.
  • AspectRatio—Attempts to size the child to a specific aspect ratio.
  • ConstrainedBox—Imposes size constraints on its child, offering control over the minimum or maximum size.
  • CustomSingleChildLayout—Uses a delegate function to position a single child. The delegate can determine the layout constraints and positioning for the child.
  • Expanded and Flexible—Allows a child of a Row or Column to shrink or grow to fill any available space.
  • FractionallySizedBox—Sizes its child to a fraction of the available space.
  • LayoutBuilder—Builds a widget that can reflow itself based on its parents size.
  • SingleChildScrollView—Adds scrolling to a single child. Often used with a Row or Column.

Multichild

  • Column, Row, and Flex—Lays out children in a single horizontal or vertical run. Both Column and Row extend the Flex widget.
  • CustomMultiChildLayout—Uses a delegate function to position multiple children during the layout phase.
  • Flow—Similar to CustomMultiChildLayout, but more efficient because it’s performed during the paint phase rather than the layout phase.
  • ListView, GridView, and CustomScrollView—Provides scrollable lists of children.
  • Stack—Layers and positions multiple children relative to the edges of the Stack. Functions similarly to position-fixed in CSS.
  • Table—Uses a classic table layout algorithm for its children, combining multiple rows and columns.
  • Wrap—Displays its children in multiple horizontal or vertical runs.

To see more available widgets and example code, see Layout widgets.

Visual density

Different input devices offer various levels of precision, which necessitate differently sized hit areas. Flutter’s VisualDensity class makes it easy to adjust the density of your views across the entire application, for example, by making a button larger (and therefore easier to tap) on a touch device.

When you change the VisualDensity for your MaterialApp, MaterialComponents that support it animate their densities to match. By default, both horizontal and vertical densities are set to 0.0, but you can set the densities to any negative or positive value that you want.

To set a custom visual density, inject the density into your MaterialApp theme.

Contextual layout

If you need more than density changes and can’t find a widget that does what you need, you can take a more procedural approach to adjust parameters, calculate sizes, swap widgets, or completely restructure your UI to suit a particular form factor.

Screen-based breakpoints

The simplest form of procedural layouts uses screen-based breakpoints. In Flutter, this can be done with the MediaQuery API. There are no hard and fast rules for the sizes to use here, but these are general values:

hard and fast rules 严格的规则 reflow v重新排版 segmentation n分隔 circuit /ˈsɜːrkɪt/ n电路 short-circuit vi/vt短路 single-use,one-off a一次性的 leverage /ˈlevərɪdʒ/ v利用 clutter /ˈklʌtə/ n乱七八糟的东西,vt拥塞

Stroke (outline around the shape) Sometimes you want an outline around your shape and to do that you can use the stroke tag.

perception */*pərˈsepʃ(ə)n/ n认知,看法 subtle /ˈsʌtəl/ a不易察觉的,淡的

Using breakpoints, you can set up a simple system to determine the device type:

ScreenType getFormFactor(BuildContext context) {
  // Use .shortestSide to detect device type regardless of orientation
  double deviceWidth = MediaQuery.of(context).size.shortestSide;
  if (deviceWidth > FormFactor.desktop) return ScreenType.Desktop;
  if (deviceWidth > FormFactor.tablet) return ScreenType.Tablet;
  if (deviceWidth > FormFactor.handset) return ScreenType.Handset;
  return ScreenType.Watch;
}

Screen-based breakpoints are best used for making top-level decisions in your app. Changing things like visual density, paddings, or font-sizes are best when defined on a global basis.

You can also use screen-based breakpoints to reflow your top-level widget trees. For example, you could switch from a vertical to a horizontal layout when the user isn’t on a handset:

bool isHandset = MediaQuery.of(context).size.width < 600;
return Flex(
    children: [Text('Foo'), Text('Bar'), Text('Baz')],
    direction: isHandset ? Axis.vertical : Axis.horizontal);
Use LayoutBuilder for extra flexibility

Even though checking total screen size is great for full-screen pages or making global layout decisions, it’s often not ideal for nested subviews. Often, subviews have their own internal breakpoints and care only about the space that they have available to render.

The simplest way to handle this in Flutter is using the LayoutBuilder class. LayoutBuilder allows a widget to respond to incoming local size constraints, which can make the widget more versatile than if it depended on a global value.

The previous example could be rewritten using LayoutBuilder:

Widget foo = LayoutBuilder(
    builder: (context, constraints) {
  bool useVerticalLayout = constraints.maxWidth < 400.0;
  return Flex(
    children: [
      Text('Hello'),
      Text('World'),
    ],
    direction: useVerticalLayout ? Axis.vertical : Axis.horizontal,
  );
});

This widget can now be composed within a side panel, dialog, or even a full-screen view, and adapt its layout to whatever space is provided.

Device segmentation

There are times when you want to make layout decisions based on the actual platform you’re running on, regardless of size. For example, when building a custom title bar, you might need to check the operating system type and tweak the layout of your title bar, so it doesn’t get covered by the native window buttons.

To determine which combination of platforms you’re on, you can use the Platform API along with the kIsWeb value:

bool get isMobileDevice => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
bool get isDesktopDevice =>
    !kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
bool get isMobileDeviceOrWeb => kIsWeb || isMobileDevice;
bool get isDesktopDeviceOrWeb => kIsWeb || isDesktopDevice;

The Platform API can’t be accessed from web builds without throwing an exception, because the dart.io package is not supported on the web target. As a result, this code checks for web first, and because of short-circuiting, Dart will never call Platform on web targets.

Single source of truth for styling

You’ll probably find it easier to maintain your views if you create a single source of truth for styling values like padding, spacing, corner shape, font sizes, and so on. This can be done easily with some helper classes:

class Insets {
  static const double xsmall = 3;
  static const double small = 4;
  static const double medium = 5;
  static const double large = 10;
  static const double extraLarge = 20;
  // etc
}

class Fonts {
  static const String raleway = 'Raleway';
  // etc
}

class TextStyles {
  static const TextStyle raleway = const TextStyle(
    fontFamily: Fonts.raleway,
  );
  static TextStyle buttonText1 =
      TextStyle(fontWeight: FontWeight.bold, fontSize: 14);
  static TextStyle buttonText2 =
      TextStyle(fontWeight: FontWeight.normal, fontSize: 11);
  static TextStyle h1 = TextStyle(fontWeight: FontWeight.bold, fontSize: 22);
  static TextStyle h2 = TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
  static late TextStyle body1 = raleway.copyWith(color: Color(0xFF42A5F5));
  // etc
}

These constants can then be used in place of hard-coded numeric values:

return Padding(
  padding: EdgeInsets.all(Insets.small),
  child: Text('Hello!', style: TextStyles.body1),
);

With all views referencing the same shared-design system rules, they tend to look better and more consistent. Making a change or adjusting a value for a specific platform can be done in a single place, instead of using an error-prone search and replace. Using shared rules has the added benefit of helping enforce consistency on the design side.

Some common design system categories that can be represented this way are:

  • Animation timings
  • Sizes and breakpoints
  • Insets and paddings
  • Corner radius
  • Shadows
  • Strokes
  • Font families, sizes, and styles

Like most rules, there are exceptions: one-off values that are used nowhere else in the app. There is little point in cluttering up the styling rules with these values, but it’s worth considering if they should be derived from an existing value (for example, padding + 1.0). You should also watch for reuse or duplication of the same semantic values. Those values should likely be added to the global styling ruleset.

Design to the strengths of each form factor

Beyond screen size, you should also spend time considering the unique strengths and weaknesses of different form factors. It isn’t always ideal for your multiplatform app to offer identical functionality everywhere. Consider whether it makes sense to focus on specific capabilities, or even remove certain features, on some device categories.

For example, mobile devices are portable and have cameras, but they aren’t well suited for detailed creative work. With this in mind, you might focus more on capturing content and tagging it with location data for a mobile UI, but focus on organizing or manipulating that content for a tablet or desktop UI.

Another example is leveraging the web’s extremely low barrier for sharing. If you’re deploying a web app, decide which deep links to support, and design your navigation routes with those in mind.

The key takeaway here is to think about what each platform does best and see if there are unique capabilities you can leverage.

opinionated /əˈpɪnjəneɪtɪd/ 固执己见的 consolidate /kənˈsɒlɪˌdeɪt/ vt巩固,合并 manifest a明显的vt显示n清单 overlay v覆盖n浮层 tooltip /ˈtuːltɪp/ n提示弹窗框 flyout n浮出控件 heads-up n警告

To show more advanced tooltips, popup panels, or create custom context menus, you either use one of the available packages, or build it yourself using a Stack or Overlay.

Understanding constraints

Constraints go down. Sizes go up. Parent sets position.

  • A widget gets its own constraints from its parent. A constraint is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height.
  • Then the widget goes through its own list of children. One by one, the widget tells its children what their constraints are (which can be different for each child), and then asks each child what size it wants to be.
  • Then, the widget positions its children (horizontally in the x axis, and vertically in the y axis), one by one.
  • And, finally, the widget tells its parent about its own size (within the original constraints, of course).

The ConstrainedBox only imposes additional constraints from those it receives from its parent.

OverflowBox is similar to UnconstrainedBox; the difference is that it won’t display any warnings if the child doesn’t fit the space.

Flutter can’t render infinite sizes, so it throws an error with the following message: BoxConstraints forces an infinite width.

If you swap the UnconstrainedBox for a Center widget, the LimitedBox won’t apply its limit anymore (since its limit is only applied when it gets infinite constraints), and the width of the Container is allowed to grow past 100.

The FittedBox lets the Text be any size it wants, but after the Text tells its size to the FittedBox, the FittedBox scales the Text until it fills all of the available width.

FittedBox tries to size itself to the Text, but it can’t be bigger than the screen. It then assumes the screen size, and resizes Text so that it fits the screen, too.

you remove the FittedBox, the Text gets its maximum width from the screen, and breaks the line so that it fits the screen.

Since Row won’t impose any constraints onto its children, it’s quite possible that the children might be too big to fit the available width of the Row. In this case, just like an UnconstrainedBox, the Row displays the “overflow warning”.

The only difference if you use Flexible instead of Expanded, is that Flexible lets its child have the same or smaller width than the Flexible itself, while Expanded forces its child to have the exact same width of the Expanded. But both Expanded and Flexible ignore their children’s width when sizing themselves.

The Scaffold tells the Container that it can be any size it wants, but not bigger than the screen.

When a widget tells its child that it can be smaller than a certain size, we say the widget supplies loose constraints to its child.

If you want the Scaffold’s child to be exactly the same size as the Scaffold itself, you can wrap its child with SizedBox.expand.

When a widget tells its child that it must be of a certain size, we say the widget supplies tight constraints to its child.

Tight vs. loose constraints

A tight constraint offers a single possibility, an exact size. In other words, a tight constraint has its maximum width equal to its minimum width; and has its maximum height equal to its minimum height.

the screen forces the red Container to be exactly the same size as the screen. The screen does that, of course, by passing tight constraints to the Container.

If you go to Flutter’s box.dart file and search for the BoxConstraints constructors, you’ll find the following:

BoxConstraints.tight(Size size)
   : minWidth = size.width,
     maxWidth = size.width,
     minHeight = size.height,
     maxHeight = size.height;
BoxConstraints.loose(Size size)
   : minWidth = 0.0,
     maxWidth = size.width,
     minHeight = 0.0,
     maxHeight = size.height;

A loose constraint, on the other hand, sets the maximum width and height, but lets the widget be as small as it wants. In other words, a loose constraint has a minimum width and height both equal to zero.

the Center lets the red Container be smaller, but not bigger than the screen. The Center does that, of course, by passing loose constraints to the Container. Ultimately, the Center’s very purpose is to transform the tight constraints it got from its parent (the screen) to loose constraints for its child (the Container).

Learning the layout rules for specific widgets

The layout source-code is usually complex, so it’s probably better to just read the documentation. However, if you decide to study the layout source-code, you can easily find it by using the navigating capabilities of your IDE.

  • Find a Column in your code and navigate to its source code. To do this, use command+B (macOS) or control+B (Windows/Linux) in Android Studio or IntelliJ. You’ll be taken to the basic.dart file. Since Column extends Flex, navigate to the Flex source code (also in basic.dart).
  • Scroll down until you find a method called createRenderObject(). As you can see, this method returns a RenderFlex. This is the render-object for the Column. Now navigate to the source-code of RenderFlex, which takes you to the flex.dart file.
  • Scroll down until you find a method called performLayout(). This is the method that does the layout for the Column.

Dealing with box constraints

Column extends Flex extends MultiChildRenderObjectWidget extends RenderObjectWidget extends Widget, RenderWrap extends RenderBox extends RenderObject

Flex implements createRenderObject() method by RenderObjectWidget, which returns a RenderWrap

In Flutter, widgets are rendered by their underlying RenderBox objects. Render boxes are given constraints by their parent, and size themselves within those constriants. Constraints consist of minimum and maximum widths and heights; sizes consist of a specific width and height.

Generally, there are three kinds of boxes, in terms of how they handle thier constraints:

  • Those that try to be as big as possible. For example, the boxes used by Center and ListView.
  • Those that try to be the same size as their children. For example, the boxes used by Transform and Opacity.
  • Those that try to be a particular size. For example, the boxes used by Image and Text.

Some widgets, for example Container, vary from type to type based on their constructor arguments. In the case of Container, it defaults to trying to be as big as possible, but if you give it a width, for instance, it tries to honor that and be that particular size.

Others, for example Row and Column (flex boxes) vary based on the constraints they are given, as described below in the “Flex” section.

The constraints are sometimes “tight”, meaning that they leave no room for the render box to decide on a size (for example, if the minimum and maximum width are the same, it is said to have a tight width). An example of this is the App widget, which is contained by the RenderView class: the box used by the child returned by the application’s build function is given a constraint that forces it to exactly fill the application’s content area (typically, the entire screen). Many of the boxes in Flutter, especially those that just take a single child, pass their constraint on to their children. This means that if you nest a bunch of boxes inside each other at the root of your application’s render tree, they’ll all exactly fit in each other, forced by these tight constraints.

Some boxes loosen the constraints, meaning the maximum is maintained but the minimum is removed. For example, Center.

Unbounded constraints

In certain situations, the constraint that is given to a box is unbounded, or infinite. This means that either the maximum width or the maximum height is set to double.infinity.

A box that tries to be as big as possible won’t function usefully when given an unbounded constraint and, in debug mode, such a combination throws an exception that points to this file.

The most common cases where a render box finds itself with unbounded constraints are within flex boxes (Row and Column), and within scrollable regions (ListView and other ScrollView subclasses).

In particular, ListView tries to expand to fit the space available in its cross-direction (for example, if it’s a vertically-scrolling block, it tries to be as wide as its parent). If you nest a vertically scrolling ListView inside a horizontally scrolling ListView, the inner one tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction.

Flex

Flex boxes themselves (Row and Column) behave differently based on whether they are in bounded constraints or unbounded constraints in their given direction.

In bounded constraints, they try to be as big as possible in that direction.

In unbounded constraints, they try to fit their children in that direction. In this case, you cannot set flex on the children to anything other than 0. In the widget library, this means that you cannot use Expanded when the flex box is inside another flex box or inside a scrollable. If you do, you’ll get an exception message pointing you at this document.

In the cross direction, for example, in the width for Column (vertical flex) or in the height for Row (horizontal flex), they must never be unbounded, otherwise they would not be able to reasonably align their children.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐李同学(李俊德-大连理工大学)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值