• Flutter Silver’s Lifetime Enemy (ScrollView)
  • Flutter silver lifetime enemies (ExtendedList)
  • Flutter Sliver You want waterfall flow little sister
  • Flutter silver locks your beauty

preface

Sliver it’s been 8 months since you asked for your little sister waterfall flow. Writing bugs takes time. Sliver says it works, but there are a few minor flaws.

  • SliverPersistentHeader

For anyone who has used this, the question is why do you have to set minextents and maxextents? If I don’t know the height of the Widget in advance, for example, if it’s a piece of text, I don’t know the length, I don’t know the height in advance (of course you can use TextPainter). It’s really disgusting, because when I was writing code a year ago, I had to do it in advance, low.

  • SliverToBoxAdapter

I want to keep the CustomScrollView pinned to a Widget, I think everyone’s first reaction is to use SliverPersistentHeader and set minExtent and maxExtent to the same. What if I don’t know the height of the Widget in advance?

  • SliverAppbar

The inside is made with a SliverPersistentHeader that exposes an expandedHeight. Again, you want me to set the height ahead of time.

How to lock you gracefully

As usual, the first step is to look at the source code.

SliverPinnedPersistentHeader

See source SliverPersistentHeader

Here, according to pinned and floating differences, the official data are divided into the following 4 situations.

    if (floating && pinned)
      return _SliverFloatingPinnedPersistentHeader(delegate: delegate);
    if (pinned)
      return _SliverPinnedPersistentHeader(delegate: delegate);
    if (floating)
      return _SliverFloatingPersistentHeader(delegate: delegate);
    return _SliverScrollingPersistentHeader(delegate: delegate);
Copy the code

I’m going to assume that you’ve seen the previous article, so I’m going to go straight to the final impact on the layout

  • RenderSliverScrollingPersistentHeader extends RenderSliverPersistentHeader
  • RenderSliverPinnedPersistentHeader extends RenderSliverPersistentHeader
  • RenderSliverFloatingPersistentHeader extends RenderSliverPersistentHeader
  • RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPersistentHeader

Can see they are ultimately RenderSliverPersistentHeader, there will be the same paint process, and their two main different is that performLayout and childMainAxisPosition method.

We will only focus on pinned:true here.

RenderSliverPinnedPersistentHeader I left the key code here.

  @override
  void performLayout() {
    final SliverConstraints constraints = this.constraints;
    / / the delegate maxExtent
    final double maxExtent = this.maxExtent;
    // Whether to overlap with other slivers
    final bool overlapsContent = constraints.overlap > 0.0;
    // layoutChild calls the delegate. Build method here
    layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
    // The rest of the draw area
    final double effectiveRemainingPaintExtent = math.max(0, constraints.remainingPaintExtent - constraints.overlap);
    / / layout area
    final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, effectiveRemainingPaintExtent) as double;
    final doublestretchOffset = stretchConfiguration ! =null ?
      constraints.overlap.abs() :
      0.0;
    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: constraints.overlap,
      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
      layoutExtent: layoutExtent,
      maxPaintExtent: maxExtent + stretchOffset,
      maxScrollObstructionExtent: minExtent,
      cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
      hasVisualOverflow: true.// Conservatively say we do have overflow to avoid complexity.
    );
  }
  
  // Affects where the final child is drawn in the paint method.
  @override
  double childMainAxisPosition(RenderBox child) => 0.0;
Copy the code

The above things should be more familiar, the Sliver series inside the compulsory knowledge. We just need to calculate minExtent and maxExtent in the performLayout process.

How do you compute that with the WidgetminExtentmaxExtent

Convert the Widget to RenderBox

We all know that Widget <=> Element <=> RenderOjbect, the Element is attached to the parent in the mount, At this point we can use the updateChild method to convert the Widget to the corresponding Element, and get the Widget’s RenderOjbect(RenderBox) from the insertChildRenderObject.

The important code is as follows:

  Element _minExtentPrototype;
  static final Object _minExtentPrototypeSlot = Object(a);Element _maxExtentPrototype;
  static final Object _maxExtentPrototypeSlot = Object(a);@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    renderObject.element = this;
    _minExtentPrototype = updateChild(_minExtentPrototype,
        // Widget corresponding to minExtent
        widget.delegate.minExtentProtoType, _minExtentPrototypeSlot);
    _maxExtentPrototype = updateChild(_maxExtentPrototype,
        // Widget corresponding to maxExtent
        widget.delegate.maxExtentProtoType, _maxExtentPrototypeSlot);
  }
  
  @override
  void insertChildRenderObject(covariant RenderBox child, dynamic slot) {
    assert(renderObject.debugValidateChild(child));

    assert(child is RenderBox);
    // Assign child to RenderOject according to slot
    if (slot == _minExtentPrototypeSlot) {
      renderObject.minProtoType = child;
    } else if (slot == _maxExtentPrototypeSlot) {
      renderObject.maxProtoType = child;
    } else{ renderObject.child = child; }}Copy the code

