diff --git a/firebase.json b/firebase.json index f991887324..58ba8558be 100644 --- a/firebase.json +++ b/firebase.json @@ -101,6 +101,7 @@ { "source": "/accessibility-and-localization", "destination": "/ui/accessibility-and-internationalization", "type": 301 }, { "source": "/accessibility-and-localization/:rest*", "destination": "/ui/accessibility-and-internationalization/:rest*", "type": 301 }, { "source": "/add-to-app/android/add-splash-screen", "destination": "/platform-integration/android/splash-screen", "type": 301 }, + { "source": "/codelabs/layout-basics", "destination": "/ui/layout", "type": 301 }, { "source": "/codelabs/explicit-animations", "destination": "/ui/animations/tutorial", "type": 301 }, { "source": "/cookbook/games", "destination": "https://flutter.dev/games", "type": 301 }, { "source": "/cookbook/games/google-mobile-ads", "destination": "/cookbook/plugins/google-mobile-ads", "type": 301 }, diff --git a/src/codelabs/index.md b/src/codelabs/index.md index 476c2d0ee8..918baca93e 100644 --- a/src/codelabs/index.md +++ b/src/codelabs/index.md @@ -84,10 +84,6 @@ one of the following codelabs: Learn about Material Design and basic Flutter concepts, like layout and animations: -* [Basic Flutter layout concepts][]
- Use DartPad in a browser (no downloads required!) - to learn the basics of creating a Flutter layout. - * [How to debug layout issues with the Flutter Inspector][]
Not an official codelab, but step-by-step instructions on how to debug common layout problems using the Flutter @@ -145,7 +141,6 @@ like layout and animations: [Building next generation UIs in Flutter]: {{site.codelabs}}/codelabs/flutter-next-gen-uis#0 [Adaptive Apps in Flutter]: {{site.codelabs}}/codelabs/flutter-adaptive-app [animations]: {{site.pub}}/packages/animations -[Basic Flutter layout concepts]: {{site.url}}/codelabs/layout-basics [Building Beautiful Transitions with Material Motion for Flutter]: {{site.codelabs}}/codelabs/material-motion-flutter [Building scrolling experiences in Flutter]: {{site.youtube-site}}/watch?v=YY-_yrZdjGc [How to debug layout issues with the Flutter Inspector]: {{site.flutter-medium}}/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db diff --git a/src/codelabs/layout-basics.md b/src/codelabs/layout-basics.md deleted file mode 100644 index b91b57d786..0000000000 --- a/src/codelabs/layout-basics.md +++ /dev/null @@ -1,3346 +0,0 @@ ---- -title: "Basic Flutter layout concepts" -description: "A codelab that teaches basic Flutter layout concepts through DartPad examples and exercises." -toc: true -js: -- defer: true - url: https://dartpad.dev/inject_embed.dart.js ---- - -Welcome to the Flutter layout codelab, -where you learn how to build a Flutter UI without -downloading and installing Flutter or Dart! - -{% include docs/dartpad-troubleshooting.md %} - -Flutter is different from other frameworks because its UI -is built in code, not (for example) in an XML file or similar. -Widgets are the basic building blocks of a Flutter UI. -As you progress through this codelab, -you'll learn that almost everything in Flutter is a widget. -A widget is an immutable object that describes a specific part of a UI. -You'll also learn that Flutter widgets are composable, meaning -that you can combine existing widgets to make more sophisticated widgets. -At the end of this codelab, -you'll get to apply what you've learned -into building a Flutter UI that displays a business card. - -**Estimated time to complete this codelab: 45-60 minutes.** - -## Row and Column classes -`Row` and `Column` are classes that contain and lay out widgets. -Widgets inside of a `Row` or `Column` are called *children*, -and `Row` and `Column` are referred to as *parents*. -`Row` lays out its widgets horizontally, -and `Column` lays out its widgets vertically. - -#### Example: Creating a Column -{:.no_toc} -{{site.alert.secondary}} -{:.no_toc} - The following example displays the differences between - a `Row` and `Column`. - - **1.** Click the **Run** button. - - **2.** In the code, change the `Row` to a `Column`, and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - runApp(MyApp()); - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final columns = controller.widgetList(find.byType(Column)); - - if (columns.isEmpty) { - _result(false, ['The Row contains three BlueBox widgets and lays them out horizontally.']); - return; - } - - if (columns.length > 1) { - _result(false, ['Found ${columns.length} Rows, rather than just one.']); - return; - } - - final column = columns.first as Column; - - if (column.children.length != 3 || column.children.any((w) => w is! BlueBox)) { - _result(false, ['Row/Column should contain three children, all BlueBox widgets.']); - return; - } - - _result(true, ['The Column contains three BlueBox widgets and lays them out vertically.']); -} -{$ end test.dart $} -``` - -## Axis size and alignment - -So far, the `BlueBox` widgets have been squished together -(either to the left or at the top of the UI Output). -You can change how the `BlueBox` widgets are spaced -out using the axis size and alignment properties. - -### mainAxisSize property - -`Row` and `Column` occupy different main axes. -A `Row`'s main axis is horizontal, -and a `Column`'s main axis is vertical. -The `mainAxisSize` property determines how much -space a `Row` and `Column` can occupy on their main axes. -The `mainAxisSize` property has two possible values: - -`MainAxisSize.max` -: `Row` and `Column` occupy all of the space on their main axes. - If the combined width of their children is - less than the total space on their main axes, - their children are laid out with extra space. - -`MainAxisSize.min` -: `Row` and `Column` only occupy enough space on their main axes - for their children. Their children are laid out without extra space - and at the middle of their main axes. - -{{site.alert.tip}} - `MainAxisSize.max` is the `mainAxisSize` property's default value. - If you don't specify another value, - the default value is used, - as shown in the previous example. -{{site.alert.end}} - -#### Example: Modifying axis size -{:.no_toc} -{{site.alert.secondary}} -{:.no_toc} - - The following example explicitly sets `mainAxisSize` - to its default value, `MainAxisSize.max`. - - **1.** Click the **Run** button. - - **2.** Change `MainAxisSize.max` to `MainAxisSize.min`, - and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.max, - children: [ - BlueBox(), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.min) { - _result(false, ['Row lays out the BlueBox widgets with extra space. Change MainAxisSize.max to MainAxisSize.min']); - return; - } - - if (row.children.length != 3 || row.children.any((w) => w is! BlueBox)) { - _result(false, ['There should only be three children, all BlueBox widgets.']); - return; - } - - _result(true, ['Row lays out the BlueBox widgets without extra space, and the BlueBox widgets are positioned at the middle of Row\'s main axis.']); -} -{$ end test.dart $} -``` - -### mainAxisAlignment property - -When `mainAxisSize` is set to `MainAxisSize.max`, -`Row` and `Column` might lay out their children with extra space. -The `mainAxisAlignment` property determines how `Row` and `Column` -can position their children in that extra space. -`mainAxisAlignment` has six possible values: - -`MainAxisAlignment.start` -: Positions children near the beginning of the main axis. - (Left for `Row`, top for `Column`) - -`MainAxisAlignment.end` -: Positions children near the end of the main axis. - (Right for `Row`, bottom for `Column`) - -`MainAxisAlignment.center` -: Positions children at the middle of the main axis. - -`MainAxisAlignment.spaceBetween` -: Divides the extra space evenly between children. - -`MainAxisAlignment.spaceEvenly` -: Divides the extra space evenly between children - and before and after the children. - -`MainAxisAlignment.spaceAround` -: Similar to `MainAxisAlignment.spaceEvenly`, - but reduces the space before the first - child and after the last child - to half of the width between the children. - -#### Example: Modifying main axis alignment -{:.no_toc} -{{site.alert.secondary}} - The following example explicitly sets - `mainAxisAlignment` to its default value, - `MainAxisAlignment.start`. - - **1.** Click the **Run** button. - - **2.** Change `MainAxisAlignment.start` to - `MainAxisAlignment.end`, and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - BlueBox(), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.children.length != 3 || row.children.any((w) => w is! BlueBox)) { - _result(false, ['The Row should have three children, all BlueBox widgets.']); - return; - } - - if (row.mainAxisAlignment == MainAxisAlignment.start) { - _result(false, ['MainAxisAlignment.start positions the BlueBox widgets on the left of the main axis. Change the value to MainAxisAlignment.end.']); - } else if (row.mainAxisAlignment == MainAxisAlignment.end) { - _result(true, ['MainAxisAlignment.end positions the BlueBox widgets on the right of the main axis.']); - } else if (row.mainAxisAlignment == MainAxisAlignment.center) { - _result(true, ['MainAxisAlignment.center positions the BlueBox widgets at the middle of the main axis.']); - } else if (row.mainAxisAlignment == MainAxisAlignment.spaceBetween) { - _result(true, ['The extra space is divided between the BlueBox widgets.']); - } else if (row.mainAxisAlignment == MainAxisAlignment.spaceEvenly) { - _result(true, ['The extra space is divided evenly between the BlueBox widgets and before and after them.']); - } else if (row.mainAxisAlignment == MainAxisAlignment.spaceAround) { - _result(true, ['Similar to MainAxisAlignment.spaceEvenly, but reduces half of the space before the first BlueBox widget and after the last BlueBox widget to half of the width between the BlueBox widgets.']); - } -} -{$ end test.dart $} -``` -{{site.alert.tip}} - Before moving to the next section, - change `MainAxisAlignment.end` to another value. -{{site.alert.end}} - -### crossAxisAlignment property - -The `crossAxisAlignment` property determines -how `Row` and `Column` can position their children -on their cross axes. -A `Row`'s cross axis is vertical, -and a `Column`'s cross axis is horizontal. -The `crossAxisAlignment` property has five possible values: - -`CrossAxisAlignment.start` -: Positions children near the start of the cross axis. (Top for `Row`, Left for `Column`) - -`CrossAxisAlignment.end` -: Positions children near the end of the cross axis. (Bottom for `Row`, Right for `Column`) - -`CrossAxisAlignment.center` -: Positions children at the middle of the cross axis. (Middle for `Row`, Center for `Column`) - -`CrossAxisAlignment.stretch` -: Stretches children across the cross axis. - (Top-to-bottom for `Row`, left-to-right for `Column`) - -`CrossAxisAlignment.baseline` -: Aligns children by their character baselines. - (`Text` class only, and requires that the - `textBaseline` property is set to - `TextBaseline.alphabetic`. See the - [Text widget](#text-widget) section for an example.) - -#### Example: Modifying cross axis alignment -{:.no_toc} -{{site.alert.secondary}} - The following example explicitly sets `crossAxisAlignment` - to its default value, `CrossAxisAlignment.center`. - - To demonstrate cross axis alignment, - `mainAxisAlignment` is set to - `MainAxisAlignment.spaceAround`, - and `Row` now contains a `BiggerBlueBox` widget - that is taller than the `BlueBox` widgets. - - **1.** Click the **Run** button. - - **2.** Change `CrossAxisAlignment.center` to - `CrossAxisAlignment.start`, and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - BlueBox(), - BiggerBlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} - -class BiggerBlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 100, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.children.length != 3 || row.children.any((w) => w is! BlueBox && w is! BiggerBlueBox)) { - _result(false, ['The Row should have three children, all BlueBox or BiggerBlueBox widgets.']); - return; - } - - if (row.crossAxisAlignment == CrossAxisAlignment.start) { - _result(true, ['The BlueBox and BiggerBlueBox widgets are positioned at the top of the cross axis.']); - } else if (row.crossAxisAlignment == CrossAxisAlignment.end) { - _result(true, ['The BlueBox and BiggerBlueBox widgets are positioned at the bottom of the cross axis']); - } else if (row.crossAxisAlignment == CrossAxisAlignment.center) { - _result(false, ['The widgets are positioned at the middle of the cross axis. Change CrossAxisAlignment.center to CrossAxisAlignment.start.']); - } else if (row.crossAxisAlignment == CrossAxisAlignment.stretch) { - _result(true, ['The BlueBox and BiggerBlueBox widgets are stretched across the cross axis. Change the Row to a Column, and run again.']); - } else if(row.crossAxisAlignment == CrossAxisAlignment.baseline) { - _result(false, ['Couldn\t find a text class.']); - } -} -{$ end test.dart $} -``` -{{site.alert.tip}} - Before moving to the next section, - change `CrossAxisAlignment.start` to another value. -{{site.alert.end}} - -## Flexible widget - -As you've seen, the `mainAxisAlignment` and -`crossAxisAlignment` properties determine -how `Row` and `Column` position widgets along both axes. -`Row` and `Column` first lay out widgets of a fixed size. -Fixed size widgets are considered *inflexible* because -they can't resize themselves after they've been laid out. - -The `Flexible` widget wraps a widget, -so the widget becomes resizable. -When the `Flexible` widget wraps a widget, -the widget becomes the `Flexible` widget's child -and is considered *flexible*. -After inflexible widgets are laid out, -the widgets are resized according to their -`flex` and `fit` properties: - -`flex` -: Compares itself against other `flex` - properties before determining what fraction of the - total remaining space each `Flexible` widget receives. - -`fit` -: Determines whether a `Flexible` widget - fills all of its extra space. - -#### Example: Changing fit properties -{:.no_toc} -{{site.alert.secondary}} - The following example demonstrates the `fit` property, - which can have one of two values: - - `FlexFit.loose` - : The widget's preferred size is used. (Default) - - `FlexFit.tight` - : Forces the widget to fill all of its extra space. - - In this example, change the `fit` properties to - make the `Flexible` widgets fill the extra space. - - **1.** Click the **Run** button. - - **2.** Change both `fit` values to `FlexFit.tight`, - and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - Flexible( - fit: FlexFit.loose, - flex: 1, - child: BlueBox(), - ), - Flexible( - fit: FlexFit.loose, - flex: 1, - child: BlueBox(), - ), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.children.length != 3) { - _result(false, ['The Row should have three children, all BlueBox or Flexible widgets.']); - return; - } - - if (row.children[0] is! BlueBox) { - _result(false, ['Row\'s first child should be a BlueBox.']); - return; - } - - if (row.children[1] is! Flexible) { - _result(false, ['Row\'s second child should be a Flexible class.']); - return; - } - - if (row.children[2] is! Flexible) { - _result(false, ['Row\'s third child should be a Flexible class.']); - return; - } - - final flexibleWidget = row.children[2] as Flexible; - - if (flexibleWidget.child is! BlueBox) { - _result(false, ['The Flexible classes should have BlueBox widgets as their children.']); - return; - } - - if (flexibleWidget.fit != FlexFit.tight) { - _result(false, ['The fit properties set the Flexible widgets to their preferred size. Change both fit values to FlexFit.tight.']); - return; - } - - _result(true, ['The Flexible widgets now occupy the space determined by their flex values.']); -} -{$ end test.dart $} -``` - -#### Example: Testing flex values -{:.no_toc} -{{site.alert.secondary}} - In the following example, - `Row` contains one `BlueBox` widget - and two `Flexible` widgets that wrap two - `BlueBox` widgets. The `Flexible` widgets - contain `flex` properties with `flex` - values set to 1 (the default value). - - When `flex` properties are compared against one another, - the ratio between their `flex` values determines - what fraction of the total remaining space each - `Flexible` widget receives. - - ```dart - remainingSpace * (flex / totalOfAllFlexValues) - ``` - - In this example, the sum of the `flex` values (2), - determines that both `Flexible` widgets receive - half of the total remaining space. - The `BlueBox` widget (or fixed-size widget) - remains the same size. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - Flexible( - fit: FlexFit.tight, - flex: 1, - child: BlueBox(), - ), - Flexible( - fit: FlexFit.tight, - flex: 1, - child: BlueBox(), - ), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.children.length != 3) { - _result(false, ['The Row should have three children, all BlueBlox or Flexible widgets.']); - return; - } - - if (row.children[0] is! BlueBox) { - _result(false, ['The Row\'s first child should be a BlueBox widget.']); - return; - } - - if (row.children[1] is! Flexible) { - _result(false, ['The Row\'s second child should be a Flexible widget.']); - return; - } - - if (row.children[2] is! Flexible) { - _result(false, ['The Row\'s third child should be a Flexible widget.']); - return; - } - - final flexibleWidget = row.children[1] as Flexible; - - if (flexibleWidget.child is! BlueBox) { - _result(false, ['The Flexible should have a BlueBox widget as its child.']); - return; - } - - if (flexibleWidget.flex != 1) { - _result(false, ['Notice how the flex properties divide the extra space between the two Flexible widgets.']); - return; - } - - _result(true, ['Both Flexible widgets receive half of the total remaining space.']); -} -{$ end test.dart $} -``` -{{site.alert.tip}} - Before moving to the next example, - try changing the `flex` properties to other values, - such as 2 and 1. -{{site.alert.end}} - -## Expanded widget - -Similar to `Flexible`, the `Expanded` widget can -wrap a widget and force the widget to fill extra space. - -{{site.alert.tip}} - **What's the difference between Flexible and Expanded?** - Use `Flexible` to resize widgets in a `Row` or `Column`. - That way, you can adjust a child widget's spacing - while keeping its size in relation to its parent widget. - `Expanded` changes the constraints of a child widget, - so it fills any empty space. -{{site.alert.end}} - -#### Example: Filling extra space -{:.no_toc} -{{site.alert.secondary}} - The following example demonstrates how the - `Expanded` widget forces its child widget to - fill extra space. - - **1.** Click the **Run** button. - - **2.** Wrap the second `BlueBox` widget in an `Expanded` widget. - - For example: - - ```dart - Expanded(child: BlueBox(),), - ``` - **3.** Select the **Format** button to properly format the code, - and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.children.length != 3) { - _result(false, ['The Row should have three children, all BlueBox widgets.']); - return; - } - - if (row.children[0] is! BlueBox) { - _result(false, ['The Row\'s first child should be a BlueBox widget.']); - return; - } - - if (row.children[1] is! Expanded) { - _result(false, ['Notice how Row contains extra space on its main axis. Wrap the second BlueBox widget in an Expanded widget.']); - return; - } - - if (row.children[2] is! BlueBox) { - _result(false, ['The Row\'s third child should be a Flexible widget.']); - return; - } - - _result(true, ['Expanded forces second BlueBox widget to fill the extra space.']); -} -{$ end test.dart $} -``` - -## SizedBox widget - -The `SizedBox` widget can be used in one of two ways when -creating exact dimensions. -When `SizedBox` wraps a widget, it resizes the widget -using the `height` and `width` properties. -When it doesn't wrap a widget, -it uses the `height` and `width` properties to -create empty space. - -#### Example: Resizing a widget -{:.no_toc} -{{site.alert.secondary}} - The following example wraps the middle `BlueBox` widget inside of a - `SizedBox` widget and sets the `BlueBox`'s width to 100 logical pixels. - - **1.** Click the **Run** button. - - **2.** Add a `height` property equal to 100 logical pixels - inside the `SizedBox` widget, and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.max, - children: [ - BlueBox(), - SizedBox( - width: 100, - child: BlueBox(), - ), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - - if (row.children.length != 3) { - _result(false, ['The Row should end up with three children.']); - return; - } - - if (row.children[0] is! BlueBox) { - _result(false, ['The Row\'s first child should be a BlueBox widget.']); - return; - } - - if (row.children[1] is! SizedBox) { - _result(false, ['The Row\'s second child should be a SizedBox widget.']); - return; - } - - if (row.children[2] is! BlueBox) { - _result(false, ['The Row\'s third child should be a BlueBox widget.']); - return; - } - - final sizedBox = row.children[1] as SizedBox; - - if (sizedBox.width != 100) { - _result(false, ['The SizedBox should have a width of 100.']); - return; - } - - if (sizedBox.height != 100) { - _result(false, ['The SizedBox widget resizes the BlueBox widget to 100 logical pixels wide. Add a height property inside SizedBox equal to 100 logical pixels.']); - return; - } - - _result(true, ['The SizedBox widget resizes the BlueBox widget to 100 logical pixels wide and tall.']); -} -{$ end test.dart $} -``` - -#### Example: Creating space -{:.no_toc} -{{site.alert.secondary}} - The following example contains three `BlueBox` widgets - and one `SizedBox` widget that separates the first - and second `BlueBox` widgets. The `SizedBox` widget - contains a `width` property equal to 50 logical pixels. - - **1.** Click the **Run** button. - - **2.** Create more space by adding another - `SizedBox` widget (25 logical pixels wide) - between the second and third `BlueBox` widgets, - and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - const SizedBox(width: 50), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.mainAxisAlignment == MainAxisAlignment.spaceAround - || row.mainAxisAlignment == MainAxisAlignment.spaceBetween - || row.mainAxisAlignment == MainAxisAlignment.spaceEvenly) { - _result(false, ['It\'s best to use MainAxisAlignment.start, MainAxisAlignment.end, or MainAxisAlignment.center to see how the SizedBox widgets work in a Row.']); - return; - } - - if (row.children.length != 5) { - _result(false, ['The SizedBox widget creates space at 50 logical pixels wide. Add another SizedBox class between the second and third BlueBox widgets with a width property equal to 25 logical pixels.']); - return; - } - - if (row.children[0] is! BlueBox) { - _result(false, ['The Row\'s first child should be a BlueBox widget.']); - return; - } - - if (row.children[1] is! SizedBox) { - _result(false, ['The Row\'s second child should be a SizedBox widget.']); - return; - } - - if (row.children[2] is! BlueBox) { - _result(false, ['The Row\'s third child should be a BlueBox widget.']); - return; - } - - if (row.children[3] is! SizedBox) { - _result(false, ['The Row\'s fourth child should be a SizedBox widget.']); - return; - } - - if (row.children[4] is! BlueBox) { - _result(false, ['The Row\'s fifth child should be a BlueBox widget.']); - return; - } - - final sizedBox = row.children[1] as SizedBox; - - if (sizedBox.width != 50) { - _result(false, ['The SizedBox should have a width of 50.']); - return; - } - - final sizedBox2 = row.children[3] as SizedBox; - - if (sizedBox2.width != 25) { - _result(false, ['SizedBox should have a width of 25.']); - return; - } - - _result(true, ['The SizedBox widgets create space between the BlueBox widgets, one space at 50 logical pixels and one at 25 logical pixels.']); -} -{$ end test.dart $} -``` - -## Spacer widget - -Similar to `SizedBox`, the `Spacer` widget also -can create space between widgets. - -{{site.alert.tip}} - **What's the difference between SizedBox and Spacer?** - Use `Spacer` when you want to create space using a `flex` property. - Use `SizedBox` when you want to create space - using a specific number of logical pixels. -{{site.alert.end}} - -#### Example: Creating more space -{:.no_toc} -{{site.alert.secondary}} - The following example separates the first two - `BlueBox` widgets using a `Spacer` widget with - a `flex` value of 1. - - **1.** Click the **Run** button. - - **2.** Add another `Spacer` widget (also with a `flex` value of 1) - between the second and third `BlueBox` widgets. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - BlueBox(), - const Spacer(flex: 1), - BlueBox(), - BlueBox(), - ], - ); - } -} - -class BlueBox extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.blue, - border: Border.all(), - ), - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.mainAxisAlignment == MainAxisAlignment.spaceAround - || row.mainAxisAlignment == MainAxisAlignment.spaceBetween - || row.mainAxisAlignment == MainAxisAlignment.spaceEvenly) { - _result(false, ['It\'s best to use MainAxisAlignment.start, MainAxisAlignment.end, or MainAxisAlignment.center to see how the SizedBox widgets work in a Row.']); - return; - } - - if (row.children.length != 5) { - _result(false, ['What do you think would happen if you added another Spacer widget with a flex value of 1 between the second and third BlueBox widgets?']); - return; - } - - if (row.children[0] is! BlueBox || - row.children[1] is! Spacer || - row.children[2] is! BlueBox || - row.children[3] is! Spacer || - row.children[4] is! BlueBox) { - _result(false, ['Not quite. Row should contain five children in this order: BlueBox, Spacer, BlueBox, Spacer, BlueBox.']); - return; - } - - final spacer = row.children[3] as Spacer; - - if (spacer.flex != 1) { - _result(false, ['The Spacer class should have a flex equal to 1.']); - return; - } - - _result(true, ['Both Spacer widgets create equal amounts of space between all three BlueBox widgets.']); -} -{$ end test.dart $} -``` - -## Text widget - -The `Text` widget displays text and can be configured -for different fonts, sizes, and colors. - -#### Example: Aligning text -{:.no_toc} -{{site.alert.secondary}} - The following example displays "Hey!" three times, - but at different font sizes and in different colors. - `Row` specifies the `crossAxisAlignment` - and `textBaseline` properties. - - **1.** Click the **Run** button. - - **2.** Change `CrossAxisAlignment.center` to - `CrossAxisAlignment.baseline`, and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - textBaseline: TextBaseline.alphabetic, - children: const [ - Text( - 'Hey!', - style: TextStyle( - fontSize: 30, - fontFamily: 'Futura', - color: Colors.blue, - ), - ), - Text( - 'Hey!', - style: TextStyle( - fontSize: 50, - fontFamily: 'Futura', - color: Colors.green, - ), - ), - Text( - 'Hey!', - style: TextStyle( - fontSize: 40, - fontFamily: 'Futura', - color: Colors.red, - ), - ), - ], - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.children.length != 3 || row.children.any((w) => w is! Text)) { - _result(false, ['The Row should have three children, all Text widgets.']); - return; - } - - if (row.textBaseline == null) { - _result(false, ['To use CrossAxisAlignment.baseline, you need to set the Row\'s textBaseline property.']); - return; - } - - if (row.crossAxisAlignment != CrossAxisAlignment.baseline) { - _result(false, ['The Text widgets are positioned at the middle of the cross axis. Change CrossAxisAlignment.center to CrossAxisAlignment.baseline.']); - return; - } - - _result(true, ['The Text widgets are now aligned by their character baselines.']); - -} -{$ end test.dart $} -``` - -## Icon widget - -The `Icon` widget displays a graphical symbol -that represents an aspect of the UI. -Flutter is preloaded with icon packages for -[Material][] and [Cupertino][] applications. - -#### Example: Creating an Icon -{:.no_toc} -{{site.alert.secondary}} - The following example displays the widget `Icons.widget` - from the [Material Icon library][] in red and blue. - - **1.** Click the **Run** button. - - **2.** Add another `Icon` from the - [Material Icon library][] - with a size of 50. - - **3.** Give the `Icon` a color of `Colors.amber` from the - [Material Color palette][], and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - textBaseline: TextBaseline.alphabetic, - children: const [ - Icon( - Icons.widgets, - size: 50, - color: Colors.blue, - ), - Icon( - Icons.widgets, - size: 50, - color: Colors.red, - ), - ], - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - - if (row.children.length != 3 || row.children.any((w) => w is! Icon)) { - _result(false, ['Row should have three children, all Icon widgets.']); - return; - } - - final icon = row.children[2] as Icon; - - if (icon.color != Colors.amber) { - _result(false, ['Add a third Icon. Give the Icon a size of 50 and a color of Colors.amber.']); - return; - } - - _result(true, ['The code displays three Icons in blue, red, and amber.']); - -} -{$ end test.dart $} -``` - -## Image widget - -The `Image` widget displays an image. -You either can reference images using a URL, -or you can include images inside your app package. -Since DartPad can't package an image, -the following example uses an image from the network. - -#### Example: Displaying an image -{:.no_toc} -{{site.alert.secondary}} - The following example displays an image that's - stored remotely on [GitHub][]. - The `Image.network` method takes a string - parameter that contains an image's URL. - - In this example, `Image.network` contains a non-working URL. - - **1.** Click the **Run** button. - - **2.** Change the non-working URL to the actual URL: - - `https://raw.githubusercontent.com/flutter/website/main/examples/layout/sizing/images/pic1.jpg` - - **3.** Then change `pic1.jpg` to `pic2.jpg` or `pic3.jpg`, - and run again. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.network('[Place an image link here!]'), - ], - ); - } -} -{$ end main.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Directionality( - textDirection: TextDirection.ltr, - child: Container( - color: const Color(0xffeeeeee), - child: Center( - child: Container( - color: const Color(0xffcccccc), - child: MyWidget(), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - final rows = controller.widgetList(find.byType(Row)); - - if (rows.isEmpty) { - _result(false, ['Couldn\'t find a Row!']); - return; - } - - if (rows.length > 1) { - _result(false, ['Found ${rows.length} Rows, rather than just one.']); - return; - } - - final row = rows.first as Row; - - if (row.mainAxisSize != MainAxisSize.max) { - _result(false, ['It\'s best to leave the mainAxisSize set to MainAxisSize.max, so there\'s space for the alignments to take effect.']); - return; - } - -} -{$ end test.dart $} -``` - -## Putting it all together - -You're almost at the end of this codelab. -If you'd like to test your knowledge of the -techniques that you've learned, why not apply -those skills into building a Flutter UI that -displays a business card! - - ![Completed business card]({{site.url}}/assets/images/docs/codelab/layout/businesscarddisplay1.png){:width="400px"}{:.text-center} - -You'll break down Flutter's layout into parts, -which is how you'd create a Flutter UI in the real world. - -In [Part 1](#part-1), -you'll implement a `Column` that contains the name and title. -Then you'll wrap the `Column` in a `Row` that contains the icon, -which is positioned to the left of the name and title. - - ![Completed business card]({{site.url}}/assets/images/docs/codelab/layout/businesscarddisplay2.png){:width="400px"}{:.text-center} - -In [Part 2](#part-2), you'll wrap the `Row` in a `Column`, -so the code contains a `Column` within a `Row` within a `Column`. -Then you'll tweak the outermost `Column`'s layout, -so it looks nice. -Finally, you'll add the contact information -to the outermost `Column`'s list of children, -so it's displayed below the name, title, and icon. - - ![Completed business card]({{site.url}}/assets/images/docs/codelab/layout/businesscarddisplay3.png){:width="400px"}{:.text-center} - -In [Part 3](#part-3), you'll finish building -the business card display by adding four more icons, -which are positioned below the contact information. - - ![Completed business card]({{site.url}}/assets/images/docs/codelab/layout/businesscarddisplay4.png){:width="400px"}{:.text-center} - -### Part 1 -{:.no_toc} - -#### Exercise: Create the name and title -{:.no_toc} -{{site.alert.secondary}} - - Implement a `Column` that contains two text widgets: - -
    -
  • - The first `Text` widget has the name `Flutter McFlutter` and - the `style` property set to `Theme.of(context).textTheme.headlineSmall`. -
  • -
  • - The second `Text` widget contains the title `Experienced App Developer`. -
  • -
