自定义布局
我们来实现这样一个布局,每个正方形都是一个子控件,先实现基础的部分
class MyLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey,
child: LayoutDemo(
children: <Widget>[
Container(
color: Colors.blue,
width: 100,
height: 100,
),
Container(
color: Colors.red,
width: 100,
height: 100,
),
Container(
color: Colors.green,
width: 100,
height: 100,
),
Container(
color: Colors.amber,
width: 100,
height: 100,
)
],
),
);
}
}
然后我们通过LayoutDemo
来对4个子控件进行位置摆放,和之前自定义控件一样,继承一个RenderObjectWidget
,不过这次我们使用MultiChildRenderObjectWidget
,顾名思义,多个子控件
class LayoutDemo extends MultiChildRenderObjectWidget{
LayoutDemo({
Key key,
List<Widget> children
}): super(key: key , children: children);
@override
RenderLayout createRenderObject(BuildContext context) {
return RenderLayout();
}
}
class PageParentData extends ContainerBoxParentData<RenderBox> {}
class RenderLayout extends RenderBox with ContainerRenderObjectMixin<RenderBox, PageParentData>,RenderBoxContainerDefaultsMixin<RenderBox, PageParentData>{
//必须使用,作用是初始化data对象
@override
void setupParentData(RenderBox child) {
if (child.parentData is! PageParentData)
child.parentData = PageParentData();
}
@override
void performLayout() {}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
}
为什么混合ContainerRenderObjectMixin
类,和之前一样,类里包含了子控件及偏移的数据,也可以不用自己写。混合RenderBoxContainerDefaultsMixin
类是用其defaultPaint(context, offset);
,当然也可以单独提取出来
直接回到layout过程
@override
void performLayout() {
var index = 0;
double widthOffset = 0.0;
double heightOffset = 0.0;
//size = constraints.constrain(Size(200, 200));
size = constraints.constrain(Size(double.infinity,double.infinity));
RenderBox child = firstChild;
while(child != null){
if(index != 0){
if(index%2 == 0){
widthOffset = 0.0;
heightOffset += 100.0;
}else{
widthOffset += 100.0;
}
}
final PageParentData childParentData = child.parentData;
//给一个约束布局,让子控件自己摆放
child.layout(constraints.heightConstraints(), parentUsesSize: true);
//保存偏移值
childParentData.offset = Offset(widthOffset, heightOffset);
index++;
//找到下一个子控件
child = childParentData.nextSibling;
}
}
layout过程中,必须给size赋值,size = constraints.constrain(Size(double.infinity,double.infinity));
,这个就是match_parent
效果,内部占据了最大;
size = constraints.constrain(Size(300, 300));
给定一个精确的值;size = constraints.constrain(Size.zero);
,这个就是warp_content
效果,布局的大小有全部子控件的大小决定
真正的绘制决定了子控件的位置
void defaultPaint(PaintingContext context, Offset offset) {
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
//使用之前保存的偏移值
context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}
同样的,也有官方的封装控件CustomMultiChildLayout
,需要给一个delegate
(作用与之前的CustomPainter
一样)
class LayoutWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey,
child: CustomMultiChildLayout(
delegate: _MyDelegate(),
children: <Widget>[
LayoutId(
id: 0,
child: Container(
color: Colors.blue,
width: 100,
height: 100,
)
),
LayoutId(
id: 1,
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
LayoutId(
id: 2,
child: Container(
color: Colors.green,
width: 100,
height: 100,
),
),
LayoutId(
id: 3,
child: Container(
color: Colors.amber,
width: 100,
height: 100,
)
),
],
),
);
}
}
LayoutId
给每个子控件一个id编号,供下面判断
class _MyDelegate extends MultiChildLayoutDelegate{
@override
void performLayout(Size size) {
//这里传递过来的size是父控件的size,因为父类并没有对size进行约束,所以是最大的
double widthOffset = 0.0;
double heightOffset = 0.0;
for(int i = 0; i < 4; i++){
if(hasChild(0)){
if(i != 0){
if(i%2 == 0){
widthOffset = 0.0;
heightOffset += 100.0;
}else{
widthOffset += 100.0;
}
}
//其实内部就是用child.layout(constraints.heightConstraints(), parentUsesSize: true);
layoutChild(i, BoxConstraints.loose(size));
//其实内部就是用childParentData.offset = Offset(widthOffset, heightOffset);
positionChild(i, Offset(widthOffset, heightOffset));
}
}
}
//是否需要重新摆放
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return false;
}
}