本文的目的是为了让读者掌握不一样布局类Widget的布局特色,分享一些在实际使用过程遇到的一些问题,在《Flutter实战》这本书中已经讲解的很详细了,本文主要是对其内容的浓缩及实际遇到的问题的补充。前端
布局类Widget就是指直接或间接继承(包含)MultiChildRenderObjectWidget的Widget,它们通常都会有一个children属性用于接收子Widget。在Flutter中Element树才是最终的绘制树,Element树是经过widget树来建立的(经过Widget.createElement()),widget其实就是Element的配置数据。它的最终布局、UI界面渲染都是经过RenderObject对象来实现的,这里的细节我就不详细描述了,由于我也不懂。不过感兴趣的小伙伴也能够看看本专栏的Flutter视图的Layout与Paint这篇文章。git
Flutter中主要有如下几种布局类的Widget:github
本文[Demo地址](https://github.com/xqqlv/flutte_layout_demo) bash
线性布局实际上是指沿水平或垂直方向排布子Widget,Flutter中经过Row来实现水平方向的子Widegt布局,经过Column来实现垂直方向的子Widget布局。他们都继承Flex,因此它们有不少类似的属性。 markdown
在前端的Flex布局中,默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫作main start,结束位置叫作main end;交叉轴的开始位置叫作cross start,结束位置叫作cross end。与Flutter中MainAxisAlignment和CrossAxisAlignment相似,分别表明主轴对齐和纵轴对齐。less
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>[],
})
复制代码
ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text("我是Row的子控件 "),
Text("MainAxisAlignment.start")
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("我是Row的子控件 "),
Text("MainAxisAlignment.center")
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text("我是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 "),
],
],
)
复制代码
ListView(children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("我是Colum的子控件"),
Text("CrossAxisAlignment.start"),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("我是Colum的子控件"),
Text("CrossAxisAlignment.center"),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text("我是Colum的子控件"),
Text("CrossAxisAlignment.end"),
],
),
],)
复制代码
因为篇幅有限,我就不详细讲解实际遇到的问题了,只说现象和解决办法:ide
弹性布局是一种容许子widget按照必定比例来分配父容器空间的布局方式,若是你知道了它的主轴方向,那就能够用Row或Column了,通常状况下,能够用Flex的地方均可以用Row或者Column一块儿使用,一般配合Expanded Widget来使用,一样Expanded也不能脱离Flex单首创建。布局
Expanded继承自Flexible,Flexible是一个控制Row、Column、Flex等子组件如何布局的组件,它能够按比例“扩伸”Row、Column和Flex子widget所占用的空间。post
const Expanded({
int flex = 1,
@required Widget child,
})
复制代码
flex为弹性系数,若是为0或null,则child是没有弹性的,即不会被扩伸占用的空间。若是大于0,全部的Expanded按照其flex的比例来分割主轴的所有空闲空间。性能
Row(children: <Widget>[
RaisedButton(
onPressed: () {
print('点击红色按钮事件');
},
color: Colors.red,
child: Text('红色按钮'),
),
Expanded(
flex: 1,
child: RaisedButton(
onPressed: () {
print('点击黄色按钮事件');
},
color: Colors.yellow,
child: Text('黄色按钮'),
),
),
RaisedButton(
onPressed: () {
print('点击粉色按钮事件');
},
color: Colors.green,
child: Text('绿色按钮'),
),
])
复制代码
流式布局(Liquid)的特色(也叫"Fluid") 是页面元素的宽度按照屏幕分辨率进行适配调整,但总体布局不变。栅栏系统(网格系统),用户标签等。在Flutter中主要有Wrap和Flow两种Widget实现。
在介绍Row和Colum时,若是子widget超出屏幕范围,则会报溢出错误,在Flutter中经过Wrap和Flow来支持流式布局,溢出部分则会自动折行。
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>[],
})
复制代码
上述有不少属性和Row的相同,其意义其实也是相同的,这里我就不一一介绍了,主要介绍下不一样的属性:
Wrap(
spacing: 10.0,
direction: Axis.horizontal,
alignment: WrapAlignment.start,
children: <Widget>[
_card('关注'),
_card('推荐'),
_card('新时代'),
_card('小视频'),
_card('党媒推荐'),
_card('中国新唱将'),
_card('历史'),
_card('视频'),
_card('游戏'),
_card('头条号'),
_card('数码'),
],
)
Widget _card(String title) {
return Card(child: Text(title),);
}
}
复制代码
咱们通常不多会使用Flow,由于其过于复杂,须要本身实现子widget的位置转换,在不少场景下首先要考虑的是Wrap是否知足需求。Flow主要用于一些须要自定义布局策略或性能要求较高(如动画中)的场景。Flow有以下优势:
咱们对六个色块进行自定义流式布局:
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,),
],
)
复制代码
实现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;
//计算每个子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;
//绘制子widget(有优化)
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x += context.getChildSize(i).width + margin.left + margin.right;
}
}
}
getSize(BoxConstraints constraints){
//指定Flow的大小
return Size(double.infinity,200.0);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate != this;
}
}
复制代码
层叠布局和Web中的绝对定位、Android中的Frame布局是类似的,子widget能够根据到父容器四个角的位置来肯定自己的位置。绝对定位容许子widget堆叠(按照代码中声明的顺序)。Flutter中使用Stack和Positioned来实现绝对定位,Stack容许子widget堆叠,而Positioned能够给子widget定位(根据Stack的四个角)。
Stack({
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
复制代码
class Loading extends StatelessWidget {
/// ProgressIndicator的padding,决定loading的大小
final EdgeInsets padding = EdgeInsets.all(30.0);
/// 文字顶部距菊花的底部的距离
final double margin = 10.0;
/// 圆角
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 ?? '加载中...')),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(cornerRadius)),
color: Colors.white),
),
);
}
}
复制代码
本控件使用Stack封装,你传入的主视图在最下面一层,背景层在中间,最上面一层为菊花和文字loading,用isLoading控制显示
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
复制代码
left、top 、right、 bottom分别表明离Stack左、上、右、底四边的距离。width和height用于指定定位元素的宽度和高度,注意,此处的width、height 和其它地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位widget,举个例子,在水平方向时,你只能指定left、right、width三个属性中的两个,如指定left和width后,right会自动算出(left+width),若是同时指定三个属性则会报错,垂直方向同理。
//经过ConstrainedBox来确保Stack占满屏幕
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
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"),
)
],
),
);
复制代码
本文版权属于再惠研发团队,欢迎转载,转载请保留出处。@xqqlv