您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
427 行
14 KiB
427 行
14 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.ui;
|
|
|
|
public interface RenderSliverVariableSizeBoxChildManager
|
|
{
|
|
void createChild(int index);
|
|
|
|
void removeChild(RenderBox child);
|
|
|
|
float estimateMaxScrollOffset(
|
|
SliverConstraints constraints,
|
|
int? firstIndex = null,
|
|
int? lastIndex = null,
|
|
float? leadingScrollOffset = null,
|
|
float? trailingScrollOffset = null
|
|
);
|
|
|
|
int childCount { get; }
|
|
|
|
void didAdoptChild(RenderBox child);
|
|
|
|
// ignore: avoid_positional_boolean_parameters
|
|
void setDidUnderflow(bool value);
|
|
|
|
void didStartLayout();
|
|
|
|
void didFinishLayout();
|
|
|
|
bool debugAssertChildListLocked();
|
|
}
|
|
|
|
public class SliverVariableSizeBoxAdaptorParentData : SliverMultiBoxAdaptorParentData
|
|
{
|
|
public float crossAxisOffset;
|
|
|
|
internal bool _keptAlive = false;
|
|
|
|
public override string ToString() => $"crossAxisOffset={crossAxisOffset}; {base.ToString()}";
|
|
}
|
|
|
|
public abstract class RenderSliverVariableSizeBoxAdaptor : TileContainerRenderObjectMixinRenderSliver<RenderBox,
|
|
SliverVariableSizeBoxAdaptorParentData>
|
|
{
|
|
public RenderSliverVariableSizeBoxAdaptor(RenderSliverVariableSizeBoxChildManager childManager)
|
|
{
|
|
_childManager = childManager;
|
|
}
|
|
|
|
public override void setupParentData(RenderObject child)
|
|
{
|
|
if (!(child.parentData is SliverVariableSizeBoxAdaptorParentData))
|
|
{
|
|
child.parentData = new SliverVariableSizeBoxAdaptorParentData();
|
|
}
|
|
}
|
|
|
|
protected RenderSliverVariableSizeBoxChildManager childManager
|
|
{
|
|
get => _childManager;
|
|
}
|
|
|
|
public readonly RenderSliverVariableSizeBoxChildManager _childManager;
|
|
|
|
public readonly Dictionary<int, RenderBox> _keepAliveBucket = new Dictionary<int, RenderBox>();
|
|
|
|
protected override void adoptChild(AbstractNodeMixinDiagnosticableTree childNode)
|
|
{
|
|
var child = childNode as RenderObject;
|
|
base.adoptChild(child);
|
|
var childParentData =
|
|
child?.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
if (childParentData?.keepAlive != null && (bool) !childParentData?.keepAlive)
|
|
{
|
|
childManager.didAdoptChild(child as RenderBox);
|
|
}
|
|
}
|
|
|
|
bool _debugAssertChildListLocked() =>
|
|
childManager.debugAssertChildListLocked();
|
|
|
|
public override void remove(int index)
|
|
{
|
|
RenderBox child = this[index];
|
|
|
|
// if child is null, it means this element was cached - drop the cached element
|
|
if (child == null)
|
|
{
|
|
RenderBox cachedChild = _keepAliveBucket[index];
|
|
if (cachedChild != null)
|
|
{
|
|
dropChild(cachedChild);
|
|
_keepAliveBucket.Remove(index);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
if (childParentData?._keptAlive != null && (bool) !childParentData?._keptAlive)
|
|
{
|
|
base.remove(index);
|
|
return;
|
|
}
|
|
|
|
D.assert(childParentData != null &&
|
|
(childParentData.index != null && (_keepAliveBucket[childParentData.index] == child)));
|
|
_keepAliveBucket.Remove(childParentData.index);
|
|
dropChild(child);
|
|
}
|
|
|
|
public override void removeAll()
|
|
{
|
|
base.removeAll();
|
|
_keepAliveBucket.Values.ToList().ForEach(dropChild);
|
|
_keepAliveBucket.Clear();
|
|
}
|
|
|
|
void _createOrObtainChild(int index)
|
|
{
|
|
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) =>
|
|
{
|
|
D.assert(constraints == this.constraints);
|
|
if (_keepAliveBucket.ContainsKey(index))
|
|
{
|
|
RenderBox child = _keepAliveBucket[index];
|
|
_keepAliveBucket.Remove(index);
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
D.assert(childParentData._keptAlive);
|
|
dropChild(child);
|
|
child.parentData = childParentData;
|
|
this[index] = child;
|
|
childParentData._keptAlive = false;
|
|
}
|
|
else
|
|
{
|
|
_childManager.createChild(index);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _destroyOrCacheChild(int index)
|
|
{
|
|
RenderBox child = this[index];
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
if (childParentData.keepAlive)
|
|
{
|
|
D.assert(!childParentData._keptAlive);
|
|
remove(index);
|
|
_keepAliveBucket[childParentData.index] = child;
|
|
child.parentData = childParentData;
|
|
base.adoptChild(child);
|
|
childParentData._keptAlive = true;
|
|
}
|
|
else
|
|
{
|
|
D.assert(child.parent == this);
|
|
_childManager.removeChild(child);
|
|
D.assert(child.parent == null);
|
|
}
|
|
}
|
|
|
|
public override void attach(object o)
|
|
{
|
|
base.attach(owner);
|
|
if (o is PipelineOwner)
|
|
{
|
|
_keepAliveBucket.Values.ToList().ForEach((child) => child.attach(owner));
|
|
}
|
|
}
|
|
|
|
public override void detach()
|
|
{
|
|
base.detach();
|
|
_keepAliveBucket.Values.ToList().ForEach((child) => child.detach());
|
|
}
|
|
|
|
public override void redepthChildren()
|
|
{
|
|
base.redepthChildren();
|
|
_keepAliveBucket.Values.ToList().ForEach(redepthChild);
|
|
}
|
|
|
|
public override void visitChildren(RenderObjectVisitor visitor)
|
|
{
|
|
base.visitChildren(visitor);
|
|
_keepAliveBucket.Values.ToList().ForEach(a => visitor(a));
|
|
}
|
|
|
|
public bool addChild(int index)
|
|
{
|
|
D.assert(_debugAssertChildListLocked());
|
|
_createOrObtainChild(index);
|
|
var child = this[index];
|
|
if (child != null)
|
|
{
|
|
D.assert(indexOf(child) == index);
|
|
return true;
|
|
}
|
|
|
|
childManager.setDidUnderflow(true);
|
|
return false;
|
|
}
|
|
|
|
public RenderBox addAndLayoutChild(
|
|
int index,
|
|
BoxConstraints childConstraints,
|
|
bool parentUsesSize = false
|
|
)
|
|
{
|
|
D.assert(_debugAssertChildListLocked());
|
|
_createOrObtainChild(index);
|
|
var child = this[index];
|
|
if (child != null)
|
|
{
|
|
D.assert(indexOf(child) == index);
|
|
child.layout(childConstraints, parentUsesSize: parentUsesSize);
|
|
return child;
|
|
}
|
|
|
|
childManager.setDidUnderflow(true);
|
|
return null;
|
|
}
|
|
|
|
protected void collectGarbage(HashSet<int> visibleIndices)
|
|
{
|
|
D.assert(_debugAssertChildListLocked());
|
|
D.assert(childCount >= visibleIndices.Count);
|
|
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) =>
|
|
{
|
|
// We destroy only those which are not visible.
|
|
indices.Except(visibleIndices).ToList().ForEach(_destroyOrCacheChild);
|
|
|
|
// Ask the child manager to remove the children that are no longer being
|
|
// kept alive. (This should cause _keepAliveBucket to change, so we have
|
|
// to prepare our list ahead of time.)
|
|
_keepAliveBucket.Values
|
|
.Where((RenderBox child) =>
|
|
{
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
return childParentData != null && !childParentData.keepAlive;
|
|
})
|
|
.ToList()
|
|
.ForEach(_childManager.removeChild);
|
|
D.assert(!_keepAliveBucket.Values.Where((RenderBox child) =>
|
|
{
|
|
return child.parentData is SliverVariableSizeBoxAdaptorParentData childParentData &&
|
|
!childParentData.keepAlive;
|
|
}).Any());
|
|
});
|
|
}
|
|
|
|
public int indexOf(RenderBox child)
|
|
{
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
D.assert(childParentData?.index != null);
|
|
return childParentData.index;
|
|
}
|
|
|
|
protected float paintExtentOf(RenderBox child)
|
|
{
|
|
D.assert(child.hasSize);
|
|
switch (constraints.axis)
|
|
{
|
|
case Axis.horizontal:
|
|
return child.size.width;
|
|
case Axis.vertical:
|
|
return child.size.height;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
protected override bool hitTestChildren(SliverHitTestResult result,
|
|
float mainAxisPosition, float crossAxisPosition)
|
|
{
|
|
foreach (var child in children)
|
|
{
|
|
if (this.hitTestBoxChild(
|
|
new BoxHitTestResult(result),
|
|
child,
|
|
mainAxisPosition: mainAxisPosition,
|
|
crossAxisPosition: crossAxisPosition)
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override float? childMainAxisPosition(RenderObject child)
|
|
{
|
|
return childScrollOffset(child) - constraints.scrollOffset;
|
|
}
|
|
|
|
public override float? childCrossAxisPosition(RenderObject child)
|
|
{
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
return childParentData.crossAxisOffset;
|
|
}
|
|
|
|
public override float? childScrollOffset(RenderObject child)
|
|
{
|
|
D.assert(child.parent == this);
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
D.assert(childParentData != null && childParentData.layoutOffset != null);
|
|
return childParentData.layoutOffset;
|
|
}
|
|
|
|
public override void applyPaintTransform(RenderObject child, Matrix4 transform)
|
|
{
|
|
this.applyPaintTransformForBoxChild(child as RenderBox, transform);
|
|
}
|
|
|
|
public override void paint(PaintingContext context, Offset offset)
|
|
{
|
|
if (childCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// offset is to the top-left corner, regardless of our axis direction.
|
|
// originOffset gives us the delta from the real origin to the origin in the axis direction.
|
|
Offset mainAxisUnit = new Offset(0, 0);
|
|
Offset crossAxisUnit = new Offset(0, 0);
|
|
Offset originOffset = new Offset(0, 0);
|
|
bool? addExtent = null;
|
|
switch (GrowthDirectionUtils.applyGrowthDirectionToAxisDirection(
|
|
constraints.axisDirection, constraints.growthDirection))
|
|
{
|
|
case AxisDirection.up:
|
|
mainAxisUnit = new Offset (0, -1);
|
|
crossAxisUnit = new Offset (1, 0);
|
|
originOffset = offset + new Offset(0, geometry.paintExtent);
|
|
addExtent = true;
|
|
break;
|
|
case AxisDirection.right:
|
|
mainAxisUnit = new Offset (1, 0);
|
|
crossAxisUnit = new Offset (0, 1);
|
|
originOffset = offset;
|
|
addExtent = false;
|
|
break;
|
|
case AxisDirection.down:
|
|
mainAxisUnit = new Offset (0, 1);
|
|
crossAxisUnit = new Offset (1, 0);
|
|
originOffset = offset;
|
|
addExtent = false;
|
|
break;
|
|
case AxisDirection.left:
|
|
mainAxisUnit = new Offset (-1, 0);
|
|
crossAxisUnit = new Offset (0, 1);
|
|
originOffset = offset + new Offset(geometry.paintExtent, 0);
|
|
addExtent = true;
|
|
break;
|
|
}
|
|
|
|
foreach (var child in children) {
|
|
float? mainAxisDelta = childMainAxisPosition(child);
|
|
float? crossAxisDelta = childCrossAxisPosition(child);
|
|
Offset childOffset = new Offset(
|
|
(float) (originOffset.dx +
|
|
mainAxisUnit.dx * mainAxisDelta +
|
|
crossAxisUnit.dx * crossAxisDelta),
|
|
(float) (originOffset.dy +
|
|
mainAxisUnit.dy * mainAxisDelta +
|
|
crossAxisUnit.dy * crossAxisDelta)
|
|
);
|
|
if (addExtent.Value)
|
|
{
|
|
childOffset += mainAxisUnit * paintExtentOf(child);
|
|
}
|
|
|
|
context.paintChild(child, childOffset);
|
|
}
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties)
|
|
{
|
|
base.debugFillProperties(properties);
|
|
properties.add(DiagnosticsNode.message(childCount > 0
|
|
? $"currently live children: ${string.Join(", ", indices)}"
|
|
: "no children current live"));
|
|
}
|
|
|
|
public override List<DiagnosticsNode> debugDescribeChildren()
|
|
{
|
|
List<DiagnosticsNode>
|
|
childList = new List<DiagnosticsNode>();
|
|
if (childCount > 0)
|
|
{
|
|
foreach (var child in
|
|
children) {
|
|
var childParentData =
|
|
child.parentData as SliverVariableSizeBoxAdaptorParentData;
|
|
childList.Add(child.toDiagnosticsNode(
|
|
name: $"child with index {childParentData.index}"));
|
|
}
|
|
}
|
|
|
|
if (_keepAliveBucket.isNotEmpty())
|
|
{
|
|
List<int> indices = _keepAliveBucket.Keys.ToList();
|
|
indices.Sort();
|
|
foreach (var index in indices) {
|
|
childList.Add(_keepAliveBucket[index].toDiagnosticsNode(
|
|
name: $"child with index {index} (kept alive offstage)",
|
|
style: DiagnosticsTreeStyle.offstage
|
|
));
|
|
}
|
|
}
|
|
|
|
return childList;
|
|
}
|
|
}
|