/// 計算回傳第一個 child 的基線 ,常用于 child 的位置順序有關
double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline)
/// 計算回傳所有 child 中最小的基線,常用于 child 的位置順序無關
double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline)
/// 觸摸碰撞測驗
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position })
/// 默認繪制
void defaultPaint(PaintingContext context, Offset offset)
/// 以陣列方式回傳 child 鏈表
List getChildrenAsList()
3、ContainerBoxParentData
ContainerBoxParentData 是 BoxParentData 的子類,主要是關聯了 ContainerDefaultsMixin 和 BoxParentData ,BoxParentData 是 RenderBox 繪制時所需的位置類,
通過 ContainerBoxParentData ,我們可以將 RenderBox 需要的 BoxParentData 和上面的 ContainerParentDataMixin 組合起來,事實上我們得到的 children 雙鏈表就是以 ParentData 的形式呈現出來的,
abstract class ContainerBoxParentData extends BoxParentData with ContainerParentDataMixin { }
4、MultiChildRenderObjectWidget
MultiChildRenderObjectWidget 的實作很簡單 ,它僅僅只是繼承了 RenderObjectWidget,然后提供了 children 陣列,并創建了 MultiChildRenderObjectElement,
上面的 RenderObjectWidget 顧名思義,它是提供 RenderObject 的 Widget ,那有不存在 RenderObject 的 Widget 嗎?
有的,比如我們常見的 StatefulWidget 、 StatelessWidget 、 Container 等,它們的 Element 都是 ComponentElement , ComponentElement 僅僅起到容器的作用,而它的 get renderObject 需要來自它的 child ,
5、MultiChildRenderObjectElement
前面的篇章我們說過 Element 是 BuildContext 的實作, 內部一般持有 Widget 、RenderObject 并作為二者溝通的橋梁,那么 MultiChildRenderObjectElement 就是我們自定義布局時的橋梁了, 如下代碼所示,MultiChildRenderObjectElement 主要實作了如下介面,其主要功能是對內部 children 的 RenderObject ,實作了插入、移除、訪問、更新等邏輯:
/// 下面三個方法都是利用 ContainerRenderObjectMixin 的 in
《Android學習筆記總結+最新移動架構視頻+大廠安卓面試真題+專案實戰原始碼講義》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整內容開源分享
sert/move/remove 去操作
/// ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin
void insertChildRenderObject(RenderObject child, Element slot)
void moveChildRenderObject(RenderObject child, dynamic slot)
void removeChildRenderObject(RenderObject child)
/// visitChildren 是通過 Element 中的 ElementVisitor 去迭代的
/// 一般在 RenderObject get renderObject 會呼叫
void visitChildren(ElementVisitor visitor)
/// 添加忽略child _forgottenChildren.add(child);
void forgetChild(Element child)
/// 通過 inflateWidget , 把 children 中 List 對應的 List
void mount(Element parent, dynamic newSlot)
/// 通過 updateChildren 方法去更新得到 List
void update(MultiChildRenderObjectWidget newWidget)
所以 MultiChildRenderObjectElement 利用 ContainerRenderObjectMixin 最終將我們自定義的 RenderBox 和 Widget 關聯起來,
6、自定義流程
上述主要描述了 MultiChildRenderObjectWidget 、 MultiChildRenderObjectElement 和其他三個輔助類ContainerRenderObjectMixin 、 RenderBoxContainerDefaultsMixin 和 ContainerBoxParentData 之間的關系,
了解幾個關鍵類之后,我們看一般情況下,實作自定義布局的簡化流程是:
1、自定義 ParentData 繼承 ContainerBoxParentData ,
2、繼承 RenderBox ,同時混入 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 實作自定義RenderObject ,
3、繼承 MultiChildRenderObjectWidget,實作 createRenderObject 和 updateRenderObject 方法,關聯我們自定義的 RenderBox,
4、override RenderBox 的 performLayout 和 setupParentData 方法,實作自定義布局,
當然我們可以利用官方的 CustomMultiChildLayout 實作自定義布局,這個后面也會講到,現在讓我們先從基礎開始, 而上述流程中混入的 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin ,在 RenderFlex 、RenderWrap 、RenderStack 等官方實作的布局里,也都會混入它們,
三、自定義布局
自定義布局就是在 performLayout 中實作的 child.layout 大小和 child.ParentData.offset 位置的賦值,
首先我們要實作類似如圖效果,我們需要自定義 RenderCloudParentData 繼承 ContainerBoxParentData ,用于記錄寬高和內容區域 :
class RenderCloudParentData extends ContainerBoxParentData {
double width;
double height;
Rect get content => Rect.fromLTWH(
offset.dx,
offset.dy,
width,
height,
);
}
然后自定義 RenderCloudWidget 繼承 RenderBox ,并混入 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 實作 RenderBox 自定義的簡化,
class RenderCloudWidget extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {
RenderCloudWidget({
List children,
Overflow overflow = Overflow.visible,
double ratio,
}) : _ratio = ratio,
_overflow = overflow {
///添加所有 child
addAll(children);
}
如下代碼所示,接下來主要看 RenderCloudWidget 中override performLayout 中的實作,這里我們只放關鍵代碼:
-
1、我們首先拿到
ContainerRenderObjectMixin鏈表中的firstChild,然后從頭到位讀取整個鏈表, -
2、對于每個 child 首先通過
child.layout設定他們的大小,然后記錄下大小之后, -
3、以容器控制元件的中心為起點,從內到外設定布局,這是設定的時候,需要通過記錄的
Rect判斷是否會重復,每次布局都需要計算位置,直到當前 child 不在重復區域內, -
4、得到最終布局內大小,然后設定整體居中,
///設定為我們的資料
@override
void setupParentData(RenderBox child) {
if (child.parentData is! RenderCloudParentData)
child.parentData = RenderCloudParentData();
}
@override
void performLayout() {
///默認不需要裁剪
_needClip = false;
///沒有 childCount 不玩
if (childCount == 0) {
size = constraints.smallest;
return;
}
///初始化區域
var recordRect = Rect.zero;
var previousChildRect = Rect.zero;
RenderBox child = firstChild;
while (child != null) {
var curIndex = -1;
///提出資料
final RenderCloudParentData childParentData = child.parentData;
child.layout(constraints, parentUsesSize: true);
var childSize = child.size;
///記錄大小
childParentData.width = childSize.width;
childParentData.height = childSize.height;
do {
///設定 xy 軸的比例
var rX = ratio >= 1 ? ratio : 1.0;
var rY = ratio <= 1 ? ratio : 1.0;
///調整位置
var step = 0.02 * _mathPi;
var rotation = 0.0;
var angle = curIndex * step;
var angleRadius = 5 + 5 * angle;
var x = rX * angleRadius * math.cos(angle + rotation);
var y = rY * angleRadius * math.sin(angle + rotation);
var position = Offset(x, y);
///計算得到絕對偏移
var childOffset = position - Alignment.center.alongSize(childSize);
++curIndex;
///設定為遏制
childParentData.offset = childOffset;
///判處是否交疊
} while (overlaps(childParentData));
///記錄區域
previousChildRect = childParentData.content;
recordRect = recordRect.expandToInclude(previousChildRect);
///下一個
child = childParentData.nextSibling;
}
///調整布局大小
size = constraints
.tighten(
height: recordRect.height,
width: recordRect.width,
)
.smallest;
///居中
var contentCenter = size.center(Offset.zero);
var recordRectCenter = recordRect.center;
var transCenter = contentCenter - recordRectCenter;
child = firstChild;
while (child != null) {
final RenderCloudParentData childParentData = child.parentData;
childParentData.offset += transCenter;
child = childParentData.nextSibling;
}
///超過了嘛?
_needClip =
size.width < recordRect.width || size.height < recordRect.height;
}
其實看完代碼可以發現,關鍵就在于你怎么設定 child.parentData 的 offset ,來控制其位置,
最后通過 CloudWidget 加載我們的 RenderCloudWidget 即可, 當然完整代碼還需要結合 FittedBox 與 RotatedBox 簡化完成,具體可見 :GSYFlutterDemo
class CloudWidget extends MultiChildRenderObjectWidget {
final Overflow overflow;
final double ratio;
CloudWidget({
Key key,
this.ratio = 1,
this.overflow = Overflow.clip,
List children = const [],
}) : super(key: key, children: children);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderCloudWidget(
ratio: ratio,
overflow: overflow,
);
}
@override
void updateRenderObject(
BuildContext context, RenderCloudWidget renderObject) {
renderObject
…ratio = ratio
…overflow = overflow;
}
}
最后我們總結,實作自定義布局的流程就是,實作自定義 RenderBox 中 performLayout child 的 offset ,
四、CustomMultiChildLayout
CustomMultiChildLayout 是 Flutter 為我們封裝的簡化自定義布局實作,它的內部同樣是通過 MultiChildRenderObjectWidget 實作,但是它為我們封裝了 RenderCustomMultiChildLayoutBox 和 MultiChildLayoutParentData ,并通過 MultiChildLayoutDelegate 暴露出需要自定義的地方,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/377112.html
標籤:其他
上一篇:Android單元測驗
