自定義布局

我們來(lái)實(shí)現(xiàn)這樣一個(gè)布局,每個(gè)正方形都是一個(gè)子控件,先實(shí)現(xiàn)基礎(chǔ)的部分
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,
)
],
),
);
}
}
然后我們通過(guò)LayoutDemo來(lái)對(duì)4個(gè)子控件進(jìn)行位置擺放,和之前自定義控件一樣,繼承一個(gè)RenderObjectWidget,不過(guò)這次我們使用MultiChildRenderObjectWidget,顧名思義,多個(gè)子控件
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對(duì)象
@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類(lèi),和之前一樣,類(lèi)里包含了子控件及偏移的數(shù)據(jù),也可以不用自己寫(xiě)?;旌?code>RenderBoxContainerDefaultsMixin類(lèi)是用其defaultPaint(context, offset);,當(dāng)然也可以單獨(dú)提取出來(lái)
直接回到layout過(guò)程
@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;
//給一個(gè)約束布局,讓子控件自己擺放
child.layout(constraints.heightConstraints(), parentUsesSize: true);
//保存偏移值
childParentData.offset = Offset(widthOffset, heightOffset);
index++;
//找到下一個(gè)子控件
child = childParentData.nextSibling;
}
}
layout過(guò)程中,必須給size賦值,size = constraints.constrain(Size(double.infinity,double.infinity));,這個(gè)就是match_parent效果,內(nèi)部占據(jù)了最大;
size = constraints.constrain(Size(300, 300));給定一個(gè)精確的值;size = constraints.constrain(Size.zero);,這個(gè)就是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,需要給一個(gè)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給每個(gè)子控件一個(gè)id編號(hào),供下面判斷
class _MyDelegate extends MultiChildLayoutDelegate{
@override
void performLayout(Size size) {
//這里傳遞過(guò)來(lái)的size是父控件的size,因?yàn)楦割?lèi)并沒(méi)有對(duì)size進(jìn)行約束,所以是最大的
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;
}
}
//其實(shí)內(nèi)部就是用child.layout(constraints.heightConstraints(), parentUsesSize: true);
layoutChild(i, BoxConstraints.loose(size));
//其實(shí)內(nèi)部就是用childParentData.offset = Offset(widthOffset, heightOffset);
positionChild(i, Offset(widthOffset, heightOffset));
}
}
}
//是否需要重新擺放
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return false;
}
}