opt: HeaderLayout with SlottedMultiChild (#1850)

* opt: HeaderLayout with SlottedMultiChild

* ordered

* update [skip ci]
This commit is contained in:
My-Responsitories
2026-02-27 14:40:17 +08:00
committed by GitHub
parent b55e102dc3
commit 6782bee11a
2 changed files with 76 additions and 66 deletions

View File

@@ -18,12 +18,7 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'
show
ContainerRenderObjectMixin,
MultiChildLayoutParentData,
RenderBoxContainerDefaultsMixin,
BoxHitTestResult;
import 'package:flutter/rendering.dart' show BoxHitTestResult, BoxParentData;
const double kHeaderHeight = 135.0;
@@ -39,79 +34,105 @@ const double _kActionsRightPadding = 15.0;
enum HeaderType { header, avatar, actions }
class HeaderLayoutWidget extends MultiChildRenderObjectWidget {
class HeaderLayoutWidget
extends SlottedMultiChildRenderObjectWidget<HeaderType, RenderBox> {
final Widget header;
final Widget avatar;
final Widget actions;
const HeaderLayoutWidget({
super.key,
super.children,
required this.header,
required this.avatar,
required this.actions,
});
@override
RenderObject createRenderObject(BuildContext context) {
Iterable<HeaderType> get slots => HeaderType.values;
@override
Widget childForSlot(HeaderType slot) => switch (slot) {
.header => header,
.avatar => avatar,
.actions => actions,
};
@override
RenderHeaderWidget createRenderObject(BuildContext context) {
return RenderHeaderWidget();
}
}
class RenderHeaderWidget extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
@override
void setupParentData(RenderBox child) {
if (child.parentData is! MultiChildLayoutParentData) {
child.parentData = MultiChildLayoutParentData();
}
with SlottedContainerRenderObjectMixin<HeaderType, RenderBox> {
Offset _getOffset(RenderBox child) {
return (child.parentData as BoxParentData).offset;
}
void _setOffset(RenderBox child, Offset offset) {
(child.parentData as BoxParentData).offset = offset;
}
@override
void performLayout() {
double height = kHeaderHeight;
RenderBox? child = firstChild;
final maxWidth = constraints.maxWidth;
while (child != null) {
final childParentData = child.parentData! as MultiChildLayoutParentData;
switch (childParentData.id as HeaderType) {
case HeaderType.header:
child.layout(constraints);
childParentData.offset = .zero;
case HeaderType.avatar:
child.layout(constraints);
childParentData.offset = const Offset(
_kAvatarLeftPadding,
_kAvatarTopPadding,
);
case HeaderType.actions:
final childSize =
(child..layout(
BoxConstraints(
maxWidth:
maxWidth -
_kActionsLeftPadding -
_kActionsRightPadding,
),
parentUsesSize: true,
))
.size;
height += (math.max(_kAvatarEffectiveHeight, childSize.height)) + 5.0;
childParentData.offset = Offset(
maxWidth - childSize.width - _kActionsRightPadding,
_kActionsTopPadding,
);
}
_setOffset(
childForSlot(HeaderType.header)!..layout(constraints),
Offset.zero,
);
child = childParentData.nextSibling;
}
_setOffset(
childForSlot(HeaderType.avatar)!..layout(constraints),
const Offset(_kAvatarLeftPadding, _kAvatarTopPadding),
);
final actions = childForSlot(HeaderType.actions)!;
final childSize =
(actions..layout(
BoxConstraints(
maxWidth:
maxWidth - _kActionsLeftPadding - _kActionsRightPadding,
),
parentUsesSize: true,
))
.size;
height += (math.max(_kAvatarEffectiveHeight, childSize.height)) + 5.0;
_setOffset(
actions,
Offset(
maxWidth - childSize.width - _kActionsRightPadding,
_kActionsTopPadding,
),
);
size = constraints.constrainDimensions(maxWidth, height);
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
for (var slot in HeaderType.values) {
final child = childForSlot(slot)!;
context.paintChild(child, _getOffset(child) + offset);
}
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
for (var slot in HeaderType.values.reversed) {
final child = childForSlot(slot)!;
final bool isHit = result.addWithPaintOffset(
offset: _getOffset(child),
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
return child.hitTest(result, position: transformed);
},
);
if (isHit) {
return true;
}
}
return false;
}
}

View File

@@ -481,20 +481,9 @@ class UserInfoCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HeaderLayoutWidget(
children: [
LayoutId(
id: HeaderType.header,
child: _buildHeader(context, colorScheme, isLight, width),
),
LayoutId(
id: HeaderType.avatar,
child: _buildAvatar,
),
LayoutId(
id: HeaderType.actions,
child: _buildRight(colorScheme),
),
],
header: _buildHeader(context, colorScheme, isLight, width),
avatar: _buildAvatar,
actions: _buildRight(colorScheme),
),
const SizedBox(height: 5),
..._buildLeft(context, colorScheme, isLight),