- - For the `Column`, - set `mainAxisSize` to `MainAxisSize.min` - and `crossAxisAlignment` to `CrossAxisAlignment.start`. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - TODO('Begin implementing the Column here.'); - } -} -{$ end main.dart $} -{$ begin solution.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ); - } -} -{$ end solution.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color(0xffeeeeee), - textTheme: const TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - ), - ), - ), - home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Container( - decoration: BoxDecoration( - color: const Color(0xffffffff), - border: Border.all(), - boxShadow: const [ - BoxShadow( - blurRadius: 10, - color: Color(0x80000000), - ), - ], - ), - padding: const EdgeInsets.all(8), - child: MyWidget(), - ), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - // Check MyWidget starts with one Column - - final myWidgetElement = controller.element(find.byType(MyWidget)); - - final myWidgetChildElements = []; - myWidgetElement.visitChildElements((e) => myWidgetChildElements.add(e)); - - if (myWidgetChildElements.length != 1 || - myWidgetChildElements[0].widget is! Column) { - _result(false, ['The root widget in MyWidget\'s build method should be a Column.']); - return; - } - - // Check Column has correct properties - - final innerColumnElement = myWidgetChildElements[0]; - final innerColumnWidget = innerColumnElement.widget as Column; - - if (innerColumnWidget.crossAxisAlignment != CrossAxisAlignment.start) { - _result(false, ['The Column that contains the name and title should use CrossAxisAlignment.start as its CrossAxisAlignment value.']); - return; - } - - if (innerColumnWidget.mainAxisSize != MainAxisSize.min) { - _result(false, ['The Column that contains the name and title should use MainAxisSize.min as its MainAxisSize value.']); - return; - } - - // Check inner Column has two Text children - - if (innerColumnWidget.children.any((w) => w is! Text)) { - _result(false, ['The Column that contains the name and title should have two children, both Text widgets.']); - return; - } - - // Check first Text has headline style - - final nameText = innerColumnWidget.children[0] as Text; - - if (nameText.style?.fontSize != 24) { - _result(false, ['The Text widget for the name should use the "headlineSmall" textStyle.']); - return; - } - - _result(true); -} -{$ end test.dart $} -``` - -#### Exercise: Wrap the Column in a Row -{:.no_toc} -{{site.alert.secondary}} - - Wrap the `Column` you implemented in a - `Row` that contains the following widgets: - -
    -
  • - An `Icon` widget set to `Icons.account_circle` - and with a size of 50 pixels. - -
  • -
  • - A `Padding` widget that creates a space of 8 - pixels around the `Icon` widget. - - To do this, you can specify `const EdgeInsets.all(8)` - for the `padding` property. - - The `Row` should look like this: -
  • -
