© Fu Cheng 2019
F. ChengFlutter Recipeshttps://doi.org/10.1007/978-1-4842-4982-6_4

4. Widget Basics

Fu Cheng1 
(1)
Sandringham, Auckland, New Zealand
 

When building Flutter apps, most of the time you are dealing with widgets. This chapter provides basic background information about widgets in Flutter. It also covers several basic widgets that display texts, images, icons, buttons, and placeholders.

4.1 Understanding Widgets

Problem

You want to know how to use components in Flutter.

Solution

Widgets are everywhere in Flutter.

Discussion

If you have been involved in development of user interface, you should be familiar with concepts like widgets or components. These concepts represent reusable building blocks to create user interface. A good user interface library should have a large number of high-quality and easy-to-use components. Buttons, icons, images, menus, dialogs, and form inputs are all examples of components. Components can be big or small. Complicated components are usually composed of small components. You can create your own components by following the component model. You can also choose to share your components to the community. A good eco-system of components is a key factor for a user interface library to be successful.

Flutter uses widgets to describe reusable building blocks in the user interface. Comparing to other libraries, widget in Flutter is a much broader concept. Not only common components like buttons and form inputs are widgets, layout constraints are also expressed as widgets in Flutter. For example, if you want to place a widget in the center of a box, you simply wrap the widget into a Center widget. Widgets are also used to retrieve context data. For example, DefaultTextStyle widget gets the TextStyle applies to un-styled Text widgets.

Widget in Flutter is an immutable description of a part of the user interface. All fields of a widget class are final and set in the constructor. Widget constructors only have named parameters. A widget can have one or many widgets as the children. Widgets of a Flutter app creates a tree-like hierarchy. The main() method of a Flutter app’s entry point file uses the runApp() method to start the app. The only parameter of runApp() is a Widget object. This Widget object is the root of the app’s widgets tree. Widgets are only static configurations that describe how to configure a subtree in the hierarchy. To actually run the app, we need a way to manage instantiation of widgets.

Flutter uses Element to represent an instantiation of a Widget at a particular location in the tree. A Widget can be instantiated zero or many times. The process to turn Widgets to Elements is called inflation. Widget class has a createElement() method to inflate the widget to a concrete instance of Element. Flutter framework is responsible for managing the lifecycle of elements. The widget associated with an element may change over time. The framework updates the element to use the new configuration.

When running the app, Flutter framework is responsible for rendering elements to create a render tree, so the end user can actually see the user interface. A render tree is composed of RenderObjects with the root of a RenderView. If you are using Android Studio, you can actually see the widgets tree and the render tree in Flutter Inspector view. Select View ➤ Tool Windows ➤ Flutter Inspector to open the Flutter Inspector view. Figure 4-1 shows the widgets tree in Flutter Inspector. The top panel shows the widgets tree, while the bottom panel shows the details of a widget.
../images/479501_1_En_4_Chapter/479501_1_En_4_Fig1_HTML.jpg
Figure 4-1

Widgets tree in Flutter Inspector

Figure 4-2 shows the render tree in Flutter Inspector. The root is a RenderView.
../images/479501_1_En_4_Chapter/479501_1_En_4_Fig2_HTML.jpg
Figure 4-2

Render tree in Flutter Inspector

4.2 Understanding BuildContext

Problem

You want to access information related to a widget in the widgets tree.

Solution

WidgetBuilder functions have a BuildContext parameter to access information related to a widget in the widgets tree. You can see BuildContext in StatelessWidget.build() and State.build() methods .

Discussion

When building a widget, the location of the widget in the widgets tree may determine its behavior, especially when it has an InheritedWidget as its ancestor. BuildContext class provides methods to access information related to the location; see Table 4-1.
Table 4-1

Methods of BuildContext

Name

Description

ancestorInheritedElementForWidgetOfExactType

Get the InheritedElement corresponding to the nearest ancestor widget of the given type of InheritedWidget.

ancestorRenderObjectOfType

Get the RenderObject of the nearest ancestor RenderObjectWidget widget.

ancestorStateOfType

Get the State object of the nearest ancestor StatefulWidget widget.

rootAncestorStateOfType

Get the State object of the furthest ancestor StatefulWidget widget.

ancestorWidgetOfExactType

Get the nearest ancestor Widget.

findRenderObject

