Flutter布局分析

Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 1,
child: Container(
height: 80.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 80.0,
color: Colors.green,
),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: SizedBox(
height: 100.0,
//Flex的三个子widget,在垂直方向按2:1:1来占用100像素的空间
child: Flex(
direction: Axis.vertical,
children: [
Expanded(
flex: 2,
child: Container(
color: Colors.red,
),
),
Spacer(//Spacer的功能是占用指定比例的空间,实际上它只是Expanded的一个包装
flex: 1,
),
Expanded(
flex: 1,
child: Container(
color: Colors.green,
),
),
],
),
),
),
],
);
}

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

流式布局

流式布局(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

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: [
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”),
)
],
),
);

运行效果:

由于第一个子widget Text(“Hello world”)没有指定定位,并且alignment值为Alignment.center,所以,它会居中显示。第二个子widget Text(“I am Jack”)只指定了水平方向的定位(left),所以属于部分定位,即垂直方向上没有定位,那么它在垂直方向对齐方式则会按照alignment指定的对齐方式对齐,即垂直方向居中。对于第三个子widget Text(“Your friend”),和第二个Text原理一样,只不过是水平方向没有定位,则水平方向居中。

参考:
https://juejin.im/post/5c2458d6f265da613a541349
https://my.oschina.net/mchp/blog/3123764
https://www.jianshu.com/p/5c2654422486
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

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

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

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

img

img

img

img

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

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

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

总结

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?

作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

960页全网最全Android开发笔记

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

络 + 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-vZ2fzNCw-1712766412014)]

[外链图片转存中…(img-Bp24e16C-1712766412014)]

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值