- - ```dart - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: Icon(Icons.account_circle, size: 50), - ), - Column( ... ), // <--- The Column you first implemented - ], - ); - ``` -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ); - } -} -{$ end main.dart $} -{$ begin solution.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - const Padding( - padding: EdgeInsets.all(8), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ); - } -} -{$ end solution.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color(0xffeeeeee), - textTheme: const TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - ), - ), - ), - home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Container( - decoration: BoxDecoration( - color: const Color(0xffffffff), - border: Border.all(), - boxShadow: const [ - BoxShadow( - blurRadius: 10, - color: Color(0x80000000), - ), - ], - ), - padding: const EdgeInsets.all(8.0), - child: MyWidget(), - ), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - // Check MyWidget starts with one Column - - final myWidgetElement = controller.element(find.byType(MyWidget)); - - final myWidgetChildElements = []; - myWidgetElement.visitChildElements((e) => myWidgetChildElements.add(e)); - - if (myWidgetChildElements.length != 1 || - myWidgetChildElements[0].widget is! Row) { - _result(false, ['The root widget in MyWidget\'s build method should be a Column.']); - return; - } - - // Check first Row has two children: Padding and Column - - final firstRowElement = myWidgetChildElements[0]; - - final firstRowChildElements = []; - firstRowElement.visitChildElements((e) => firstRowChildElements.add(e)); - - if (firstRowChildElements.length != 2 || - firstRowChildElements[0].widget is! Padding || - firstRowChildElements[1].widget is! Column) { - _result(false, ['The first Row should have two children: first a Padding, and then a Column.']); - return; - } - - // Check Padding has correct padding - - final paddingElement = firstRowChildElements[0]; - - if ((paddingElement.widget as Padding).padding != const EdgeInsets.all(8)) { - _result(false, ['The Padding widget in the first Row should have a padding of 8.']); - return; - } - - // Check Padding has an Icon as its child - - final paddingChildren = []; - paddingElement.visitChildElements((e) => paddingChildren.add(e)); - - if (paddingChildren.length != 1 || paddingChildren[0].widget is! Icon) { - _result(false, ['The Padding widget in the first Row should have an Icon as its child.']); - return; - } - - // Check icon has a size of 50 - - if ((paddingChildren[0].widget as Icon).size != 50) { - _result(false, ['The Icon in the top-left corner should have a size of 50.']); - return; - } - - // Check inner Column has correct properties - - final innerColumnElement = firstRowChildElements[1]; - final innerColumnWidget = innerColumnElement.widget as Column; - - if (innerColumnWidget.crossAxisAlignment != CrossAxisAlignment.start) { - _result(false, ['The Column for the name and title should use CrossAxisAlignment.start as its crosAxisAlignment.']); - return; - } - - if (innerColumnWidget.mainAxisSize != MainAxisSize.min) { - _result(false, ['The Column for the name and title should use MainAxisSize.min as its mainAxisSize.']); - return; - } - - // Check inner Column has two Text children - - if (innerColumnWidget.children.any((w) => w is! Text)) { - _result(false, ['The Column for the name and title should have two children, both Text widgets.']); - return; - } - - // Check first Text has headline style - - final nameText = innerColumnWidget.children[0] as Text; - - if (nameText.style?.fontSize != 24) { - _result(false, ['The Text widget for the name should use the "headlineSmall" textStyle.']); - return; - } - - _result(true); -} -{$ end test.dart $} -``` - -### Part 2 -{:.no_toc} - -#### Exercise: Tweak the layout -{:.no_toc} -{{site.alert.secondary}} - - Wrap the `Row` in a `Column` that has a `mainAxisSize` - property set to `MainAxisSize.min` and a - `crossAxisAlignment` property set to `CrossAxisAlignment.stretch`. - The `Column` contains the following widgets: - - * A `SizedBox` widget with a height of 8. - - * An empty `Row` where you'll add the contact information in - a later step. - - * A second `SizedBox` widget with a height of 16. - - * A second empty `Row` where you'll add - four icons (Part 3). - - The `Column`'s list of widgets should be formatted as follows, - so the contact information and icons are displayed below the - name and title: - - ```dart - - ], - ), // <--- Closing parenthesis for the Row - SizedBox(), - Row(), // First empty Row - SizedBox(), - Row(), // Second empty Row - ], - ); // <--- Closing parenthesis for the Column that wraps the Row - - ``` - -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ); - } -} -{$ end main.dart $} -{$ begin solution.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ), - const SizedBox(height: 8), - Row(), - const SizedBox(height: 16), - Row(), - ], - ); - } -} -{$ end solution.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color(0xffeeeeee), - textTheme: const TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - ), - ), - ), - home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: Container( - decoration: BoxDecoration( - color: const Color(0xffffffff), - border: Border.all(), - boxShadow: const [ - BoxShadow( - blurRadius: 10, - color: Color(0x80000000), - ), - ], - ), - padding: const EdgeInsets.all(8.0), - child: MyWidget(), - ), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - // Check MyWidget starts with one Column - - final myWidgetElement = controller.element(find.byType(MyWidget)); - - final myWidgetChildElements = []; - myWidgetElement.visitChildElements((e) => myWidgetChildElements.add(e)); - - if (myWidgetChildElements.length != 1 || - myWidgetChildElements[0].widget is! Column) { - _result(false, ['The root widget in MyWidget\'s build method should be a Column.']); - return; - } - - // Check outermost Column has 5 correct children. - - final outerColumnElement = myWidgetChildElements[0]; - final outerColumnChildWidgets = - (outerColumnElement.widget as Column).children; - final outerColumnChildElements = []; - outerColumnElement.visitChildElements((e) => outerColumnChildElements.add(e)); - - if (outerColumnChildWidgets.length != 5 || - outerColumnChildWidgets[0] is! Row || - outerColumnChildWidgets[1] is! SizedBox || - outerColumnChildWidgets[2] is! Row || - outerColumnChildWidgets[3] is! SizedBox || - outerColumnChildWidgets[4] is! Row) { - _result(false, ['The children of the outermost Column should be [Row, SizedBox, Row, SizedBox, Row] in that order.']); - return; - } - - // Check outermost Column's properties - - if ((outerColumnElement.widget as Column).mainAxisSize != MainAxisSize.min) { - _result(false, ['The outermost Column should use MainAxisSize.min for its mainAxisSize.']); - return; - } - - if ((outerColumnElement.widget as Column).crossAxisAlignment != - CrossAxisAlignment.stretch) { - _result(false, ['The outermost Column should use CrossAxisAlignment.stretch for its crossAxisAlignment.']); - return; - } - - // Check first Row has two children: Padding and Column - - final firstRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[0]); - - final firstRowChildElements = []; - firstRowElement.visitChildElements((e) => firstRowChildElements.add(e)); - - if (firstRowChildElements.length != 2 || - firstRowChildElements[0].widget is! Padding || - firstRowChildElements[1].widget is! Column) { - _result(false, ['The first Row should have two children: first a Padding, and then a Column.']); - return; - } - - // Check Padding has correct padding - - final paddingElement = firstRowChildElements[0]; - - if ((paddingElement.widget as Padding).padding != const EdgeInsets.all(8)) { - _result(false, ['The Padding widget in the first Row should have a padding of 8.']); - return; - } - - // Check Padding has an Icon as its child - - final paddingChildren = []; - paddingElement.visitChildElements((e) => paddingChildren.add(e)); - - if (paddingChildren.length != 1 || paddingChildren[0].widget is! Icon) { - _result(false, ['The Padding widget in the first Row should have an Icon as its child.']); - return; - } - - // Check icon has a size of 50 - - if ((paddingChildren[0].widget as Icon).size != 50) { - _result(false, ['The Icon in the top-left corner should have a size of 50.']); - return; - } - - // Check inner Column has correct properties - - final innerColumnElement = firstRowChildElements[1]; - final innerColumnWidget = innerColumnElement.widget as Column; - - if (innerColumnWidget.crossAxisAlignment != CrossAxisAlignment.start) { - _result(false, ['The Column for the name and title should use CrossAxisAlignment.start as its crosAxisAlignment.']); - return; - } - - if (innerColumnWidget.mainAxisSize != MainAxisSize.min) { - _result(false, ['The Column for the name and title should use MainAxisSize.min as its mainAxisSize.']); - return; - } - - // Check inner Column has two Text children - - if (innerColumnWidget.children.any((w) => w is! Text)) { - _result(false, ['The Column for the name and title should have two children, both Text widgets.']); - return; - } - - // Check first Text has headline style - - final nameText = innerColumnWidget.children[0] as Text; - - if (nameText.style?.fontSize != 24) { - _result(false, ['The Text widget for the name should use the "headlineSmall" textStyle.']); - return; - } - - // Check first SizedBox has correct properties - - final firstSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[1]); - - if ((firstSizedBoxElement.widget as SizedBox).height != 8) { - _result(false, ['The SizedBox before the first empty Row should have a height of 8.']); - return; - } - - // Check second SizedBox has correct properties - - final secondSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[3]); - - if ((secondSizedBoxElement.widget as SizedBox).height != 16) { - _result(false, ['The SizedBox between the first and second empty Rows should have a height of 16.']); - return; - } - - _result(true); -} -{$ end test.dart $} -``` - -#### Exercise: Enter contact information -{:.no_toc} -{{site.alert.secondary}} - Enter two `Text` widgets inside the first empty `Row` : - -
    -
  • - The first `Text` widget contains the address `123 Main Street`. -
  • -
  • - The second `Text` widget contains the phone number `(415) 555-0198`. -
  • -