Get the current RenderObject for the widget.

inheritFromElement

Register this BuildContext with the given ancestor InheritedElement such that this BuildContext is rebuilt when the ancestor’s widget changes.

inheritFromWidgetOfExactType

Get the nearest InheritedWidget of the given type and register this BuildContext such that this BuildContext is rebuilt when the widget changes.

visitAncestorElements

Visit ancestor elements.

visitChildElements

Visit children elements.

BuildContext is actually the interface of Element class. In StatelessWidget.build() and State.build() methods , the BuildContext object represents the location where the current widget is inflated. In Listing 4-1, ancestorWidgetOfExactType() method is used to get the ancestor widget of type Column.
class WithBuildContext extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Column column = context.ancestorWidgetOfExactType(Column);
    return Text(column.children.length.toString());
  }
}
Listing 4-1

Use BuildContext

4.3 Understanding Stateless Widget

Problem

You want to create a widget that has no mutable state.

Solution

Extend from StatelessWidget class.

Discussion

When using a widget to describe a part of user interface, if the part can be fully described using the configuration information of the widget itself and the BuildContext in which it’s inflated, then this widget should extend from StatelessWidget. When creating a StatelessWidget class, you need to implement the build() method which accepts a BuildContext and returns a Widget. In Listing 4-2, HelloWorld class extends from StatelessWidget class and returns a Center widget in the build() method.
class HelloWorld extends StatelessWidget {
  const HelloWorld({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Hello World!'),
    );
  }
}
Listing 4-2

Example of StatelessWidget

4.4 Understanding Stateful Widget

Problem

You want to create a widget that has mutable state.

Solution

Extend from StatefulWidget class.

Discussion

If a part of user interface may change dynamically, you need to extend from StatefulWidget class . StatefulWidgets themselves are immutable with states managed in State objects created by them. A StatefulWidget subclass needs to implement the createState() method that returns a State<StatefulWidget> object. When the state changes, the State object should call setState() method to notify the framework to trigger the update. In Listing 4-3, _CounterState class is the State object of the Counter widget. When the button is pressed, the value is updated in the setState() method, which updates the _CounterState widget to show the new value.
class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
  int value = 0;
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Text('$value'),
        RaisedButton(
          child: Text('+'),
          onPressed: () {
            setState(() {
              value++;
            });
          },
        ),
      ],
    );
  }
}
Listing 4-3

Example of StatefulWidget

4.5 Understanding Inherited Widget

Problem

You want to propagate data down the widgets tree.

Solution

Extend from InheritedWidget class .

Discussion

When building a subtree of widgets, you may need to propagate data down the widgets tree. For example, your root widget of a subtree may define some context data, for example, configuration data retrieved from the server. Other widgets in the subtree may also need to access the context data. One possible way is to add the context data to a widget’s constructor, then propagate the data as constructor parameter of children widgets. The major drawback of this solution is that you need to add the constructor parameter to all widgets in the subtree. Even though some widgets may not actually need the data, they still need to have the data to pass to their children widgets.

A better approach is to use InheritedWidget class. BuildContext class has an inheritFromWidgetOfExactType() method to get the nearest instance of a particular type of InheritedWidget. With InheritedWidget, you can store the context data in an InheritedWiget instance. If a widget needs to access the context data, you can use inheritFromWidgetOfExactType() method to get the instance and access the data. If an inherited widget changes state, it will cause its consumers to rebuild.

In Listing 4-4, ConfigWidget class has the data config. The static of() method gets the nearest ancestor ConfigWidget instance for the config value. The method updateShouldNotify() determines when the consumer widgets should be notified.
class ConfigWidget extends InheritedWidget {
  const ConfigWidget({
    Key key,
    @required this.config,
    @required Widget child,
  })  : assert(config != null),
        assert(child != null),
        super(key: key, child: child);
  final String config;
  static String of(BuildContext context) {
    final ConfigWidget configWidget =
        context.inheritFromWidgetOfExactType(ConfigWidget);
    return configWidget?.config ?? ";
  }
  @override
  bool updateShouldNotify(ConfigWidget oldWidget) {
    return config != oldWidget.config;
  }
}
Listing 4-4

Example of InheritedWidget

In Listing 4-5, ConfigUserWidget class uses the ConfigWidget.of() method to get the config value.
class ConfigUserWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Data is ${ConfigWidget.of(context)}');
  }
}
Listing 4-5

