08_flutter中如何优雅的提前获取child的宽高

08_flutter中如何优雅的提前获取child的宽高
一. 先上效果

最近在使用SliverAppBar实现可以折叠的标题栏时发现,在设置子SliverAppBar的展开高度时,需要显式的指定展开的高度,显得不够灵活,以下面代码为例:

dart 复制代码
CustomScrollView(
  slivers: [
    SliverAppBar(
      pinned: true,
      collapsedHeight: kToolbarHeight,
      expandedHeight: 300 - MediaQuery.paddingOf(context).top,
      centerTitle: true,
      title: Builder(builder: (context) {
        final FlexibleSpaceBarSettings? settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
        bool isScrolledUnder = settings?.isScrolledUnder ?? false;
        Color? titleColor = isScrolledUnder ? Theme.of(context).appBarTheme.foregroundColor:Colors.white;
        return Text(
          "首页",
          style: TextStyle(
            color: titleColor
          ),
        );
      }),
      backgroundColor: MaterialStateColor.resolveWith((states) {
        if(states.contains(MaterialState.scrolledUnder)) {
          return Theme.of(context).appBarTheme.backgroundColor ?? Colors.white;
        }
        return Colors.transparent;
      }),
      flexibleSpace: FlexibleSpaceBar(
        collapseMode: CollapseMode.pin,
        background: InkResponse(
          onTap: () {
            debugPrint("click");
          },
          child: Padding(
            padding: const EdgeInsets.only(bottom: 48),
            child: Image.asset(
              "assets/user.jpeg",
              fit: BoxFit.fitWidth,
            ),
          ),
        ),
      ),
      bottom: const TabBar(
        tabs: [
          Tab(
            text: "tab1",
          ),
          Tab(
            text: "tab2",
          )
        ],
      )
    ),

    SliverPadding(
      padding: const EdgeInsets.all(5),
      sliver: SliverList.list(
        children: List<Widget>.generate(10, (index) {
          return Container(
            height: 100,
            color: Colors.red,
            margin: const EdgeInsets.all(5),
            alignment: Alignment.center,
            child: Text(
              "item $index",
              style: const TextStyle(
                color: Colors.white
              ),
            ),
          );
        })
      ),
    )
  ],
)

我希望通过指定expandedHeight的值,保证标题栏能够完全显示出Image的内容,同时Image不会被裁剪。这个时候我们就需要提前知道flexibleSpace中的background以及bottom的高度flexibleHeight,然后设置expandedHeight的值为flexibleHeight - MediaQuery.paddingOf(context).top,那么,怎么样才能提前获取到background以及bottom的高度呢?

二.实现思路
  • 方案一

    自己计算,适用于当折叠内容的布局比较简单时,比如只用到了文本以及paddingmargin,那么可以先借助TextPainter计算文本的高度,再叠加上paddingmargin,这样做不能重用,且有局限性,还容易计算错误,所以不推荐。

  • 方案二

    重新实现一个SliverPersistentHeaderSliverAppBar,在SliverPersistentHeader对应的RenderObject中,重写performLayout来提前调整SliverPersistentHeadermaxExtent,这样做呢,貌似没什么问题,也能复用。但是假如Flutter SDKSliverPersistentHeaderSliverAppBar做了调整,那么咱们自己实现的组件也得跟着变,而且要变的东西很多,牵一发而动全身了,所以也不推荐。

  • 方案三

    参考了内置组件SliverPrototypeExtentList的实现,自定义RenderObjectWidgetRenderObjectElementRenderObject,在RenderObject中额外挂载一个原型widget,原型widget只参与layout,不参与paint,相当于原型widget只是为了layout之后计算宽高,并不会显示到屏幕上去,所以也就不会有性能的损耗。

    然后重写RenderObjectperformLayout,先layout原型widget,再根据原型widget的尺寸,去构建实际需要渲染的child

    最后使用如下:

    dart 复制代码
    SliverPrototypeBuilder(
      prototype: Container(
        height: 200,
        color: Colors.red,
      ),
      sliverBuilder: (context, mainAxisChildExtent, crossAxisChildExtent) {
        return SliverToBoxAdapter(
          child: Container(
            height: mainAxisChildExtent,
            color: Colors.red,
          ),
        );
      }
    )

    这种方式易扩展,支持复用,我们可以将这个组件作用于任何的Sliver组件,同样也可以配套实现一个普通RenderBox的版本,作用于普通的widget,同时使用简单也足够优雅,不需要重写sdk提供的widget,推荐使用这种方式,本文也将采用这种方式实现。