- - For the first empty `Row`, - set the `mainAxisAlignment` property to - `MainAxisAlignment.spaceBetween`. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ), - const SizedBox(height: 8), - Row( - children: const [], - ), - const SizedBox(height: 16), - Row( - children: const [], - ), - ], - ); - } -} -{$ end main.dart $} -{$ begin solution.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - Text( - '123 Main Street', - ), - Text( - '(415) 555-0198', - ), - ], - ), - const SizedBox(height: 16), - Row( - children: const [], - ), - ], - ); - } -} -{$ end solution.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color(0xffeeeeee), - textTheme: const TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - ), - ), - ), - home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: Container( - decoration: BoxDecoration( - color: const Color(0xffffffff), - border: Border.all(), - boxShadow: const [ - BoxShadow( - blurRadius: 10, - color: Color(0x80000000), - ), - ], - ), - padding: const EdgeInsets.all(8.0), - child: MyWidget(), - ), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - // Check MyWidget starts with one Column - - final myWidgetElement = controller.element(find.byType(MyWidget)); - - final myWidgetChildElements = []; - myWidgetElement.visitChildElements((e) => myWidgetChildElements.add(e)); - - if (myWidgetChildElements.length != 1 || - myWidgetChildElements[0].widget is! Column) { - _result(false, ['The root widget in MyWidget\'s build method should be a Column.']); - return; - } - - // Check outermost Column has 5 correct children. - - final outerColumnElement = myWidgetChildElements[0]; - final outerColumnChildWidgets = - (outerColumnElement.widget as Column).children; - final outerColumnChildElements = []; - outerColumnElement.visitChildElements((e) => outerColumnChildElements.add(e)); - - if (outerColumnChildWidgets.length != 5 || - outerColumnChildWidgets[0] is! Row || - outerColumnChildWidgets[1] is! SizedBox || - outerColumnChildWidgets[2] is! Row || - outerColumnChildWidgets[3] is! SizedBox || - outerColumnChildWidgets[4] is! Row) { - _result(false, ['The children of the outermost Column should be [Row, SizedBox, Row, SizedBox, Row] in that order.']); - return; - } - - // Check outermost Column's properties - - if ((outerColumnElement.widget as Column).mainAxisSize != MainAxisSize.min) { - _result(false, ['The outermost Column should use MainAxisSize.min for its mainAxisSize.']); - return; - } - - if ((outerColumnElement.widget as Column).crossAxisAlignment != - CrossAxisAlignment.stretch) { - _result(false, ['The outermost Column should use CrossAxisAlignment.stretch for its crossAxisAlignment.']); - return; - } - - // Check first Row has two children: Padding and Column - - final firstRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[0]); - - final firstRowChildElements = []; - firstRowElement.visitChildElements((e) => firstRowChildElements.add(e)); - - if (firstRowChildElements.length != 2 || - firstRowChildElements[0].widget is! Padding || - firstRowChildElements[1].widget is! Column) { - _result(false, ['The first Row should have two children: first a Padding, and then a Column.']); - return; - } - - // Check Padding has correct padding - - final paddingElement = firstRowChildElements[0]; - - if ((paddingElement.widget as Padding).padding != const EdgeInsets.all(8)) { - _result(false, ['The Padding widget in the first Row should have a padding of 8.']); - return; - } - - // Check Padding has an Icon as its child - - final paddingChildren = []; - paddingElement.visitChildElements((e) => paddingChildren.add(e)); - - if (paddingChildren.length != 1 || paddingChildren[0].widget is! Icon) { - _result(false, ['The Padding widget in the first Row should have an Icon as its child.']); - return; - } - - // Check icon has a size of 50 - - if ((paddingChildren[0].widget as Icon).size != 50) { - _result(false, ['The Icon in the top-left corner should have a size of 50.']); - return; - } - - // Check inner Column has correct properties - - final innerColumnElement = firstRowChildElements[1]; - final innerColumnWidget = innerColumnElement.widget as Column; - - if (innerColumnWidget.crossAxisAlignment != CrossAxisAlignment.start) { - _result(false, ['The Column for the name and title should use CrossAxisAlignment.start as its crosAxisAlignment.']); - return; - } - - if (innerColumnWidget.mainAxisSize != MainAxisSize.min) { - _result(false, ['The Column for the name and title should use MainAxisSize.min as its mainAxisSize.']); - return; - } - - // Check inner Column has two Text children - - if (innerColumnWidget.children.any((w) => w is! Text)) { - _result(false, ['The Column for the name and title should have two children, both Text widgets.']); - return; - } - - // Check first Text has headline style - - final nameText = innerColumnWidget.children[0] as Text; - - if (nameText.style?.fontSize != 24) { - _result(false, ['The Text widget for the name should use the "headlineSmall" textStyle.']); - return; - } - - // Check first SizedBox has correct properties - - final firstSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[1]); - - if ((firstSizedBoxElement.widget as SizedBox).height != 8) { - _result(false, ['The SizedBox before the first empty Row widget should have a height of 8.']); - return; - } - - // Check second Row has two Text children - - final secondRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[2]); - - final secondRowChildElements = []; - secondRowElement.visitChildElements((e) => secondRowChildElements.add(e)); - - if (secondRowChildElements.length != 2 || - secondRowChildElements.any((e) => e.widget is! Text)) { - _result(false, ['The first empty Row widget should have two children, both Text widgets.']); - return; - } - - // Check second Row has correct properties - - if ((secondRowElement.widget as Row).mainAxisAlignment != - MainAxisAlignment.spaceBetween) { - _result(false, ['The first empty Row widget should use MainAxisAlignment.spaceBetween as its MainAxisAlignment value.']); - return; - } - - // Check second SizedBox has correct properties - - final secondSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[3]); - - if ((secondSizedBoxElement.widget as SizedBox).height != 16) { - _result(false, ['The SizedBox between the first and second empty Row widgets should have a height of 16.']); - return; - } - - _result(true); -} -{$ end test.dart $} -``` - -### Part 3 -{:.no_toc} -#### Exercise: Add four icons -{:.no_toc} -{{site.alert.secondary}} - Enter the following `Icon` widgets inside the second empty `Row`: - - * `Icons.accessibility` - * `Icons.timer` - * `Icons.phone_android` - * `Icons.phone_iphone` - - For the second empty `Row`, - set the `mainAxisAlignment` property to - `MainAxisAlignment.spaceAround`. -{{site.alert.end}} - -```run-dartpad:theme-dark:mode-flutter:width-100%:height-400px:split-60 -{$ begin main.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - Text('123 Main Street'), - Text('415-555-0198'), - ], - ), - const SizedBox(height: 16), - Row( - children: const [], - ), - ], - ); - } -} -{$ end main.dart $} -{$ begin solution.dart $} -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MyWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.account_circle, size: 50), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Flutter McFlutter', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text('Experienced App Developer'), - ], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - Text( - '123 Main Street', - ), - Text( - '(415) 555-0198', - ), - ], - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: const [ - Icon(Icons.accessibility), - Icon(Icons.timer), - Icon(Icons.phone_android), - Icon(Icons.phone_iphone), - ], - ), - ], - ); - } -} -{$ end solution.dart $} -{$ begin test.dart $} -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - scaffoldBackgroundColor: const Color(0xffeeeeee), - textTheme: const TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - ), - ), - ), - home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: Container( - decoration: BoxDecoration( - color: const Color(0xffffffff), - border: Border.all(), - boxShadow: const [ - BoxShadow( - blurRadius: 10, - color: Color(0x80000000), - ), - ], - ), - padding: const EdgeInsets.all(8.0), - child: MyWidget(), - ), - ), - ), - ), - ); - } -} - -Future main() async { - final completer = Completer(); - - runApp(MyApp()); - - WidgetsFlutterBinding.ensureInitialized() - .addPostFrameCallback((timestamp) async { - completer.complete(); - }); - - await completer.future; - - final controller = LiveWidgetController(WidgetsBinding.instance); - - // Check MyWidget starts with one Column - - final myWidgetElement = controller.element(find.byType(MyWidget)); - - final myWidgetChildElements = []; - myWidgetElement.visitChildElements((e) => myWidgetChildElements.add(e)); - - if (myWidgetChildElements.length != 1 || - myWidgetChildElements[0].widget is! Column) { - _result(false, ['The root widget in MyWidget\'s build method should be a Column.']); - return; - } - - // Check outermost Column has 5 correct children. - - final outerColumnElement = myWidgetChildElements[0]; - final outerColumnChildWidgets = - (outerColumnElement.widget as Column).children; - final outerColumnChildElements = []; - outerColumnElement.visitChildElements((e) => outerColumnChildElements.add(e)); - - if (outerColumnChildWidgets.length != 5 || - outerColumnChildWidgets[0] is! Row || - outerColumnChildWidgets[1] is! SizedBox || - outerColumnChildWidgets[2] is! Row || - outerColumnChildWidgets[3] is! SizedBox || - outerColumnChildWidgets[4] is! Row) { - _result(false, ['The children of the outermost Column should be [Row, SizedBox, Row, SizedBox, Row] in that order.']); - return; - } - - // Check outermost Column's properties - - if ((outerColumnElement.widget as Column).mainAxisSize != MainAxisSize.min) { - _result(false, ['The outermost Column should use MainAxisSize.min for its mainAxisSize.']); - return; - } - - if ((outerColumnElement.widget as Column).crossAxisAlignment != - CrossAxisAlignment.stretch) { - _result(false, ['The outermost Column should use CrossAxisAlignment.stretch for its crossAxisAlignment.']); - return; - } - - // Check first Row has two children: Padding and Column - - final firstRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[0]); - - final firstRowChildElements = []; - firstRowElement.visitChildElements((e) => firstRowChildElements.add(e)); - - if (firstRowChildElements.length != 2 || - firstRowChildElements[0].widget is! Padding || - firstRowChildElements[1].widget is! Column) { - _result(false, ['The first Row should have two children: first a Padding, and then a Column.']); - return; - } - - // Check Padding has correct padding - - final paddingElement = firstRowChildElements[0]; - - if ((paddingElement.widget as Padding).padding != const EdgeInsets.all(8)) { - _result(false, ['The Padding widget in the first Row should have a padding of 8.']); - return; - } - - // Check Padding has an Icon as its child - - final paddingChildren = []; - paddingElement.visitChildElements((e) => paddingChildren.add(e)); - - if (paddingChildren.length != 1 || paddingChildren[0].widget is! Icon) { - _result(false, ['The Padding widget in the first Row should have an Icon as its child.']); - return; - } - - // Check icon has a size of 50 - - if ((paddingChildren[0].widget as Icon).size != 50) { - _result(false, ['The Icon in the top-left corner should have a size of 50.']); - return; - } - - // Check inner Column has correct properties - - final innerColumnElement = firstRowChildElements[1]; - final innerColumnWidget = innerColumnElement.widget as Column; - - if (innerColumnWidget.crossAxisAlignment != CrossAxisAlignment.start) { - _result(false, ['The Column for the name and title should use CrossAxisAlignment.start as its crosAxisAlignment.']); - return; - } - - // Check inner Column has two Text children - - if (innerColumnWidget.children.any((w) => w is! Text)) { - _result(false, ['The Column for the name and title should have two children, both Text widgets.']); - return; - } - - if (innerColumnWidget.mainAxisSize != MainAxisSize.min) { - _result(false, ['The Column for the name and title should use MainAxisSize.min as its mainAxisSize.']); - return; - } - - // Check first Text has headline style - - final nameText = innerColumnWidget.children[0] as Text; - - if (nameText.style?.fontSize != 24) { - _result(false, ['The Text widget for the name should use the "headlineSmall" textStyle.']); - return; - } - - // Check first SizedBox has correct properties - - final firstSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[1]); - - if ((firstSizedBoxElement.widget as SizedBox).height != 8) { - _result(false, ['The SizedBox before the first empty Row widget should have a height of 8.']); - return; - } - - // Check second Row has two Text children - - final secondRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[2]); - - final secondRowChildElements = []; - secondRowElement.visitChildElements((e) => secondRowChildElements.add(e)); - - if (secondRowChildElements.length != 2 || - secondRowChildElements.any((e) => e.widget is! Text)) { - _result(false, ['The first Row widget should have two children, both Text widgets.']); - return; - } - - // Check second Row has correct properties - - if ((secondRowElement.widget as Row).mainAxisAlignment != - MainAxisAlignment.spaceBetween) { - _result(false, ['The first Row widget should use MainAxisAlignment.spaceBetween as its mainAxisAlignment.']); - return; - } - - // Check second SizedBox has correct properties - - final secondSizedBoxElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[3]); - - if ((secondSizedBoxElement.widget as SizedBox).height != 16) { - _result(false, ['The SizedBox between the first and second Row widgets should have a height of 16.']); - return; - } - - // Check second empty Row has four Icon children - - final thirdRowElement = outerColumnChildElements - .firstWhere((e) => e.widget == outerColumnChildWidgets[4]); - - final thirdRowChildElements = []; - thirdRowElement.visitChildElements((e) => thirdRowChildElements.add(e)); - - if (thirdRowChildElements.length != 4 || - thirdRowChildElements.any((e) => e.widget is! Icon)) { - _result(false, ['The second empty Row widget should have four children, all Icon widgets.']); - return; - } - - // Check second empty Row has correct properties - - if ((thirdRowElement.widget as Row).mainAxisAlignment != - MainAxisAlignment.spaceAround) { - _result(false, ['The second empty Row widget should use MainAxisAlignment.spaceAround as its MainAxisAligment value.']); - return; - } - - _result(true); -} -{$ end test.dart $} -``` - -## What's next? - -Congratulations, you've finished this codelab! -If you'd like to know more about Flutter, -here are a few suggestions for resources worth exploring: - -* Learn more about layouts in Flutter by - visiting the [Building layouts][] page. -* Check out the [sample apps][]. -* Visit [Flutter's YouTube channel][], - where you can watch a variety videos from - videos that focus on individual widgets - to videos of developers building apps. - -You can download Flutter from the [install][] page. - - - - -[Building layouts]: {{site.url}}/ui/layout -[Cupertino]: {{site.api}}/flutter/cupertino/CupertinoApp-class.html -[DartPad issue]: {{site.github}}/dart-lang/dart-pad/issues/new -[Flutter's YouTube channel]: {{site.social.youtube}} -[GitHub]: {{site.repo.this}}/tree/{{site.branch}}/examples/layout/sizing/images -[install]: {{site.url}}/get-started/install -[Material]: {{site.api}}/flutter/material/MaterialApp-class.html -[Material Color palette]: {{site.api}}/flutter/material/Colors-class.html -[Material Icon library]: {{site.api}}/flutter/material/Icons-class.html -[sample apps]: {{site.github}}/flutter/samples diff --git a/src/release/whats-new.md b/src/release/whats-new.md index 78b22712e3..a46bcac2ee 100644 --- a/src/release/whats-new.md +++ b/src/release/whats-new.md @@ -1253,7 +1253,7 @@ New and updated docs on the site include: * The Flutter layout codelab has been rewritten and uses the updated DartPad, the browser-based tool for running Dart code. DartPad now supports Flutter! - [Try it out] and let us know what you think. + [Try it out]({{site.dartpad}}) and let us know what you think. * A new page on [using the dart:ffi library][] to bind your app to native code (a feature currently under development). @@ -1301,7 +1301,6 @@ Happy Fluttering! [Showcase]: {{site.main-url}}/showcase [`ToggleButtons`]: {{site.api}}/flutter/material/ToggleButtons-class.html [ToggleButtons demo]: {{site.github}}/csells/flutter_toggle_buttons -[Try it out]: {{site.url}}/codelabs/layout-basics [Upgrading from package:flutter_web to the Flutter SDK]: {{site.repo.flutter}}/wiki/Upgrading-from-package:flutter_web-to-the-Flutter-SDK [using the dart:ffi library]: {{site.url}}/platform-integration/android/c-interop [web FAQ]: {{site.url}}/platform-integration/web/faq @@ -1364,10 +1363,9 @@ component and how to customize it, see For more information on updates, see the [1.5.4 release notes][] or [download the release][]. -We are updating DartPad to work with Flutter. Try our new -[Basic Flutter layout codelab][] and tell us what you think! +We are updating DartPad to work with Flutter. Try the new +Basic Flutter layout codelab and tell us what you think! -[Basic Flutter layout codelab]: {{site.url}}/codelabs/layout-basics [download the release]: {{site.url}}/release/archive [Flutter 1.5]: {{site.google-blog}}/2019/05/Flutter-io19.html [1.5.4 release notes]: {{site.url}}/release/release-notes/release-notes-1.5.4 diff --git a/src/ui/index.md b/src/ui/index.md index dce8cc6020..b3a091e7f4 100644 --- a/src/ui/index.md +++ b/src/ui/index.md @@ -22,8 +22,7 @@ tree to transition from one state to the next. {{site.alert.note}} If you would like to become better acquainted with Flutter by diving - into some code, check out [basic layout codelab][], - [building layouts][], + into some code, check out [building layouts][], and [adding interactivity to your Flutter app][]. {{site.alert.end}} @@ -902,7 +901,6 @@ For more information, check out the [`GlobalKey`][] API. [`actions`]: {{api}}/material/AppBar-class.html#actions [adding interactivity to your Flutter app]: {{site.url}}/ui/interactivity [`AppBar`]: {{api}}/material/AppBar-class.html -[basic layout codelab]: {{site.url}}/codelabs/layout-basics [`BoxDecoration`]: {{api}}/painting/BoxDecoration-class.html [`build()`]: {{api}}/widgets/StatelessWidget/build.html [building layouts]: {{site.url}}/ui/layout