Use of ConfigWidget

In Listing 4-6, ConfigWidget instance has a config value of “Hello!” and a descendant ConfigUserWidget instance.
ConfigWidget(
  config: 'Hello!',
  child: Center(
    child: ConfigUserWidget(),
  ),
);
Listing 4-6

Complete example

4.6 Displaying Text

Problem

You want to display some text.

Solution

Use the Text and RichText widgets .

Discussion

Almost all apps need to display some text to the end users. Flutter provides several classes related to text. Text and RichText are the two widgets to display text. In fact, Text uses RichText internally. The build() method of Text widget returns a RichText instance. The difference between Text and RichText is that Text uses the style from the closest enclosing DefaultTextStyle object, while RichText requires explicit style.

Text

Text has two constructors. The first constructor Text() accepts a String as the text to display. Another constructor Text.rich() accepts a TextSpan object to represent both text and style. The simplest form to create a Text widget is Text('Hello world'), which displays text using the style from the closest enclosing DefaultTextStyle object. Both Text() and Text.rich() constructors have several named parameters to customize them; see Table 4-2.
Table 4-2

Named parameters of Text() and Text.rich()

Name

Type

Description

style

TextStyle

Style of the text.

textAlign

TextAlign

How text should be aligned horizontally.

textDirection

TextDirection

Direction of text.

locale

Locale

Locale to select font based on Unicode.

softWrap

bool

Whether to break text at soft line breaks.

overflow

TextOverflow

How to handle text overflow.

textScaleFactor

double

The factor to scale the text.

maxLines

int

The maximum number of lines. If the text exceeds the limit, it will be truncated according to the strategy specified in overflow.

semanticsLabel

String

Semantics label for the text.

TextAlign is an enum type with values shown in Table 4-3.
Table 4-3

TextAlign values

Name

Description

left

Align text on the left edge of its container.

right

Align text on the right edge of its container.

center

Align text in the center of its container.

justify

For lines of text end with soft line breaks, stretch these lines to fill the width of the container; for lines of text end with hard line breaks, align them toward the start edge.

start

Align text on the leading edge of its container. The leading edge is the left edge for left-to-right text, while it’s the right edge for right-to-left text.

end

Align text on the trailing edge of its container. The trailing edge is the opposite of the leading edge.

It’s recommended to always use TextAlign values start and end instead of left and right to better handle bidirectional text. TextDirection is an enum type with values ltr and rtl. TextOverflow is an enum type with values shown in Table 4-4.
Table 4-4

TextOverflow values

Name

Description

clip

Clip the overflowing text.

fade

Fade the overflowing text to be transparent.

ellipsis

Add an ellipsis after the overflowing text.

DefaultTextStyle is an InheritedWidget that has properties style, textAlign, softWrap, overflow, and maxLines which have the same meaning as named parameters shown in Table 4-2. If a named parameter is provided in the constructors Text() and Text.rich(), then the provided value overrides the value in the nearest ancestor DefaultTextStyle object. Listing 4-7 shows several examples of using Text widget.
Text('Hello World')
Text(
  'Bigger Bold Text',
  style: TextStyle(fontWeight: FontWeight.bold),
  textScaleFactor: 2.0,
);
Text(
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt',
  maxLines: 1,
  overflow: TextOverflow.ellipsis,
);
Listing 4-7

Examples of Text

TextSpan

The constructor Text.rich() takes a TextSpan object as the required parameter. TextSpan represents an immutable span of text. TextSpan() constructor has four named parameters; see Table 4-5. TextSpans are organized in a hierarchy. A TextSpan object may have many TextSpan objects as the children. Children TextSpans can override styles from their parent.
Table 4-5

Named parameters of TextSpan()

Name

Type

Description

style

TextStyle

Style of the text and children.

text

String

Text in the span.

children

List<TextSpan>

TextSpans as children of this span.

recognizer

GestureRecognizer

A gesture recognizer to receive events.

Listing 4-8 shows the example of using Text.rich(). This example displays the sentence “The quick brown fox jumps over the lazy dog” using different styles.
Text.rich(TextSpan(
  style: TextStyle(
    fontSize: 16,
  ),
  children: [
    TextSpan(text: 'The quick brown '),
    TextSpan(
        text: 'fox',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.red,
        )),
    TextSpan(text: ' jumps over the lazy '),
    TextSpan(
        text: 'dog',
        style: TextStyle(
          color: Colors.blue,
        )),
  ],
));
Listing 4-8