The stack information is shown below

Calculate by renderBox.layoutminExtentmaxExtent

The logic is the same as the official logic, except that minProtoType and maxProtoType are used to calculate minExtent and maxProtoType


  RenderBox _minProtoType;
  RenderBox get minProtoType => _minProtoType;
  set minProtoType(RenderBox value) {
    if(_minProtoType ! =null) {
      dropChild(_minProtoType);
    }
    _minProtoType = value;
    if(_minProtoType ! =null) {
      adoptChild(_minProtoType);
    }
    markNeedsLayout();
  }

  RenderBox _maxProtoType;
  RenderBox get maxProtoType => _maxProtoType;
  set maxProtoType(RenderBox value) {
    if(_maxProtoType ! =null) {
      dropChild(_maxProtoType);
    }
    _maxProtoType = value;
    if(_maxProtoType ! =null) {
      adoptChild(_maxProtoType);
    }
    markNeedsLayout();
  }

  double get minExtent => getChildExtend(minProtoType, constraints);
  double get maxExtent => getChildExtend(maxProtoType, constraints);

double getChildExtend(RenderBox child, SliverConstraints constraints) {
  if (child == null) {
    return 0.0;
  }
  assert(child.hasSize);
  assert(constraints.axis ! =null);
  switch (constraints.axis) {
    case Axis.vertical:
      return child.size.height;
    case Axis.horizontal:
      return child.size.width;
  }
  return null;
}

  @override
  void performLayout() {
    final SliverConstraints constraints = this.constraints;
    minProtoType.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    maxProtoType.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    final bool overlapsContent = constraints.overlap > 0.0;
    excludeFromSemanticsScrolling =
        overlapsContent || (constraints.scrollOffset > maxExtent - minExtent);
    layoutChild(constraints.scrollOffset, maxExtent,
        overlapsContent: overlapsContent);
    final double effectiveRemainingPaintExtent =
        math.max(0, constraints.remainingPaintExtent - constraints.overlap);
    final double layoutExtent = (maxExtent - constraints.scrollOffset)
        .clamp(0.0, effectiveRemainingPaintExtent) as double;

    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: constraints.overlap,
      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
      layoutExtent: layoutExtent,
      maxPaintExtent: maxExtent,
      maxScrollObstructionExtent: minExtent,
      cacheExtent: layoutExtent > 0.0
          ? -constraints.cacheOrigin + layoutExtent
          : layoutExtent,
      hasVisualOverflow:
          true.// Conservatively say we do have overflow to avoid complexity.
    );
  }
Copy the code

SliverPinnedToBoxAdapter

See source SliverToBoxAdapter

The SliverToBoxAdapter source code is relatively simple. It is drawn in the paint method using ParentData to set the drawing start point

class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
  /// Creates a [RenderSliver] that wraps a [RenderBox].
  RenderSliverToBoxAdapter({
    RenderBox child,
  }) : super(child: child);

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    double childExtent;
    switch (constraints.axis) {
      case Axis.horizontal:
        childExtent = child.size.width;
        break;
      case Axis.vertical:
        childExtent = child.size.height;
        break;
    }
    assert(childExtent ! =null);
    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
    final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);

    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);
    geometry = SliverGeometry(
      scrollExtent: childExtent,
      paintExtent: paintedChildSize,
      cacheExtent: cacheExtent,
      maxPaintExtent: childExtent,
      hitTestExtent: paintedChildSize,
      hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,); setChildParentData(child, constraints, geometry); }}Copy the code

