【Flutter高级玩法- Flow 】我的位置我做主

Flow布局是一个超级强大的布局,但应该很少有人用,因为入手的门槛还是有的
Flow的属性很简单,只有FlowDelegate类型的delegate和组件列表children,
可能很多人看到delegate就挥挥手:臣妾做不到,今天就来掰扯一下这个FlowDelegate.

class Flow extends MultiChildRenderObjectWidget {
Flow({
Key key,
@required this.delegate,
List children = const [],
}) : assert(delegate != null),
复制代码

第一幕、开场-演员入台

  1. 展示舞台

我们的第一个舞台是一个200*200的灰色box,由FlowDemo组件出当主角

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Flutter Demo’,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(),
body: Center(child: HomePage()),
));
}
}

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: Colors.grey.withAlpha(66),
alignment: Alignment.center,
child: FlowDemo(),
);
}
}
复制代码
2. Flow出场

FlowDemo中使用Flow组件,包含四个box
四个box变成依次是60.0(红), 50.0(黄), 40.0(蓝), 30.0(绿)

class FlowDemo extends StatelessWidget {
final sides = [60.0, 50.0, 40.0, 30.0];
final colors = [Colors.red,Colors.yellow,Colors.blue,Colors.green];

@override
Widget build(BuildContext context) {
return Flow(
delegate: _Delegate(),
children: sides.map((e) => _buildItem(e)).toList(),
);
}

Widget _buildItem(double e) {
return Container(
width: e,
alignment: Alignment.center,
height: e,
color: colors[sides.indexOf(e)],
child: Text(’$e’),
);
}
}
复制代码
3. FlowDelegate出场

Flow布局需要一个FlowDelegate类型的delegate对象
但是Flutter中并没有其实现类,所以想玩Flow,只有一条路:自定义

class _Delegate extends FlowDelegate {
@override
void paintChildren(FlowPaintingContext context) {

}

@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return true;
}
}

复制代码
4. paintChildren方法和FlowPaintingContext对象

paintChildren顾名思义是用来画孩子的
FlowPaintingContext也就是绘制的上下文,即绘制的信息
那就轻轻的瞄一眼FlowPaintingContext里面有啥吧:
一共有四个东西: size、childCount、getChildSize、paintChild

---->[源码:flutter/lib/src/rendering/flow.dart:23]----
abstract class FlowPaintingContext {
Size get size;//父亲尺寸
int get childCount;//孩子个数
Size getChildSize(int i);//第i个孩子尺寸
//绘制孩子
void paintChild(int i, { Matrix4 transform, double opacity = 1.0 });
}
复制代码
接下来用代码测试一下这几个属性看看,不出所料
默认是绘制在父容器的左上角。

class _Delegate extends FlowDelegate {
@override
void paintChildren(FlowPaintingContext context) {
print(“父容器尺寸: c o n t e x t . s i z e " ) ; p r i n t ( " : {context.size}"); print("孩子个数: {context.childCount}”);
for(int i=0;i<context.childCount;i++){
print(“第 i : i个孩子尺寸: {context.getChildSize(i)}”);
}
}
复制代码

第二幕、排兵布阵

前面只是将组件排在了左上角,那如何对进行其他排布呢?

  1. paintChild与Matrix4

在paintChild时可以传入transform的Matrix4对象进行变换
在这里基本上只用了Matrix4的平移translationValues功能,
至于Matrix4的具体用法,那又是一个故事了
这里让黄色的box移到右上角,即X方向平移(父宽-己宽):

@override
void paintChildren(FlowPaintingContext context) {
var size = context.size;
for (int i = 0; i < context.childCount; i++) {
if (i == 1) {
var tr = context.getChildSize(i);
context.paintChild(i,
transform:
Matrix4.translationValues(size.width - tr.width, 0, 0.0));
} else {
context.paintChild(i);
}
}
}
复制代码

现在让四个组件排布在父亲的四角,如下:

class _AngleDelegate extends FlowDelegate {
Matrix4 m4;

@override
void paintChildren(FlowPaintingContext context) {
var size = context.size;
for (int i = 0; i < context.childCount; i++) {
var cSize = context.getChildSize(i);
if (i == 1) {
m4 = Matrix4.translationValues(size.width - cSize.width, 0, 0.0);
} else if (i == 2) {
m4 = Matrix4.translationValues(0, size.height - cSize.height, 0.0);
} else if (i == 3) {
m4 = Matrix4.translationValues(size.width - cSize.width, size.height - cSize.height, 0.0);
}
context.paintChild(i, transform: m4);
}
}

@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return true;
}
}
复制代码
2. Flow布局的封装

如果需要一个排布四角的组件,可以基于上面的Delegate做一个组件
虽然用处很有限,但原来了解一下Flow还是挺好的。

class AngleFlow extends StatelessWidget {
final List children;

AngleFlow({@required this.children}) : assert(children.length == 4);

@override
Widget build(BuildContext context) {
return Flow(
delegate: _AngleDelegate(),
children: children,
);
}
}

class _AngleDelegate extends FlowDelegate {
Matrix4 m4;

@override
void paintChildren(FlowPaintingContext context) {
var size = context.size;
for (int i = 0; i < context.childCount; i++) {
var cSize = context.getChildSize(i);
if (i == 1) {
m4 = Matrix4.translationValues(size.width - cSize.width, 0, 0.0);
} else if (i == 2) {
m4 = Matrix4.translationValues(0, size.height - cSize.height, 0.0);
} else if (i == 3) {
m4 = Matrix4.translationValues(
size.width - cSize.width, size.height - cSize.height, 0.0);
}
context.paintChild(i, transform: m4);
}
}

@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return true;
}
}
复制代码
3. 圆形的Flow布局

其实可以看出,Flow的核心就是根据信息来计算位置
所以,所有的布局都可以通过Flow进行实现。
除此之外对应一些特定情况的布局,使用Flow会非常简单,比如:

class CircleFlow extends StatelessWidget {
final List children;

CircleFlow({@required this.children});

@override
Widget build(BuildContext context) {
return Flow(
delegate: _CircleFlowDelegate(),
children: children,
);
}
}

class _CircleFlowDelegate extends FlowDelegate {
@override //绘制孩子的方法
void paintChildren(FlowPaintingContext context) {
double radius = context.size.shortestSide / 2;
var count = context.childCount;
var perRad = 2 * pi / count;
for (int i = 0; i < count; i++) {
print(i);
var cSizeX = context.getChildSize(i).width / 2;
var cSizeY = context.getChildSize(i).height / 2;

  var offsetX = (radius - cSizeX) * cos(i * perRad) + radius;
  var offsetY = (radius - cSizeY) * sin(i * perRad) + radius;
  context.paintChild(i,
      transform: Matrix4.translationValues(
          offsetX - cSizeX, offsetY - cSizeY, 0.0));
}

}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return true;
}
}

发布了68 篇原创文章 · 获赞 1 · 访问量 954

猜你喜欢

转载自blog.csdn.net/A669MM/article/details/104857910