Example of Text.rich()

RichText

RichText always uses TextSpan objects to represent text and styles. RichText() constructor has a required named parameter text of the type TextSpan. It also has optional named parameters textAlign, textDirection, softWrap, overflow, textScaleFactor, maxLines, and locale. These optional named parameters have the same meaning as Text() constructor shown in Table 4-2.

Text displayed in RichText requires explicit styling. You can use DefaultTextStyle.of() to get the default style from the BuildContext object. This is exactly what Text does internally. Text widget gets the default style and merges with the style provided in the style parameter, then creates a RichText with a TextSpan wrapping the text and merged style. If you find out that you do need to use the default style as the base, you should use Text directly instead of RichText. Listing 4-9 shows an example of using RichText.
RichText(
  text: TextSpan(
    text: 'Level 1',
    style: TextStyle(color: Colors.black),
    children: [
      TextSpan(
        text: 'Level 2',
        style: TextStyle(fontWeight: FontWeight.bold),
        children: [
          TextSpan(
            text: 'Level 3',
            style: TextStyle(color: Colors.red),
          ),
        ],
      ),
    ],
  ),
);
Listing 4-9

Example of RichText

4.7 Applying Styles to Text

Problem

You want the displayed text to have different styles.

Solution

Use TextStyle to describe styles.

Discussion

TextStyle describes styles applied to text. TextStyle() constructor has many named parameters to describe the style; see Table 4-6.
Table 4-6

Named parameters of TextStyle()

Name

Type

Description

color

Color

Color of the text.

fontSize

Double

Size of font.

fontWeight

FontWeight

Typeface thickness.

fontStyle

FontStyle

Typeface variant.

letterSpacing

Double

Space between each letter.

wordSpacing

Double

Space between each word.

textBaseLine

TextBaseLine

Common baseline to align this text span and its parent span.

height

Double

Height of the text.

locale

Locale

Locale to select region-specific glyphs.

foreground

Paint

Foreground for the text.

background

Paint

Background for the text.

shadows

List<Shadow>

Shadows painted underneath the text.

decoration

TextDecoration

Decoration of the text.

decorationColor

Color

Color of text decorations.

decorationStyle

TextDecorationStyle

Style of text decorations.

debugLabel

String

Description of the style for debugging.

fontFamily

String

Name of the font.

package

String

Use with fontFamily if the font is defined in a package.

FontWeight class defines values w100, w200, w300, w400, w500, w600, w700, w800, and w900. FontWeight.w100 is the thinnest, while w900 is the thickest. FontWeight.bold is an alias of FontWeight.w700, while FontWeight.normal is an alias of FontWeight.w400. FontStyle is an enum type with two values italic and normal. TextBaseline is an enum type with values alphabetic and ideographic.

TextDecoration class defines different types of text decorations. You can also use constructor TextDecoration.combine() to create a new TextDecoration instance by combing a list of TextDecoration instances. For example, TextDecoration.combine([TextDecoration.underline, TextDecoration.overline]) instance draws lines underneath and above text. Table 4-7 shows constants in TextDecoration.
Table 4-7

TextDecoration constants

Name

Description

none

No decoration.

underline

Draw a line underneath text.

overline

Draw a line above text.

lineThrough

Draw a line through text.

TextDecorationStyle is an enum type with values shown in Table 4-8. TextDecorationStyle defines the style of lines created by TextDecoration.
Table 4-8

TextDecorationStyle values

Name

Description

solid

Draw a solid line.

double

Draw two lines.

dotted

Draw a dotted line.

dashed

Draw a dashed line.

wavy

Draw a sinusoidal line.

Listing 4-10 shows an example of using TextDecoration and TextDecorationStyle.
Text(
  'Decoration',
  style: TextStyle(
    fontWeight: FontWeight.w900,
    decoration: TextDecoration.lineThrough,
    decorationStyle: TextDecorationStyle.dashed,
  ),
);
Listing 4-10

Example of using TextDecoration and TextDecorationStyle

