blob: ee679538be22217cd638d52e603d8dde512b3af5 [file] [log] [blame]
// Copyright 2017 The Chromium 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 'basic.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({
Key key,
@required SliverChildDelegate delegate,
@required this.prototypeItem,
}) : assert(prototypeItem != null), super(key: key, delegate: delegate);
/// 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
_RenderSliverPrototypeExtentList createRenderObject(BuildContext context) {
final _SliverPrototypeExtentListElement element = context;
return new _RenderSliverPrototypeExtentList(childManager: element);
}
@override
_SliverPrototypeExtentListElement createElement() => new _SliverPrototypeExtentListElement(this);
}
class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement {
_SliverPrototypeExtentListElement(SliverPrototypeExtentList widget) : super(widget);
@override
SliverPrototypeExtentList get widget => super.widget;
@override
_RenderSliverPrototypeExtentList get renderObject => super.renderObject;
Element _prototype;
static final Object _prototypeSlot = new Object();
@override
void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot) {
if (slot == _prototypeSlot) {
assert(child is RenderBox);
renderObject.child = child;
} else {
super.insertChildRenderObject(child, slot);
}
}
@override
void didAdoptChild(RenderBox child) {
if (child != renderObject.child)
super.didAdoptChild(child);
}
@override
void moveChildRenderObject(RenderBox child, dynamic slot) {
if (slot == _prototypeSlot)
assert(false); // There's only one prototype child so it cannot be moved.
else
super.moveChildRenderObject(child, slot);
}
@override
void removeChildRenderObject(RenderBox child) {
if (renderObject.child == child)
renderObject.child = null;
else
super.removeChildRenderObject(child);
}
@override
void visitChildren(ElementVisitor visitor) {
if (_prototype != null)
visitor(_prototype);
super.visitChildren(visitor);
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_prototype = updateChild(_prototype, widget.prototypeItem, _prototypeSlot);
}
@override
void update(SliverPrototypeExtentList newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_prototype = updateChild(_prototype, widget.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;
}
}