三.RenderObjectWidgetRenderObjectElementRenderObject各自负责什么。
  • RenderObjectWidget

    定义组件的尺寸约束、颜色等配置信息,负责创建RenderObjectElement RenderObject 对象,是一个不可变对象,要改变配置,只能重新去创建对象,由RenderObjectElement控制重建。

  • RenderObjectElement

    Flutter中随处可见的BuildContext就是一个RenderObjectElement对象,负责创建RenderObjectWidget对象,从而递归创建子组件的RenderObjectWidgetRenderObjectElementRenderObject,以及挂载子组件child对应的RenderObject到当前RenderObject上,同时控制RenderObjectWidget的重新创建以及指挥当前RenderObject去更新和干活(layoutpaint...),Flutter中的三棵树就是这样形成的,RenderObjectElement是连接RenderObjectWidgetRenderObject的桥梁。

  • RenderObject

    RenderObject才是真正负责干活的大冤种,负责确定widget的宽高(layout),以及调用渲染引擎把结果显示到屏幕上(paint),此过程同样是递归,layout过程遵循向下传递布局约束,向上传递child尺寸,父级根据子组件传递的尺寸以及自身的布局约束,确定自身的尺寸。

四.自定义RenderObjectElement
  • 创建_SliverPrototypeBuilderElement类继承RenderObjectElement

    dart 复制代码
    class _SliverPrototypeBuilderElement extends RenderObjectElement {
      _SliverPrototypeBuilderElement(SliverPrototypeBuilder super.widget);
    }
  • 重写visitChildrenforgetChildmountupdateinsertRenderObjectChildmoveRenderObjectChildremoveRenderObjectChild方法,把自身引用传递给RenderObjectRenderObject可以通过这个应用通知构建真正需要渲染的child对应的RenderObjectWidget,读取RenderObjectWidget的配置,通知RenderObjectWidget创建prototype对应的RenderObjectWidget以及挂载prototype原型widget对应的RenderBox

    dart 复制代码
    class _SliverPrototypeBuilderElement extends RenderObjectElement {
      _SliverPrototypeBuilderElement(SliverPrototypeBuilder super.widget);
      
      @override
      _RenderSliverPrototypeBuilder get renderObject => super.renderObject as _RenderSliverPrototypeBuilder;
      
      Element? _prototype;
      static final Object _prototypeSlot = Object();
      Element? _child;
      
      @override
      void visitChildren(ElementVisitor visitor) {
        // 暂时不知道是什么意思,依葫芦画了个瓢
        if (_prototype != null) {
          visitor(_prototype!);
        }
        if (_child != null) {
          visitor(_child!);
        }
      }
    
      @override
      void forgetChild(Element child) {
        if(_prototype == child) {
          // 卸载prototype
          _prototype = null;
        } else {
          // 卸载真实渲染的child
          _child = null;
        }
        super.forgetChild(child);
      }
    
      @override
      void mount(Element? parent, Object? newSlot) {
        super.mount(parent, newSlot);
        // 创建prototype对应的RenderObjectWidget、RenderObject、RenderObjectElement
        _prototype = updateChild(_prototype, (widget as SliverPrototypeBuilder).prototype, _prototypeSlot);
        // 由于真正需要渲染的child依赖prototype的尺寸,所以我们先不自动创建它,等到RenderObject中prototype的尺寸确定了,再通知创建
        // 把自身引用传递给RenderObject,RenderObject可以通过这个引用通知RenderObjectElement构建真正需要渲染的child对应的RenderObjectWidget
        renderObject._element = this;
      }
    
      @override
      void update(SliverPrototypeBuilder newWidget) {
        super.update(newWidget);
        assert(widget == newWidget);
        // 重建prototype
        _prototype = updateChild(_prototype, newWidget.prototype, _prototypeSlot);
        // 通知renderObject,prototype对应的RenderObjectWidget被重建了,请您重新执行layout过程更新
        renderObject.markNeedsLayout();
      }
    
      @override
      void insertRenderObjectChild(RenderObject child, Object? slot) {
        if (slot == _prototypeSlot) {
          // 将prototype对应的RenderBox挂载到renderObject
          assert(child is RenderBox);
          renderObject.prototype = child as RenderBox;
        } else {
          // 将真正需要渲染的child对应的RenderBox挂载到renderObject
          final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
          assert(renderObject.debugValidateChild(child));
          renderObject.child = child;
        }
      }
    
      @override
      void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
        // 单子容器组件无需实现。
        assert(false);
      }
    
      @override
      void removeRenderObjectChild(RenderObject child, Object? slot) {
        if (slot == _prototypeSlot) {
          // 将prototype对应的RenderBox从renderObject移除
          renderObject.prototype = null;
        } else {
          // 将真正需要渲染的child对应的RenderBox从renderObject移除
          renderObject.child = null;
        }
      }
    }
  • 提供一个_build方法,当计算出prototype的尺寸后,创建和更新真正需要渲染的child对应的RenderObjectWidgetRenderObjectRenderObjectElement

    dart 复制代码
    // mainAxisChildExtent:prototype在主轴方向的尺寸
    // crossAxisChildExtent:prototype在交叉轴方向的尺寸
    // 注:上下滚动时,主轴为垂直方向,mainAxisChildExtent代表height,crossAxisChildExtent代表width
    void _build(double mainAxisChildExtent, double crossAxisChildExtent) {
      owner!.buildScope(this, () {
        final SliverPrototypeBuilder renderObjectWidget = widget as SliverPrototypeBuilder;
        _child = updateChild(
          _child,
          renderObjectWidget.sliverBuilder(this, mainAxisChildExtent, crossAxisChildExtent),
          null,
        );
      });
    }