If you want to create a copy of a TextStyle instance with some properties updated, use the copyWith() method . The apply() method also creates a new TextStyle instance, but it allows updating some properties using factor and delta. For example, the named parameters fontSizeFactor and fontSizeDelta can update the font size. The updated value of fontSize is calculated with "fontSize * fontSizeFactor + fontSizeDelta". You can also update values of height, letterSpacing, and wordSpacing using the same pattern. For fontWeight, only fontWeightDelta is supported. In Listing 4-11, the TextStyle applied to the text has updated values of fontSize and decoration.
Text(
  'Scale',
  style: DefaultTextStyle.of(context).style.apply(
        fontSizeFactor: 2.0,
        fontSizeDelta: 1,
        decoration: TextDecoration.none,
      ),
);
Listing 4-11

Update TextStyle

4.8 Displaying Images

Problem

You want to display images loaded from network.

Solution

Use Image.network() with the image URL to load and display an image.

Discussion

If you have images hosted in your own servers or other places, you can display them using the Image.network() constructor. Image.network() constructor only requires the URL of the image to load. An image widget should be given specific dimension using the named parameters width and height or placed in a context that sets tight layout constraints. This is because the dimension of the image may change when the image is loaded. Without a strict size constraint, the image widget may affect layout of other widgets. In Listing 4-12, the size of the image widget is specified with named parameters width and height .
Image.network(
  'https://picsum.photos/400/300',
  width: 400,
  height: 300,
);
Listing 4-12

Example of Image.network()

All downloaded images are cached regardless of HTTP headers. This means that all HTTP cache control headers will be ignored. You can use cache buster to force cached images to refresh. For example, you can add a random string to the image URL.

If extra HTTP headers are required to load the image, you can specify the headers parameter of type Map<String, String> to provide these headers. A typical use case is to load protected images that require HTTP headers for authentication.

If an image cannot cover the whole area of a box, you can use the repeat parameter of type ImageRepeat to specify how images are repeated. ImageRepeat is an enum type with values shown in Table 4-9. The default value is noRepeat .
Table 4-9

ImageRepeat values

Name

Description

Repeat

Repeat in both x and y directions.

repeatX

Repeat only in the x direction.

repeatY

Repeat only in the y direction.

noRepeat

No repeat. The uncovered area will be transparent.

In Listing 4-13, the image is placed into a SizedBox which is larger than the image. By using ImageRepeat.repeat, the box is filled with this image.
SizedBox(
  width: 400,
  height: 300,
  child: Image.network(
    'https://picsum.photos/300/200',
    alignment: Alignment.topLeft,
    repeat: ImageRepeat.repeat,
  ),
);
Listing 4-13

Repeated images

4.9 Displaying Icons

Problem

You want to use icons.

Solution

Use Icon to show icons from Material Design or icon packs from community.

Discussion

Icons are used extensively in mobile apps. Comparing to text, icons take less screen estate to express the same semantics. Icons can be created from font glyphs or images. The Icon widget is drawn with a font glyph. A font glyph is described with IconData class. To create an IconData instance, the Unicode code point of this icon in the font is required.

