上一篇Flutter–>自定义Widget(类比Android自定义View)
介绍了如何自定义一个Widget, 这一篇文章介绍如果自定义容器Widget, 相当于Android
中的ViewGroup
Android
自定义ViewGroup
先来简单介绍一下Android
中自定义的ViewGroup
:
class CustomViewGroup(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//进行测量操作, 确定自身的大小, 已经测量child的大小
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//进行布局操作, 确定子view的位置
}
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
return LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
}
class LayoutParams(width: Int, height: Int) : ViewGroup.LayoutParams(width, height) {
//自定义的布局参数
}
}
onMeasure
方法: 进行测量操作, 确定自身的大小, 已经测量child的大小onLayout
方法: 进行布局操作, 确定子view的位置LayoutParams
child的布局参数, 在parent中读取生效
Flutter
自定义容器Widget
那么在Flutter
中, 怎么自定义容器Widget
呢?
自定义只有一个child
的容器如下:
class CustomContainerWidget extends SingleChildRenderObjectWidget {
const CustomContainerWidget({super.key, super.child});
RenderObject createRenderObject(BuildContext context) =>
CustomContainerRenderObject();
}
/// [RenderObjectWithChildMixin]
class CustomContainerRenderObject extends RenderBox
with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
void setupParentData(covariant RenderObject child) {
if (child.parentData is! CustomContainerParentData) {
child.parentData = CustomContainerParentData();
}
}
void performLayout() {
if (child != null) {
final parentData = child!.parentData as CustomContainerParentData;
parentData.offset = const Offset(100, 100); // 确定子元素的位置
}
}
void paint(PaintingContext context, Offset offset) {
final RenderBox? child = this.child;
if (child == null) {
return;
}
context.paintChild(child, offset);
}
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return child?.hitTest(result, position: position) ?? false;
}
}
在Flutter
中, 有可能代码都是通过mixin
混入的方式实现的, 这一点非常方便.
setupParentData
方法, 用来设置child
的参数, 提供给容器使用的.performLayout
方法, 用来确定自身大小
和child大小
, 还有child位置
paint
方法, 用来自绘和绘制child
hitTestChildren
方法, 用来实现child
的事件支持
自定义一组child
的容器如下:
class CustomListContainerWidget extends MultiChildRenderObjectWidget {
const CustomListContainerWidget({super.key, super.children});
RenderObject createRenderObject(BuildContext context) =>
CustomListContainerRenderObject();
}
/// [ContainerRenderObjectMixin]
class CustomListContainerRenderObject extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, CustomContainerParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, CustomContainerParentData> {
void setupParentData(covariant RenderObject child) {
if (child.parentData is! CustomContainerParentData) {
child.parentData = CustomContainerParentData();
}
}
void performLayout() {
visitChildren((child) {
final parentData = child.parentData as CustomContainerParentData;
parentData.offset = const Offset(100, 100); // 确定子元素的位置
});
}
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
一组child
的自定义容器, 核心方法和自定义一个child
是一样的, 只不会每个方法都要处理一组child
.
以上都是BoxConstraints
约束模型的自定义, SliverConstraints
约束模型, 比较复杂, 之后再谈…
/// [ParentDataWidget]
class CustomContainerParentData extends ContainerBoxParentData<RenderBox> {}
Flutter
中有一个Widget
专门用来配置ParentData
它就是ParentDataWidget
.
所以一般都是自定义一个ParentDataWidget
用来传递ParentData
:
class CustomContainerParentDataWidget
extends ParentDataWidget<CustomContainerParentData> {
const CustomContainerParentDataWidget({super.key, required super.child});
void applyParentData(RenderObject renderObject) {
if (renderObject.parentData is! CustomContainerParentData) {
renderObject.parentData = CustomContainerParentData();
}
}
Type get debugTypicalAncestorWidgetClass => CustomContainerParentDataWidget;
}
总结
Android | Flutter | |
---|---|---|
测量入口 | View.onMeasure | RenderObject.performLayout |
child 位置 | View.layout | BoxParentData.offset |
child 参数 | LayoutParams | ParentData |
触发重新绘制 | View.invalidate | RenderObject.markNeedsPaint |
触发重新布局 | View.requestLayout | RenderObject.markNeedsLayout |
源码地址
群内有各(pian)种(ni)各(jin)样(qun)
的大佬,等你来撩.
联系作者
点此QQ对话 该死的空格
点此快速加群