五.自定义RenderObject
  • 创建_RenderSliverPrototypeBuilder继承RenderProxySliver,通过继承RenderProxySliver后,我们只用重写performLayout的实现,其他的沿用系统的实现即可。

    dart 复制代码
    class _RenderSliverPrototypeBuilder extends RenderProxySliver {}
  • 定义prototype对应的RenderBox

    dart 复制代码
    class _RenderSliverPrototypeBuilder extends RenderProxySliver {
     	RenderBox? _prototype;
      RenderBox? get prototype => _prototype;
      set prototype(RenderBox? value) {
        // 先卸载
        if (_prototype != null) {
          dropChild(_prototype!);
        }
        _prototype = value;
        // 再挂载
        if (_prototype != null) {
          adoptChild(_prototype!);
        }
        // 重新layout
        markNeedsLayout();
      } 
      
        @override
      void attach(PipelineOwner owner) {
        super.attach(owner);
        if (_prototype != null) {
          _prototype!.attach(owner);
        }
      }
    
      @override
      void detach() {
        super.detach();
        if (_prototype != null) {
          _prototype!.detach();
        }
      }
    
      @override
      void redepthChildren() {
        if (_prototype != null) {
          redepthChild(_prototype!);
        }
        super.redepthChildren();
      }
    
      @override
      void visitChildren(RenderObjectVisitor visitor) {
        if (_prototype != null) {
          visitor(_prototype!);
        }
        super.visitChildren(visitor);
      }
    }
  • 重写performLayout,计算prototype的尺寸。

    dart 复制代码
    class _RenderSliverPrototypeBuilder extends RenderProxySliver {
      
      ...
      
      Size _computePrototypeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
        if (prototype != null) {
          if (!constraints.hasTightHeight) {
            final double height = prototype!.getMaxIntrinsicHeight(constraints.maxWidth);
            assert(height.isFinite);
            constraints = constraints.tighten(height: height);
          }
    
          if(!constraints.hasTightWidth) {
            final double width = prototype!.getMaxIntrinsicWidth(constraints.maxHeight);
            assert(width.isFinite);
            constraints = constraints.tighten(width: width);
          }
    
          return layoutChild(prototype!, constraints);
        } else {
          return constraints.smallest;
        }
      }
      
     	@override
      void performLayout() {
        // layout prototype...
        final SliverConstraints constraints = this.constraints;
        Size prototypeSize = _computePrototypeSize(
          layoutChild: ChildLayoutHelper.layoutChild,
          constraints: constraints.asBoxConstraints(),
        );
    
        ...
    
        super.performLayout();
      } 
    }
  • prototype尺寸确定后,通知RenderObjectElement去创建实际需要渲染的child对应的RenderObjectWidgetRenderObjectElementRenderObject,并把尺寸信息传递出去。

    dart 复制代码
    class _RenderSliverPrototypeBuilder extends RenderProxySliver {
      
      ...
      
      bool _needsUpdateChild = true;
      double? _lastMainAxisChildExtent;
      double? _lastCrossAxisChildExtent;
      _SliverPrototypeBuilderElement? _element;
        
      @override
      void markNeedsLayout() {
        _needsUpdateChild = true;
        super.markNeedsLayout();
      }
        
      void updateChild(Size size) {
        double mainAxisChildExtent = 0;
        double crossAxisChildExtent = 0;
        if(constraints.axis == Axis.vertical) {
          mainAxisChildExtent = size.height;
          crossAxisChildExtent = size.width;
        } else {
          mainAxisChildExtent = size.width;
          crossAxisChildExtent = size.height;
        }
    
        if (_needsUpdateChild || _lastMainAxisChildExtent != mainAxisChildExtent || _lastCrossAxisChildExtent != crossAxisChildExtent) {
          invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
            assert(constraints == this.constraints);
            assert(_element != null);
            _element!._build(mainAxisChildExtent, crossAxisChildExtent);
          });
          _lastMainAxisChildExtent = mainAxisChildExtent;
          _lastCrossAxisChildExtent = crossAxisChildExtent;
          _needsUpdateChild = false;
        }
      }
        
      @override
      void performLayout() {
        // layout prototype...
        final SliverConstraints constraints = this.constraints;
        Size prototypeSize = _computePrototypeSize(
          layoutChild: ChildLayoutHelper.layoutChild,
          constraints: constraints.asBoxConstraints(),
        );
    
        updateChild(prototypeSize);
    
        super.performLayout();
      } 
    }