We said at the beginning that a SliverToBoxAdapter can be pinned to the data side and remain pinned to the data side. True) and minExtent = maxExtent = child’s extent. So all is well solved, we can get the performLayout RenderSliverPinnedPersistentHeader code directly to use, and calculate the good drawing the starting point to ParentData can.

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    assert(childExtent ! =null);
    final double effectiveRemainingPaintExtent =
        math.max(0, constraints.remainingPaintExtent - constraints.overlap);
    final double layoutExtent = (childExtent - constraints.scrollOffset)
        .clamp(0.0, effectiveRemainingPaintExtent) as double;

    geometry = SliverGeometry(
      scrollExtent: childExtent,
      paintOrigin: constraints.overlap,
      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
      layoutExtent: layoutExtent,
      maxPaintExtent: childExtent,
      maxScrollObstructionExtent: childExtent,
      cacheExtent: layoutExtent > 0.0
          ? -constraints.cacheOrigin + layoutExtent
          : layoutExtent,
      hasVisualOverflow:
          true.// Conservatively say we do have overflow to avoid complexity.
    );
    setChildParentData(child, constraints, geometry);
  }

  @override
  void setChildParentData(RenderObject child, SliverConstraints constraints,
      SliverGeometry geometry) {
    final SliverPhysicalParentData childParentData =
        child.parentData as SliverPhysicalParentData;
    assert(constraints.axisDirection ! =null);
    assert(constraints.growthDirection ! =null);
    Offset offset = Offset.zero;
    switch (applyGrowthDirectionToAxisDirection(
        constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
        offset += Offset(
            0.0,
            geometry.paintExtent -
                childMainAxisPosition(child as RenderBox) -
                childExtent);
        break;
      case AxisDirection.down:
        offset += Offset(0.0, childMainAxisPosition(child as RenderBox));
        break;
      case AxisDirection.left:
        offset += Offset(
            geometry.paintExtent -
                childMainAxisPosition(child as RenderBox) -
                childExtent,
            0.0);
        break;
      case AxisDirection.right:
        offset += Offset(childMainAxisPosition(child as RenderBox), 0.0);
        break;
    }
    childParentData.paintOffset = offset;
    assert(childParentData.paintOffset ! =null);
  }

  @override
  double childMainAxisPosition(RenderBox child) => 0.0;
Copy the code

ExtendedSliverAppbar

This is not to look at the code, very simple, it is made of SliverPinnedPersistentHeader, of course, is also the reference for the official SliverAppbar `, just as complicated as there is no official, believes that seen in front of me a few silver related article, can literally change.

How do I use the Silver Extension library

Add reference

Add the reference to dependencies under pubspec.yaml

dependencies:
  extended_sliver: latest-version
Copy the code

Execute the flutter Packages get download

SliverPinnedPersistentHeader

Same as the official SliverPersistentHeader(pinned: true), the difference is that you don’t have to set minExtent and maxExtent.

It calculates minextents and maxextents by setting minExtentProtoType and maxExtentProtoType.

This is a very useful component when you don’t know the actual size of the Widget before it has a layout.

    SliverPinnedPersistentHeader(
      delegate: MySliverPinnedPersistentHeaderDelegate(
        minExtentProtoType: Container(
          height: 120.0,
          color: Colors.red.withOpacity(0.5),
          child: FlatButton(
            child: const Text('minProtoType'),
            onPressed: () {
              print('minProtoType');
            },
          ),
          alignment: Alignment.topCenter,
        ),
        maxExtentProtoType: Container(
          height: 200.0,
          color: Colors.blue,
          child: FlatButton(
            child: const Text('maxProtoType'),
            onPressed: () {
              print('maxProtoType');
            },
          ),
          alignment: Alignment.bottomCenter,
        ),
      ),
    )
Copy the code

SliverPinnedToBoxAdapter

You can easily create a locked Sliver.

This is a very useful component when you have no way of knowing the actual size of the child before it has a layout.

    SliverPinnedToBoxAdapter(
      child: Container(
        padding: const EdgeInsets.all(20),
        color: Colors.blue.withOpacity(0.5),
        child: Column(
          children: <Widget>[
            const Text(
                '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. '
                '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]'
                '\n\nif you meet any problem, please let me konw @zmtzawqlp .[sun_glasses]'),
            FlatButton(
              child: const Text('I\'m button. click me! '),
              onPressed: () {
                debugPrint('click'); },),],),)Copy the code

ExtendedSliverAppbar

You can create a SliverAppbar without setting an expandedHeight.

return CustomScrollView(
  slivers: <Widget>[
    ExtendedSliverAppbar(
      title: const Text(
        'ExtendedSliverAppbar',
        style: TextStyle(color: Colors.white),
      ),
      leading: const BackButton(
        onPressed: null,
        color: Colors.white,
      ),
      background: Image.asset(
        'assets/cypridina.jpeg',
        fit: BoxFit.cover,
      ),
      actions: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Icon(
          Icons.more_horiz,
          color: Colors.white,
        ),
      ),
    ),
  ],
);
Copy the code

Complex example

Example address, including the following features.

  • Random length of text, no need to write maxExtent
  • The drop-down refresh
  • Controls the visibility of buttons in the Toolbar based on their location

conclusion

2020 is going to be a busy year and a fast growth year for Flutter, web performance has been improved, MacOS, Linux are all in beta, and UWP is on the way. All I need is you. What are you looking at? You? ! Put extended_sliver on, if you have any good Sliver effects, welcome pr; If you need anything new, you’re welcome.

Welcome to joinFlutter CandiesAnd produce cute little Flutter candies (QQ group: 181398081)

Finally put the Flutter on the bucket. It smells good.