Flutter布局详解,必知必会

本文详细介绍了Flutter中的各种布局组件,如Column,Row,Expanded,Flexible,Wrap,Flow,Stack以及Positioned,重点讲解了它们的用法、区别和适用场景,展示了如何实现流式布局、弹性布局和层叠布局。
摘要由CSDN通过智能技术生成

),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(“我是Colum的子控件”),
Text(“CrossAxisAlignment.end”),
],
),
],)

代码运行效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Column和Row差不多,只是布局方向不一样而已,大家可以参考着看,这里就不再赘述了。

实际使用

由于篇幅有限,我就不详细讲解实际遇到的问题了,只说现象和解决办法:

  • 如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有对最外面的Row或Column会占用尽可能大的空间,里面Row或Column所占用的空间为实际大小,如果要让里面的Colum或Row占满外部Colum或Row,可以使用Expanded widget
  • 如果使用Column发现超范围,可用SingleChildScrollView包裹,scrollDirection属性设置滑动方向
  • 使用Column嵌套ListView/GridView的时候,会报异常信息【Viewports expand in the scrolling direction to fill their container…】,这种情况flutter已给出解决办法,将ListView/GridView的 shrinkWrap属性设为true
  • 有的时候修改Row/Column的verticalDirection会得到很好的效果,比如需要页面在底部需要几个按键,也可以用Stack来布局,但是相对麻烦,而且有时还需要知道控件的大小,没有verticalDirection方便

弹性布局

弹性布局是一种允许子widget按照一定比例来分配父容器空间的布局方式,如果你知道了它的主轴方向,那就可以用Row或Column了,一般情况下,可以用Flex的地方都可以用Row或者Column一起使用,通常配合Expanded Widget来使用,同样Expanded也不能脱离Flex单独创建。

Expanded

Expanded继承自Flexible,Flexible是一个控制Row、Column、Flex等子组件如何布局的组件,它可以按比例“扩伸”Row、Column和Flex子widget所占用的空间。

const Expanded({
int flex = 1,
@required Widget child,
})

flex为弹性系数,如果为0或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其flex的比例来分割主轴的全部空闲空间。

示例代码

Row(children: [
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(‘绿色按钮’),
),
])

代码运行效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Flexible和 Expanded的区别
  • Flexible组件必须是Row、Column、Flex等组件的后裔,并且从Flexible到它封装的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidgets)
  • Row、Column、Flex会被Expanded撑开,充满主轴可用空间,而Flexible不强制子组件填充可用空间,这是因为fit属性的值不同,该属性在Expanded中为FlexFit.tight,Flexible为FlexFit.loose,区别在于tight表示强制使子控件填充剩余可用空间,loose表示最多填满其在父控件所设置的比例,所以loose默认即为控件的大小

流式布局

流式布局(Liquid)的特点(也叫"Fluid") 是页面元素的宽度按照屏幕分辨率进行适配调整,但整体布局不变。栅栏系统(网格系统),用户标签等。在Flutter中主要有Wrap和Flow两种Widget实现。

Wrap

在介绍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 children = const [],
})

上述有很多属性和Row的相同,其意义其实也是相同的,这里我就不一一介绍了,主要介绍下不同的属性:

  • spacing:主轴方向子widget的间距
  • runSpacing:纵轴方向的间距
  • runAlignment:纵轴方向的对齐方式
示例代码

Wrap(
spacing: 10.0,
direction: Axis.horizontal,
alignment: WrapAlignment.start,
children: [
_card(‘关注’),
_card(‘推荐’),
_card(‘新时代’),
_card(‘小视频’),
_card(‘党媒推荐’),
_card(‘中国新唱将’),
_card(‘历史’),
_card(‘视频’),
_card(‘游戏’),
_card(‘头条号’),
_card(‘数码’),
],
)

Widget _card(String title) {
return Card(child: Text(title),);
}
}

运行效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结
  • 使用Wrap可以很轻松的实现流式布局效果
  • Wrap支持设置流式布局是纵向显示或者是横向显示
  • 可以使用alignment属性来控制widgets的布局方式
Flow

我们一般很少会使用Flow,因为其过于复杂,需要自己实现子widget的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。Flow有如下优点:

  • 性能好;Flow是一个对child尺寸以及位置调整非常高效的控件,Flow用转换矩阵(transformation matrices)在对child进行位置调整的时候进行了优化:在Flow定位过后,如果child的尺寸或者位置发生了变化,在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘,而context.paintChild在重绘时使用了转换矩阵(transformation matrices),并没有实际调整Widget位置。
  • 灵活;由于我们需要自己实现FlowDelegate的paintChildren()方法,所以我们需要自己计算每一个widget的位置,因此,可以自定义布局策略。 缺点:
  • 使用复杂.
  • 不能自适应子widget大小,必须通过指定父容器大小或实现TestFlowDelegate的getSize返回固定大小。
示例代码

我们对六个色块进行自定义流式布局:

Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
children: [
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;
}
}

运行效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到我们主要的任务就是实现paintChildren,它的主要任务是确定每个子widget位置。由于Flow不能自适应子widget的大小,我们通过在getSize返回一个固定大小来指定Flow的大小,实现起来还是比较麻烦的。

小结
  • 参数简单,不过需要自己定义delegate
  • delegate一般是为了实现child的绘制,就是位置的摆放,不同情况需要定义不同的delegate
  • 不同的delegate一般会提供实现的几个方法:
  • getConstraintsForChild: 设置每个child的布局约束条件,会覆盖已有的方式
  • getSize:设置控件的尺寸
  • shouldRelayout:表示是否需要重新布局
  • 尽可能的用Wrap,毕竟简单

层叠布局

层叠布局和Web中的绝对定位、Android中的Frame布局是相似的,子widget可以根据到父容器四个角的位置来确定本身的位置。绝对定位允许子widget堆叠(按照代码中声明的顺序)。Flutter中使用Stack和Positioned来实现绝对定位,Stack允许子widget堆叠,而Positioned可以给子widget定位(根据Stack的四个角)。

Stack

Stack({
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List children = const [],
})

  • alignment:此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子widget。所谓部分定位,在这里特指没有在某一个轴上定位:left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位。
  • textDirection:和Row、Wrap的textDirection功能一样,都用于决定alignment对齐的参考系即:textDirection的值为TextDirection.ltr,则alignment的start代表左,end代表右;textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左。
  • fit:此参数用于决定没有定位的子widget如何去适应Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示扩伸到Stack的大小。
  • overflow:此属性决定如何显示超出Stack显示空间的子widget,值为Overflow.clip时,超出部分会被剪裁(隐藏),值为Overflow.visible 时则不会。
下面是我用Stack实现的一个简易的loading

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 widgetList = List();
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: [
CupertinoActivityIndicator(),
Padding(
padding: EdgeInsets.only(top: margin),
child: Text(text ?? ‘加载中…’)),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(cornerRadius)),
color: Colors.white),
),
);
}
}

显示效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本控件使用Stack封装,你传入的主视图在最下面一层,背景层在中间,最上面一层为菊花和文字loading,用isLoading控制显示

Positioned

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

[外链图片转存中…(img-qHInEwCx-1712269682149)]

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值