六.自定义RenderObjectWidget
  • 比较简单,直接上代码了就。

    dart 复制代码
    typedef SliverPrototypeWidgetBuilder = Widget Function(BuildContext context, double mainAxisChildExtent, double crossAxisChildExtent);
    
    class SliverPrototypeBuilder extends RenderObjectWidget {
    
      final Widget? prototype;
      final SliverPrototypeWidgetBuilder sliverBuilder;
    
      const SliverPrototypeBuilder({
        super.key,
        this.prototype,
        required this.sliverBuilder
      });
    
      @override
      RenderObjectElement createElement() => _SliverPrototypeBuilderElement(this);
    
      @override
      RenderObject createRenderObject(BuildContext context) => _RenderSliverPrototypeBuilder();
    }
七.组件源码
dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

typedef SliverPrototypeWidgetBuilder = Widget Function(BuildContext context, double mainAxisChildExtent, double crossAxisChildExtent);

class SliverPrototypeBuilder extends RenderObjectWidget {

  final Widget? prototype;
  final SliverPrototypeWidgetBuilder sliverBuilder;

  const SliverPrototypeBuilder({
    super.key,
    this.prototype,
    required this.sliverBuilder
  });

  @override
  RenderObjectElement createElement() => _SliverPrototypeBuilderElement(this);

