preface
The purpose of this article is to help readers understand the layout characteristics of different layout widgets and share some problems encountered in their practical use. The book “Flutter Practice” has been explained in detail. This article mainly concentrates the contents and supplements the problems encountered in practice.
What is a layout class Widget
Layout class Widget is refers to the direct or indirect inheritance (containing) MultiChildRenderObjectWidget widgets, they generally have a children attribute used to receive child widgets. The Element tree is the final draw tree in Flutter. The Element tree is created from the widget tree (via widget.createElement ()). The widget is the Element’s configuration data. The final layout and UI rendering is done with RenderObject, but I won’t go into the details here because I don’t understand it. You can also check out Layout and Paint for the Flutter view in this column.
Flutter contains the following layout types of widgets:
- Linear layout of Row and Column
- Flexible layout Flex
- Flow layout Wrap, Flow
- Stacking, Stack, jam
This article [Demo address] (https://github.com/xqqlv/flutte_layout_demo)
Linear layout of Row and Column
Linear layout refers to the horizontal or vertical layout of the sub-widgets. The horizontal layout of the sub-widgets is implemented by Row and the vertical layout of the sub-widgets is implemented by Column. They both inherit from Flex, so they have many similar properties.
In the Flex layout on the front end, there are two axes by default: a horizontal main axis and a vertical cross axis. The starting position of the spindle (where it intersects with the border) is called main start and the ending position is called main end; The starting position of the intersecting axis is called cross start and the ending position is called cross end. This is similar to the MainAxisAlignment and CrossAxisAlignment in the Flutter, representing spindle alignment and vertical alignment respectively.
Source code attribute interpretation
Row({
.....
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
Column({
.....
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
Copy the code
- TextDirection: indicates the horizontal layout order of the child widgets (from left to right or from right to left). The default value is the textDirection of the current Locale (for example, the textDirection is from left to right in Chinese and English, but from right to left in Arabic).
- Main axis: Row is the horizontal direction, Column is the vertical direction
- MainAxisAlignment specifies the mainAxisAlignment, which applies to the child
- Center: Place children in the center of the spindle
- Start: Place children at the beginning of the spindle
- End: Places children at the end of the spindle
- SpaceAround: Divide the blank areas along the main axis so that the blank areas between children are equal, but the edge spacing between the beginning and the end of the child is 1/2 of the blank area
- SpaceBetween: Divide the blank areas along the main axis evenly, so that the blank areas between children are equal, and there is no gap between the beginning and the end of the child
- Spaceinstituted: evenly split the blank areas along the main axis so that the blank areas between children are equal, including beginning and end children
- MainAxisSize Max means as many controls as possible, and min causes the controls to cluster together
- CrossAxisAlignment crossAxisAlignment applies to the child
- Baseline: align children baseline
- Center: Children are shown in the center of the cross axis
- End: Children are shown at the end of the cross axis
- Start: Children are shown at the starting point on the cross axis
- Stretch: Let children fill the cross axis direction
- VerticalDirection, the placement order of the child
- Verticaldirection. down, which in Row is left to right, Column stands for top to bottom
- VerticalDirection. Up, on the contrary
Row
The sample code
ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text("I am a child control of Row"),
Text("MainAxisAlignment.start")
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("I am a child control of Row"),
Text("MainAxisAlignment.center")
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text("I am a child control of Row"),
Text("MainAxisAlignment.end")
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
verticalDirection: VerticalDirection.up,
children: <Widget>[
Text(" Hello World ", style: TextStyle(fontSize: 30.0),),
Text(" I am Jack "),]],)Copy the code
Code performance
Column
The sample code
ListView(children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("I'm a child of Colum."),
Text("CrossAxisAlignment.start"),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("I'm a child of Colum."),
Text("CrossAxisAlignment.center"),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text("I'm a child of Colum."),
Text("CrossAxisAlignment.end"),,),,)Copy the code
Code performance
The actual use
Due to space constraints, I won’t go into detail about the actual problems encountered, but just the symptoms and solutions:
- If a Row is nested within a Row, or a Column is nested within a Column, then only the outermost Row or Column will take up as much space as possible. The inner Row or Column will take up the actual amount of space. If the inner Colum or Row is to fill the outer Colum or Row, You can use Expanded Widgets
- If Column is found to be out of range, use SingleChildScrollView to wrap it with the scrollDirection property to set the sliding direction
- Viewports expand in the scrolling direction to fill their container… The solution to this situation has been given by Flutter by setting the shrinkWrap property of ListView/GridView to true
- In some cases, changing the verticalDirection of a Row/Column can be useful. For example, it requires several buttons at the bottom of the page. You can also use a Stack to layout the page, but it is cumbersome and sometimes you need to know the size of the control
Elastic layout
Elastic layout is a layout that allows child widgets to allocate the space of the parent container proportioned. If you know the main axis, you can use Row or Column. In general, you can use Row or Column together wherever Flex works. Expanded Widgets are usually used in conjunction with Expanded Widgets, and Expanded cannot be created independently of Flex.
Expanded
Expanded extends from Flexible, which is a component that controls how Row, Column, Flex and other child components are laid out, scaling up the space occupied by Row, Column, and Flex child widgets.
const Expanded({
int flex = 1,
@required Widget child,
})
Copy the code
Flex is the elastic coefficient, and if it is 0 or NULL, child is inelastic, that is, the space not occupied by extension. If greater than 0, all Expanded partitions the total free space of the spindle in proportion to their Flex.
The sample code
Row(children: <Widget>[
RaisedButton(
onPressed: () {
print('Click the red button event');
},
color: Colors.red,
child: Text('Red button'),
),
Expanded(
flex: 1,
child: RaisedButton(
onPressed: () {
print('Click the yellow button event');
},
color: Colors.yellow,
child: Text('Yellow button'),
),
),
RaisedButton(
onPressed: () {
print('Click the pink button event');
},
color: Colors.green,
child: Text('Green button'),),)Copy the code
Code performance
Difference between Flexible and Expanded
- The Flexible component must be a descendant of Row, Column, Flex, etc., and the path from Flexible to the Row, Column, Flex that it encapsulates must include only StatelessWidgets or StatefulWidgets components (no other type of component, Like RenderObjectWidgets)
- Row, Column, and Flex are Expanded to fill the spindle available space, while Flexible does not force child components to fill the available space because of the difference in the value of the FIT property, which in Expanded is flexfit.tight, Flexible is flexfit.loose. Tight forces the child control to fill the remaining free space, while loose fills up at most the proportion it sets on the parent control, so loose defaults to the size of the control
Fluid layout
Liquid allows the width of page elements to be adjusted according to screen resolution, but the overall layout remains the same. Fence system (grid system), user tags, etc. There are Wrap and Flow widgets implemented in Flutter.
Wrap
When introducing Row and Colum, an overflow error will be reported if the child widget is outside the screen. The Flow layout is supported by Wrap and Flow in Flutter, and the overflow will be folded automatically.
Source code attribute interpretation
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0.this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0.this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
Copy the code
Many of the attributes above are the same as Row, and their meanings are the same. I won’t go through them all, but I’ll focus on the different attributes:
- Spacing: Spacing of child widgets in the main axis direction
- RunSpacing: Spacing along the vertical axis
- RunAlignment: Vertical alignment
The sample code
Wrap(
spacing: 10.0,
direction: Axis.horizontal,
alignment: WrapAlignment.start,
children: <Widget>[
_card('attention'),
_card('recommendations'),
_card(New Age),
_card('Little video'),
_card('Party media recommended'),
_card('China's New Singer'),
_card('history'),
_card('video'),
_card('games'),
_card('Headline number'),
_card('digital'),
],
)
Widget _card(String title) {
returnCard(child: Text(title),); }}Copy the code
Running effect
summary
- Using Wrap makes it easy to implement streaming layouts
- Wrap supports setting whether a streaming layout is displayed vertically or horizontally
- You can use the alignment attribute to control how widgets are laid out
Flow
Flow is rarely used because it is so complex that you need to implement the position conversion of the child widgets yourself, and in many scenarios the first consideration is whether the Wrap meets the requirements. Flow is mainly used in scenarios where a custom layout strategy is required or where performance is required (such as in animation). Flow has the following advantages:
- Performance is good; Flow is a very efficient control for child sizing and placement, optimized for child placement with Transformation Matrices: If the size or position of the child changes after the Flow location, call context.paintChild in the FlowDelegate paintChildren() method to redraw. Context. PaintChild uses Transformation Matrices when redrawing without actually adjusting the Widget location.
- Flexible; Since we need to implement the FlowDelegate’s paintChildren() method ourselves, we need to calculate the position of each widget ourselves, so we can customize the layout strategy. Disadvantages:
- Complex to use.
- You cannot customize the child widget size and must return a fixed size either by specifying the parent container size or by implementing TestFlowDelegate’s getSize.
The sample code
We made a custom streaming layout for six color blocks:
Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
children: <Widget>[
new Container(width: 80.0, height:80.0, color: Colors.red,),
new Container(width: 80.0, height:80.0, color: Colors.green,),
new Container(width: 80.0, height:80.0, color: Colors.blue,),
new Container(width: 80.0, height:80.0, color: Colors.yellow,),
new Container(width: 80.0, height:80.0, color: Colors.brown,),
new Container(width: 80.0, height:80.0, color: Colors.purple,),
],
)
Copy the code
Implement TestFlowDelegate:
class TestFlowDelegate extends FlowDelegate {
EdgeInsets margin = EdgeInsets.zero;
TestFlowDelegate({this.margin});
@override
void paintChildren(FlowPaintingContext context) {
var x = margin.left;
var y = margin.top;
// Calculates the position of each child widget
for (int i = 0; i < context.childCount; i++) {
var w = context.getChildSize(i).width + x + margin.right;
if (w < context.size.width) {
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x = w + margin.left;
} else {
x = margin.left;
y += context.getChildSize(i).height + margin.top + margin.bottom;
// Draw child widgets (optimized)
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x += context.getChildSize(i).width + margin.left + margin.right;
}
}
}
getSize(BoxConstraints constraints){
// Specify the size of Flow
return Size(double.infinity,200.0);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
returnoldDelegate ! =this; }}Copy the code
Running effect
summary
- Parameters are simple, but you need to define your own delegate
- Delegate is usually used to draw a child, that is, the placement of a position. Different situations require different delegate definitions
- Different delegates typically provide several methods to implement:
- GetConstraintsForChild: Sets layout constraints for each child, overriding existing methods
- GetSize: Sets the size of the control
- ShouldRelayout: Indicates whether the layout needs to be rearranged
- Wrap whenever possible. It’s easy
Cascade layout
The cascading layout is similar to the absolute positioning on the Web and the Frame layout in Android, in that child widgets can position themselves based on their position to the four corners of the parent container. Absolute positioning allows the child widgets to be stacked (in the order declared in the code). The Stack allows the child widgets to be stacked, while the Positioned child widgets can be Positioned (according to the four corners of the Stack).
Stack
Stack({
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
Copy the code
- Alignment: This parameter determines how to position the child widget without positioning (without using the tourists) or partial positioning. The so-called partial positioning, here specifically refers to no positioning on a certain axis: left, right for the horizontal axis, top, bottom for the vertical axis, as long as it contains a positioning attribute on the axis even if there is positioning on the axis.
- TextDirection: Like the textDirection function of Row and Wrap, it is used to determine the reference frame of alignment: LTR indicates that the alignment start indicates left and the alignment end indicates right. TextDirection is textdirection. RTL. In this case, start indicates the right and end indicates the left.
- Fit: This parameter is used to determine how unpositioned child widgets fit into the Stack size. Loose indicates the size to use the child widgets, and stackfit. expand indicates the size to expand into the Stack.
- Overflow: This property determines how to display child widgets that are out of the Stack display space. With overflow. clip, the excess will be clipped (hidden), but not with overflow. visible.
Here is a simple loading I implemented with Stack
class Loading extends StatelessWidget {
/// padding of ProgressIndicator to determine the loading size
final EdgeInsets padding = EdgeInsets.all(30.0);
/// The distance between the top of the text and the bottom of the chrysanthemum
final double margin = 10.0;
/ / / rounded corners
final double cornerRadius = 10.0;
final Widget _child;
final bool _isLoading;
final double opacity;
final Color color;
final String text;
Loading({
Key key,
@required child,
@required isLoading,
this.text,
this.opacity = 0.3.this.color = Colors.grey,
}) : assert(child ! =null),
assert(isLoading ! =null),
_child = child,
_isLoading = isLoading,
super(key: key);
@override
Widget build(BuildContext context) {
List<Widget> widgetList = List<Widget>();
widgetList.add(_child);
if (_isLoading) {
final loading = [
Opacity(
opacity: opacity,
child: ModalBarrier(dismissible: false, color: color),
),
_buildProgressIndicator()
];
widgetList.addAll(loading);
}
return Stack(
children: widgetList,
);
}
Widget _buildProgressIndicator() {
return Center(
child: Container(
padding: padding,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CupertinoActivityIndicator(),
Padding(
padding: EdgeInsets.only(top: margin),
child: Text(text ?? 'Loading... ')), ], ), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(cornerRadius)), color: Colors.white), ), ); }}Copy the code
According to the effect
This control uses Stack encapsulation, you pass the main view in the bottom layer, background layer in the middle, the top layer for chrysanthemum and text loading, isLoading control display
Positioned
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
Copy the code
Left, top, right, and bottom indicate the distances from the left, top, right, and bottom edges of the Stack. Width and height are used to specify the width and height of the positioning element. Note that width and height here are slightly different from those used elsewhere. This is used with left, top, right, and bottom to position widgets. You can specify only two of the three attributes left, right, and width. If you specify left and width, right will automatically calculate (left+width). If you specify three attributes at the same time, an error will be reported.
The sample code
// Make sure the Stack fills the screen by ConstrainedBox
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
alignment:Alignment.center , // Specifies the alignment of unpositioned or partially positioned widgets
children: <Widget>[
Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
color: Colors.red,
),
Positioned(
left: 18.0,
child: Text("I am Jack"),
),
Positioned(
top: 18.0,
child: Text("Your friend"() [() [();Copy the code
Operation effect:
The copyright of this article belongs to Zaihui RESEARCH and development team, welcome to reprint, reprint please reserve source. @xqqlv