Icons class has a number of predefined IconData constants for icons in Material Design (https://​material.​io/​tools/​icons/​). For example, Icons.call is the IconData constant for the icon named “call”. If the app uses Material Design, then these icons can be used out of box. CupertinoIcons class has a number of predefined IconData constants for iOS-style icons.

Icon() constructor has named parameters size and color to specify the size and color of the icon, respectively. Icons are always square with width and height both equal to size. The default value of size is 24. Listing 4-14 creates a red Icons.call icon of size 100.
Icon(
  Icons.call,
  size: 100,
  color: Colors.red,
);
Listing 4-14

Example of Icon()

To use the popular Font Awesome icons, you can use the package font_awesome_flutter ( https://pub.dartlang.org/packages/font_awesome_flutter ). After adding the package dependency to pubspec.yaml file, you can import the file to use FontAwesomeIcons class. Similar with Icons class, FontAwesomeIcons class has a number of IconData constants for different icons in Font Awesome. Listing 4-15 creates a blue FontAwesomeIcons.angry icon of size 80.
Icon(
  FontAwesomeIcons.angry,
  size: 80,
  color: Colors.blue,
);
Listing 4-15

Use Font Awesome icon

4.10 Using Buttons with Text

Problem

You want to use buttons with text.

Solution

Use button widgets FlatButton, RaisedButton, OutlineButton, and CupertinoButton.

Discussion

Flutter has different types of buttons for Material Design and iOS. These button widgets all have a required parameter onPressed to specify the handler function when pressed. If the onPressed handler is null, the button is disabled. The content of a button is specified with the parameter child of type Widget. FlatButton, RaisedButton, and OutlineButton have different styles and behaviors reacting to touches:
  • A FlatButton has zero elevation and no visible borders. It reacts to touches by filling with color specified by highlightColor.

  • A RaisedButton has elevation and is filled with color. It reacts to touches by increasing elevation to highlightElevation.

  • An OutlineButton has borders, an initial elevation of 0.0, and transparent background. It reacts to touches by making its background opaque with the color and increasing its elevation to highlightElevation.

FlatButtons should be used on toolbars, in dialogs, in cards, or inline with other content where there is enough space to make buttons’ presence obvious. RaisedButtons should be used where using space is not enough to make the buttons stand out. OutlineButton is the cross between RaisedButton and FlatButton. OutlineButtons can be used when neither FlatButtons nor RaisedButtons are appropriate.

If you prefer the iOS-style button, you can use the CupertinoButton widget. CupertinoButton reacts to touches by fading out and in. Listing 4-16 shows examples of creating different types of buttons.
FlatButton(
  child: Text('Flat'),
  color: Colors.white,
  textColor: Colors.grey,
  highlightColor: Colors.red,
  onPressed: () => {},
);
RaisedButton(
  child: Text('Raised'),
  color: Colors.blue,
  onPressed: () => {},
);
OutlineButton(
  child: Text('Outline'),
  onPressed: () => {},
);
CupertinoButton(
  child: Text('Cupertino'),
  color: Colors.green,
  onPressed: () => {},
);
Listing 4-16

Different types of buttons

4.11 Using Buttons with Icons

Problem

You want to use buttons with icons.

Solution

Use IconButton widget, FlatButton.icon(), RaisedButton.icon(), and OutlineButton.icon().

Discussion

There are two ways to create a button with an icon. If only the icon is enough, use IconButton widget. If both the icon and text are required, use constructors FlatButton.icon() , RaisedButton.icon() , or OutlineButton.icon() .

IconButton constructor requires the icon parameter to specify the icon. FlatButton.icon(), RaisedButton.icon(), and OutlineButton.icon() use the parameters icon and label to specify the icon and text, respectively. Listing 4-17 shows examples of using IconButton() and RaisedButton.icon().
IconButton(
  icon: Icon(Icons.map),
  iconSize: 50,
  tooltip: 'Map',
  onPressed: () => {},
);
RaisedButton.icon(
  icon: Icon(Icons.save),
  label: Text('Save'),
  onPressed: () => [],
);
Listing 4-17

Examples of IconButton() and RaisedButton.icon()

4.12 Adding Placeholders

Problem

You want to add placeholders to represent widgets that will be added later.

Solution

Use Placeholder.

Discussion

Before implementing the interface of an app, you usually have a basic idea about how the app looks like. You can start by breaking down the interface into many widgets. You can use placeholders to represent unfinished widgets during development, so you can test the layout of other widgets. For example, if you need to create two widgets, one displays at the top, while the other one displays at the bottom. If you choose to create the bottom widget first and use a placeholder for the top widget, you can see the bottom widget in its desired position.

The Placeholder() constructor takes named parameters color, strokeWidth, fallbackWidth, and fallbackHeight. The placeholder is drawn as a rectangle and two diagonals. The parameters color and strokeWidth specify color and width of the lines, respectively. By default, the placeholder fits its container. However, if the placeholder’s container is unbounded, it uses the given fallbackWidth and fallbackHeight to determine the size. Both fallbackWidth and fallbackHeight have the default value 400.0. Listing 4-18 shows an example of Placeholder widget.
Placeholder(
  color: Colors.red,
  strokeWidth: 1,
  fallbackHeight: 200,
  fallbackWidth: 200,
);
Listing 4-18

Example of Placeholder

4.13 Summary

Widgets are everywhere in Flutter apps. This chapter provides basic introduction of widgets in Flutter, including StatelessWidget, StatefulWidget, and InheritedWidget. This chapter also covers usage of common basic widgets to display text, images, icons, buttons, and placeholders. The next chapter will discuss layout in Flutter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.219.65