  @override
  RenderObject createRenderObject(BuildContext context) => _RenderSliverPrototypeBuilder();
}

class _SliverPrototypeBuilderElement extends RenderObjectElement {
  _SliverPrototypeBuilderElement(SliverPrototypeBuilder super.widget);

  @override
  _RenderSliverPrototypeBuilder get renderObject => super.renderObject as _RenderSliverPrototypeBuilder;

  Element? _prototype;
  static final Object _prototypeSlot = Object();
  Element? _child;

  @override
  void visitChildren(ElementVisitor visitor) {
    /// 暂时不知道是什么意思,依葫芦画的瓢
    if (_prototype != null) {
      visitor(_prototype!);
    }
    if (_child != null) {
      visitor(_child!);
    }
  }

  @override
  void forgetChild(Element child) {
    /// 卸载prototype
    if(_prototype == child) {
      _prototype = null;
    } else {
      _child = null;
    }
    super.forgetChild(child);
  }

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    /// 创建prototype对应的RenderObjectWidget
    _prototype = updateChild(_prototype, (widget as SliverPrototypeBuilder).prototype, _prototypeSlot);
    /// 由于真正需要渲染的child依赖prototype的尺寸,所以我们先不自动创建它,等到prototype的尺寸确定了,在通知创建
    /// 把自身引用传递给RenderObject,RenderObject可以通过这个应用通知构建真正需要渲染的child对应的RenderObjectWidget
    renderObject._element = this;
  }

  @override
  void update(SliverPrototypeBuilder newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    /// 重建prototype
    _prototype = updateChild(_prototype, newWidget.prototype, _prototypeSlot);
    /// 通知renderObject,prototype对应的RenderObjectWidget被重建了,重写执行layout过程更新
    renderObject.markNeedsLayout();
  }

  @override
  void insertRenderObjectChild(RenderObject child, Object? slot) {
    if (slot == _prototypeSlot) {
      /// 将prototype对应的RenderBox挂载到renderObject
      assert(child is RenderBox);
      renderObject.prototype = child as RenderBox;
    } else {
      /// 将真正需要渲染的child对应的RenderBox挂载到renderObject
      final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
      assert(renderObject.debugValidateChild(child));
      renderObject.child = child;
    }
  }

  @override
  void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
    /// 单子容器组件无需实现。
    assert(false);
  }

  @override
  void removeRenderObjectChild(RenderObject child, Object? slot) {
    if (slot == _prototypeSlot) {
      /// 将prototype对应的RenderBox从renderObject移除
      renderObject.prototype = null;
    } else {
      /// 将真正需要渲染的child对应的RenderBox从renderObject移除
      renderObject.child = null;
    }
  }

  /// mainAxisChildExtent:prototype在主轴方向的尺寸
  /// crossAxisChildExtent:prototype在交叉轴方向的尺寸
  /// 注:上下滚动时,主轴为垂直方向,mainAxisChildExtent代表height,crossAxisChildExtent代表width
  void _build(double mainAxisChildExtent, double crossAxisChildExtent) {
    owner!.buildScope(this, () {
      final SliverPrototypeBuilder renderObjectWidget = widget as SliverPrototypeBuilder;
      _child = updateChild(
        _child,
        renderObjectWidget.sliverBuilder(this, mainAxisChildExtent, crossAxisChildExtent),
        null,
      );
    });
  }
}

