blob: 5fa4533b2c22bdc40eb8134c740ce73502be4625 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'sliver.dart';
/// A sliver that places its box children in a linear array and constrains them
/// to have the same extent as a prototype item along the main axis.
///
/// [SliverPrototypeExtentList] arranges its children in a line along
/// the main axis starting at offset zero and without gaps. Each child is
/// constrained to the same extent as the [prototypeItem] along the main axis
/// and the [SliverConstraints.crossAxisExtent] along the cross axis.
///
/// [SliverPrototypeExtentList] is more efficient than [SliverList] because
/// [SliverPrototypeExtentList] does not need to lay out its children to obtain
/// their extent along the main axis. It's a little more flexible than
/// [SliverFixedExtentList] because there's no need to determine the appropriate
/// item extent in pixels.
///
/// See also:
///
/// * [SliverFixedExtentList], whose itemExtent is a pixel value.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
/// * [SliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
/// * [SliverList], which shows a list of variable-sized children in a
/// viewport.
class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget {
/// Creates a sliver that places its box children in a linear array and
/// constrains them to have the same extent as a prototype item along
/// the main axis.
const SliverPrototypeExtentList({
super.key,
required super.delegate,
required this.prototypeItem,
}) : assert(prototypeItem != null);
/// Defines the main axis extent of all of this sliver's children.
///
/// The [prototypeItem] is laid out before the rest of the sliver's children
/// and its size along the main axis fixes the size of each child. The
/// [prototypeItem] is essentially [Offstage]: it is not painted and it
/// cannot respond to input.
final Widget prototypeItem;
@override
RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context) {
final _SliverPrototypeExtentListElement element = context as _SliverPrototypeExtentListElement;
return _RenderSliverPrototypeExtentList(childManager: element);
}
@override
SliverMultiBoxAdaptorElement createElement() => _SliverPrototypeExtentListElement(this);
}
class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement {
_SliverPrototypeExtentListElement(SliverPrototypeExtentList super.widget);
@override
_RenderSliverPrototypeExtentList get renderObject => super.renderObject as _RenderSliverPrototypeExtentList;
Element? _prototype;
static final Object _prototypeSlot = Object();
@override
void insertRenderObjectChild(covariant RenderObject child, covariant Object slot) {
if (slot == _prototypeSlot) {
assert(child is RenderBox);
renderObject.child = child as RenderBox;
} else {
super.insertRenderObjectChild(child, slot as int);
}
}
@override
void didAdoptChild(RenderBox child) {
if (child != renderObject.child) {
super.didAdoptChild(child);
}
}
@override
void moveRenderObjectChild(RenderBox child, Object oldSlot, Object newSlot) {
if (newSlot == _prototypeSlot) {
// There's only one prototype child so it cannot be moved.
assert(false);
} else {
super.moveRenderObjectChild(child, oldSlot as int, newSlot as int);
}
}
@override
void removeRenderObjectChild(RenderBox child, Object slot) {
if (renderObject.child == child) {
renderObject.child = null;
} else {
super.removeRenderObjectChild(child, slot as int);
}
}
@override
void visitChildren(ElementVisitor visitor) {
if (_prototype != null) {
visitor(_prototype!);
}
super.visitChildren(visitor);
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_prototype = updateChild(_prototype, (widget as SliverPrototypeExtentList).prototypeItem, _prototypeSlot);
}
@override
void update(SliverPrototypeExtentList newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_prototype = updateChild(_prototype, (widget as SliverPrototypeExtentList).prototypeItem, _prototypeSlot);
}
}
class _RenderSliverPrototypeExtentList extends RenderSliverFixedExtentBoxAdaptor {
_RenderSliverPrototypeExtentList({
required _SliverPrototypeExtentListElement childManager,
}) : super(childManager: childManager);
RenderBox? _child;
RenderBox? get child => _child;
set child(RenderBox? value) {
if (_child != null) {
dropChild(_child!);
}
_child = value;
if (_child != null) {
adoptChild(_child!);
}
markNeedsLayout();
}
@override
void performLayout() {
child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
super.performLayout();
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_child != null) {
_child!.attach(owner);
}
}
@override
void detach() {
super.detach();
if (_child != null) {
_child!.detach();
}
}
@override
void redepthChildren() {
if (_child != null) {
redepthChild(_child!);
}
super.redepthChildren();
}
@override
void visitChildren(RenderObjectVisitor visitor) {
if (_child != null) {
visitor(_child!);
}
super.visitChildren(visitor);
}
@override
double get itemExtent {
assert(child != null && child!.hasSize);
return constraints.axis == Axis.vertical ? child!.size.height : child!.size.width;
}
}