class _RenderSliverPrototypeBuilder extends RenderProxySliver {

  bool _needsUpdateChild = true;
  double? _lastMainAxisChildExtent;
  double? _lastCrossAxisChildExtent;
  _SliverPrototypeBuilderElement? _element;

  RenderBox? _prototype;
  RenderBox? get prototype => _prototype;
  set prototype(RenderBox? value) {
    if (_prototype != null) {
      dropChild(_prototype!);
    }
    _prototype = value;
    if (_prototype != null) {
      adoptChild(_prototype!);
    }
    markNeedsLayout();
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_prototype != null) {
      _prototype!.attach(owner);
    }
  }

  @override
  void detach() {
    super.detach();
    if (_prototype != null) {
      _prototype!.detach();
    }
  }

  @override
  void redepthChildren() {
    if (_prototype != null) {
      redepthChild(_prototype!);
    }
    super.redepthChildren();
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
    if (_prototype != null) {
      visitor(_prototype!);
    }
    super.visitChildren(visitor);
  }

  /// prototype尺寸确定后,
  /// 通知RenderObjectElement去创建实际需要渲染的child对应的
  /// RenderObjectWidget、RenderObjectElement、RenderObject,并把prototype的尺寸信息传递出去。
  void updateChild(Size size) {
    double mainAxisChildExtent = 0;
    double crossAxisChildExtent = 0;
    if(constraints.axis == Axis.vertical) {
      mainAxisChildExtent = size.height;
      crossAxisChildExtent = size.width;
    } else {
      mainAxisChildExtent = size.width;
      crossAxisChildExtent = size.height;
    }

    if (_needsUpdateChild || _lastMainAxisChildExtent != mainAxisChildExtent || _lastCrossAxisChildExtent != crossAxisChildExtent) {
      invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
        assert(constraints == this.constraints);
        assert(_element != null);
        _element!._build(mainAxisChildExtent, crossAxisChildExtent);
      });
      _lastMainAxisChildExtent = mainAxisChildExtent;
      _lastCrossAxisChildExtent = crossAxisChildExtent;
      _needsUpdateChild = false;
    }
  }

  @override
  void markNeedsLayout() {
    _needsUpdateChild = true;
    super.markNeedsLayout();
  }

  /// 计算prototype的尺寸信息
  Size _computePrototypeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
    if (prototype != null) {
      if (!constraints.hasTightHeight) {
        final double height = prototype!.getMaxIntrinsicHeight(constraints.maxWidth);
        assert(height.isFinite);
        constraints = constraints.tighten(height: height);
      }

      if(!constraints.hasTightWidth) {
        final double width = prototype!.getMaxIntrinsicWidth(constraints.maxHeight);
        assert(width.isFinite);
        constraints = constraints.tighten(width: width);
      }

      return layoutChild(prototype!, constraints);
    } else {
      return constraints.smallest;
    }
  }

  @override
  void performLayout() {
    /// layout prototype...
    final SliverConstraints constraints = this.constraints;
    Size prototypeSize = _computePrototypeSize(
      layoutChild: ChildLayoutHelper.layoutChild,
      constraints: constraints.asBoxConstraints(),
    );

    updateChild(prototypeSize);

    super.performLayout();
  }
}
八.例子
dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: DefaultTabController(
      initialIndex: 0,
      length: 2,
      child: CustomScrollView(
        slivers: [
          SliverPrototypeBuilder(
            prototype: Padding(
              padding: const EdgeInsets.only(bottom: 48),
              child: Image.asset(
                "assets/user.jpeg",
                fit: BoxFit.fitWidth,
              ),
            ),
            sliverBuilder: (context, mainAxisChildExtent, crossAxisChildExtent) {
              return SliverAppBar(
                pinned: true,
                collapsedHeight: kToolbarHeight,
                expandedHeight: mainAxisChildExtent - MediaQuery.paddingOf(context).top,
                centerTitle: true,
                title: Builder(builder: (context) {
                  final FlexibleSpaceBarSettings? settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
                  bool isScrolledUnder = settings?.isScrolledUnder ?? false;
                  Color? titleColor = isScrolledUnder ? Theme.of(context).appBarTheme.foregroundColor:Colors.white;
                  return Text(
                    "首页",
                    style: TextStyle(
                      color: titleColor
                    ),
                  );
                }),
                backgroundColor: MaterialStateColor.resolveWith((states) {
                  if(states.contains(MaterialState.scrolledUnder)) {
                    return Theme.of(context).appBarTheme.backgroundColor ?? Colors.white;
                  }
                  return Colors.transparent;
                }),
                flexibleSpace: FlexibleSpaceBar(
                  collapseMode: CollapseMode.pin,
                  background: InkResponse(
                    onTap: () {
                      debugPrint("click");
                    },
                    child: Padding(
                      padding: const EdgeInsets.only(bottom: 48),
                      child: Image.asset(
                        "assets/user.jpeg",
                        fit: BoxFit.fitWidth,
                      ),
                    ),
                  ),
                ),
                bottom: const TabBar(
                  tabs: [
                    Tab(
                      text: "tab1",
                    ),
                    Tab(
                      text: "tab2",
                    )
                  ],
                )
              );
            }
          ),

          SliverPadding(
            padding: const EdgeInsets.all(5),
            sliver: SliverList.list(
              children: List<Widget>.generate(10, (index) {
                return Container(
                  height: 100,
                  color: Colors.red,
                  margin: const EdgeInsets.all(5),
                  alignment: Alignment.center,
                  child: Text(
                    "item $index",
                    style: const TextStyle(
                      color: Colors.white
                    ),
                  ),
                );
              })
            ),
          )
        ],
      ),
    ),
  );
}
九.附上普通RenderBox版本源码
dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

typedef PrototypeWidgetBuilder = Widget Function(BuildContext context, double width, double height);

class PrototypeBuilder extends RenderObjectWidget {

  final Widget? prototype;
  final PrototypeWidgetBuilder builder;

  const PrototypeBuilder({
    super.key,
    this.prototype,
    required this.builder
  });

  @override
  RenderObjectElement createElement() => _PrototypeBuilderElement(this);

  @override
  RenderObject createRenderObject(BuildContext context) => _RenderPrototypeBuilder();
}

class _PrototypeBuilderElement extends RenderObjectElement {
  _PrototypeBuilderElement(PrototypeBuilder super.widget);

  @override
  _RenderPrototypeBuilder get renderObject => super.renderObject as _RenderPrototypeBuilder;

  Element? _prototype;
  static final Object _prototypeSlot = Object();
  Element? _child;

  @override
  void visitChildren(ElementVisitor visitor) {
    if (_prototype != null) {
      visitor(_prototype!);
    }
    if (_child != null) {
      visitor(_child!);
    }
  }

  @override
  void forgetChild(Element child) {
    if(_prototype == child) {
      _prototype = null;
    } else {
      _child = null;
    }
    super.forgetChild(child);
  }

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _prototype = updateChild(_prototype, (widget as PrototypeBuilder).prototype, _prototypeSlot);
    renderObject._element = this;
  }

  @override
  void update(PrototypeBuilder newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _prototype = updateChild(_prototype, newWidget.prototype, _prototypeSlot);
    renderObject.markNeedsLayout();
  }

  @override
  void insertRenderObjectChild(RenderObject child, Object? slot) {
    if (slot == _prototypeSlot) {
      assert(child is RenderBox);
      renderObject.prototype = child as RenderBox;
    } else {
      final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
      assert(renderObject.debugValidateChild(child));
      renderObject.child = child;
    }
  }

  @override
  void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
    assert(false);
  }

  @override
  void removeRenderObjectChild(RenderObject child, Object? slot) {
    if (slot == _prototypeSlot) {
      renderObject.prototype = null;
    } else {
      renderObject.child = null;
    }
  }

  void _build(double width, double height) {
    owner!.buildScope(this, () {
      final PrototypeBuilder renderObjectWidget = widget as PrototypeBuilder;
      _child = updateChild(
        _child,
        renderObjectWidget.builder(this, width, height),
        null,
      );
    });
  }
}

class _RenderPrototypeBuilder extends RenderProxyBox {

  bool _needsUpdateChild = true;
  double? _lastWidth;
  double? _lastHeight;
  _PrototypeBuilderElement? _element;

  RenderBox? _prototype;
  RenderBox? get prototype => _prototype;
  set prototype(RenderBox? value) {
    if (_prototype != null) {
      dropChild(_prototype!);
    }
    _prototype = value;
    if (_prototype != null) {
      adoptChild(_prototype!);
    }
    markNeedsLayout();
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_prototype != null) {
      _prototype!.attach(owner);
    }
  }

  @override
  void detach() {
    super.detach();
    if (_prototype != null) {
      _prototype!.detach();
    }
  }

  @override
  void redepthChildren() {
    if (_prototype != null) {
      redepthChild(_prototype!);
    }
    super.redepthChildren();
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
    if (_prototype != null) {
      visitor(_prototype!);
    }
    super.visitChildren(visitor);
  }

  void updateChild(Size size) {
    double width = size.width;
    double height = size.height;

    if (_needsUpdateChild || _lastWidth != width || _lastHeight != height) {
      invokeLayoutCallback<BoxConstraints>((BoxConstraints constraints) {
        assert(constraints == this.constraints);
        assert(_element != null);
        _element!._build(width, height);
      });
      _lastWidth = width;
      _lastHeight = height;
      _needsUpdateChild = false;
    }
  }

  @override
  void markNeedsLayout() {
    _needsUpdateChild = true;
    super.markNeedsLayout();
  }

  Size _computePrototypeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
    if (prototype != null) {
      if (!constraints.hasTightHeight) {
        final double height = prototype!.getMaxIntrinsicHeight(constraints.maxWidth);
        assert(height.isFinite);
        constraints = constraints.tighten(height: height);
      }

      if(!constraints.hasTightWidth) {
        final double width = prototype!.getMaxIntrinsicWidth(constraints.maxHeight);
        assert(width.isFinite);
        constraints = constraints.tighten(width: width);
      }

      return layoutChild(prototype!, constraints);
    } else {
      return constraints.smallest;
    }
  }

  @override
  void performLayout() {
    // layout prototype...
    Size prototypeSize = _computePrototypeSize(
      layoutChild: ChildLayoutHelper.layoutChild,
      constraints: constraints,
    );

    updateChild(prototypeSize);

    super.performLayout();
  }
}
相关推荐
用户69371750013847 小时前
315曝光AI搜索问题:GEO技术靠内容投喂操控答案,新型营销操作全揭秘
android·前端·人工智能
小蜜蜂嗡嗡7 小时前
flutter列表中实现置顶动画
flutter
进击的cc7 小时前
彻底搞懂 Binder:不止是 IPC,更是 Android 的灵魂
android·面试
段娇娇7 小时前
Android jetpack LiveData (三) 粘性数据(数据倒灌)问题分析及解决方案
android·android jetpack
用户2018792831677 小时前
TabLayout被ViewPager2遮盖部分导致Tab难选中
android
始持7 小时前
第十二讲 风格与主题统一
前端·flutter
始持7 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
法欧特斯卡雷特7 小时前
Kotlin 2.3.20 现已发布,来看看!
android·前端·后端
始持7 小时前
第十三讲 异步操作与异步构建
前端·flutter
闻哥8 小时前
深入理解 MySQL InnoDB Buffer Pool 的 LRU 冷热数据机制
android·java·jvm·spring boot·mysql·adb·面试