37_Flutter之Sliver组件扩展

Flutter之Sliver组件扩展

Sliver主要用于处理嵌套滑动、合并多个List的情况,像GridView、ListView自身是支持滑动的,这个时候如果再在外层嵌套一个滑动的容器组件的话,如果说GridView、ListView的滑动方向与外层可滑动容器的滑动方向一致的时候,必然会引发滑动冲突的问题,这个时候我们的外层可滑动容器可以采用CustomScrollView,并配合SliverGrid、SliverList实现,查看ListView、GridView的源码可以知道,SliverGrid、SliverList相当于是GridView、ListView去除了滚动特性,即:

SliverGrid + Scrollable = GridView

SliverList + Scrollable = ListView

接下来看一个官方的GridView的实例:

CustomScrollView(
  primary: false,
  slivers: <Widget>[
    SliverPadding(
      padding: const EdgeInsets.all(20),
      sliver: SliverGrid.count(
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
        crossAxisCount: 2,
        children: <Widget>[
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text("He'd have you all unravel at the"),
            color: Colors.green[100],
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text('Heed not the rabble'),
            color: Colors.green[200],
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text('Sound of screams but the'),
            color: Colors.green[300],
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text('Who scream'),
            color: Colors.green[400],
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text('Revolution is coming...'),
            color: Colors.green[500],
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: const Text('Revolution, they...'),
            color: Colors.green[600],
          ),
        ],
      ),
    ),
  ],
)

在这里插入图片描述

现在我们想单独为这个SliverGrid设置圆角和背景怎么办,实现下面的效果怎么办呢:

在这里插入图片描述

首先我们会想到给SliverGrid套一个Container,然后为Container指定decoration来实现,这样行不行呢,试试看就知道了

CustomScrollView(
  primary: false,
  slivers: <Widget>[
    SliverPadding(
      padding: const EdgeInsets.all(20),
      sliver: Container(
        decoration: BoxDecoration(
          color: Color(0xffff0000),
          borderRadius: BorderRadius.all(Radius.circular(20))
        ),
        child: SliverGrid.count(
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
          crossAxisCount: 2,
          children: <Widget>[
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text("He'd have you all unravel at the"),
              color: Colors.green[100],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Heed not the rabble'),
              color: Colors.green[200],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Sound of screams but the'),
              color: Colors.green[300],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Who scream'),
              color: Colors.green[400],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Revolution is coming...'),
              color: Colors.green[500],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Revolution, they...'),
              color: Colors.green[600],
            ),
          ],
        ),
      ),
    ),
  ],
),

很明显这样是不行的,这是因为一个支持Sliver的组件他的子组件对应的RenderObject必须要是一个RenderSliver对象,而Container内部在处理decoration属性的时候是通过DecoratedBox来处理的,而DecoratedBox本身对应的RenderObject是一个RenderBox对象。

在这里插入图片描述

然而google官方并没有为我们提供一个支持Sliver的DecoratedBox组件,而默认支持Sliver的组件包含SliverPadding、SliverOpacity、SliverOffstage并不能满足我们的要求,所以我们可以通过扩展一个Sliver组件,实现我们想要的效果,接下来我们参考DecoratedBox的源码来实现一个SliverDecoratedBox,从而实现图2中的效果:

1.编写SliverDecoratedBox widget

直接拷贝DecoratedBox的源码在他的基础上做改造:

1.接收子元素的成员变量名有child变成了Sliver

2.createRenderObject返回对象由RenderDecoratedBox变成RenderSliverDecoratedBox

3.updateRenderObject参数中的RenderDecoratedBox变成RenderSliverDecoratedBox

class SliverDecoratedBox extends SingleChildRenderObjectWidget {
  const SliverDecoratedBox({
    Key? key,
    required this.decoration,
    this.position = DecorationPosition.background,
    Widget? sliver,
  }) : assert(decoration != null),
        assert(position != null),
        super(key: key, child: sliver);

  final Decoration decoration;

  final DecorationPosition position;

  @override
  RenderSliverDecoratedBox createRenderObject(BuildContext context) {
    return RenderSliverDecoratedBox(
      decoration: decoration,
      position: position,
      configuration: createLocalImageConfiguration(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderSliverDecoratedBox renderObject) {
    renderObject
      ..decoration = decoration
      ..configuration = createLocalImageConfiguration(context)
      ..position = position;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    final String label;
    switch (position) {
      case DecorationPosition.background:
        label = 'bg';
        break;
      case DecorationPosition.foreground:
        label = 'fg';
        break;
    }
    properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
    properties.add(DiagnosticsProperty<Decoration>(label, decoration));
  }
}
2.实现RenderSliverDecoratedBox RenderSliver

直接拷贝RenderDecoratedBox的源码在他的基础上做改造:

1.父类由RenderProxyBox变为RenderProxySliver

2.接收子元素的成员变量类型由RenderBox变为RenderSliver

3.获取组件的大小的Size对象,不在通过从RenderBox继承的size属性获取,而是通过从RenderSliver继承的getAbsoluteSize方法获取

class RenderSliverDecoratedBox extends RenderProxySliver {
  RenderSliverDecoratedBox({
    required Decoration decoration,
    DecorationPosition position = DecorationPosition.background,
    ImageConfiguration configuration = ImageConfiguration.empty,
    RenderSliver? sliver,
  }) : assert(decoration != null),
        assert(position != null),
        assert(configuration != null),
        _decoration = decoration,
        _position = position,
        _configuration = configuration {
    child = sliver;
  }

  BoxPainter? _painter;

  Decoration get decoration => _decoration;
  Decoration _decoration;
  set decoration(Decoration value) {
    assert(value != null);
    if (value == _decoration)
      return;
    _painter?.dispose();
    _painter = null;
    _decoration = value;
    markNeedsPaint();
  }

  DecorationPosition get position => _position;
  DecorationPosition _position;
  set position(DecorationPosition value) {
    assert(value != null);
    if (value == _position)
      return;
    _position = value;
    markNeedsPaint();
  }

  ImageConfiguration get configuration => _configuration;
  ImageConfiguration _configuration;
  set configuration(ImageConfiguration value) {
    assert(value != null);
    if (value == _configuration)
      return;
    _configuration = value;
    markNeedsPaint();
  }

  @override
  void detach() {
    _painter?.dispose();
    _painter = null;
    super.detach();
    markNeedsPaint();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    Size size = getAbsoluteSize();
    assert(size.width != null);
    assert(size.height != null);

    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
    if (position == DecorationPosition.background) {
      int? debugSaveCount;
      assert(() {
        debugSaveCount = context.canvas.getSaveCount();
        return true;
      }());
      _painter!.paint(context.canvas, offset, filledConfiguration);
      assert(() {
        if (debugSaveCount != context.canvas.getSaveCount()) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('${_decoration.runtimeType} painter had mismatching save and restore calls.'),
            ErrorDescription(
              'Before painting the decoration, the canvas save count was $debugSaveCount. '
                  'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. '
                  'Every call to save() or saveLayer() must be matched by a call to restore().',
            ),
            DiagnosticsProperty<Decoration>('The decoration was', decoration, style: DiagnosticsTreeStyle.errorProperty),
            DiagnosticsProperty<BoxPainter>('The painter was', _painter, style: DiagnosticsTreeStyle.errorProperty),
          ]);
        }
        return true;
      }());
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
    super.paint(context, offset);
    if (position == DecorationPosition.foreground) {
      _painter!.paint(context.canvas, offset, filledConfiguration);
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
    properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
  }
}
4.处理child对应的圆角
class RenderSliverDecoratedBox extends RenderProxySliver {

  ...

  @override
  void paint(PaintingContext context, Offset offset) {
    ...

    if(decoration is BoxDecoration) {
      BorderRadiusGeometry? borderRadius = (decoration as BoxDecoration).borderRadius;
      if(borderRadius != null) {
        RRect clipRect = borderRadius.resolve(configuration.textDirection).toRRect(Rect.fromLTRB(
            0, 0, constraints.crossAxisExtent, geometry!.maxPaintExtent));
        context.pushClipRRect(
          needsCompositing,
          offset,
          clipRect.outerRect,
          clipRect,
          super.paint,
        );
      }
    }
  }

  ...
  
}

到此一个支持Sliver的DecoratedBox就编写完了,接下来我们在使用的时候只需要将上面的Container替换成SliverDecoratedBox即可,这里只是以DecoratedBox为例来说明,我们怎么参考系统组件的源码来扩展一个支持Sliver的对应的组件。

CustomScrollView(
  primary: false,
  slivers: <Widget>[
    SliverPadding(
      padding: const EdgeInsets.all(20),
      sliver: SliverDecoratedBox(
        decoration: BoxDecoration(
          color: Color(0xffff0000),
          borderRadius: BorderRadius.all(Radius.circular(20))
        ),
        sliver: SliverGrid.count(
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
          crossAxisCount: 2,
          children: <Widget>[
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text("He'd have you all unravel at the"),
              color: Colors.green[100],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Heed not the rabble'),
              color: Colors.green[200],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Sound of screams but the'),
              color: Colors.green[300],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Who scream'),
              color: Colors.green[400],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Revolution is coming...'),
              color: Colors.green[500],
            ),
            Container(
              padding: const EdgeInsets.all(8),
              child: const Text('Revolution, they...'),
              color: Colors.green[600],
            ),
          ],
        ),
      ),
    ),
  ],
),
5.完整源码
class SliverDecoratedBox extends SingleChildRenderObjectWidget {
  const SliverDecoratedBox({
    Key? key,
    required this.decoration,
    this.position = DecorationPosition.background,
    Widget? sliver,
  }) : assert(decoration != null),
        assert(position != null),
        super(key: key, child: sliver);

  final Decoration decoration;

  final DecorationPosition position;

  @override
  RenderSliverDecoratedBox createRenderObject(BuildContext context) {
    return RenderSliverDecoratedBox(
      decoration: decoration,
      position: position,
      configuration: createLocalImageConfiguration(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderSliverDecoratedBox renderObject) {
    renderObject
      ..decoration = decoration
      ..configuration = createLocalImageConfiguration(context)
      ..position = position;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    final String label;
    switch (position) {
      case DecorationPosition.background:
        label = 'bg';
        break;
      case DecorationPosition.foreground:
        label = 'fg';
        break;
    }
    properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
    properties.add(DiagnosticsProperty<Decoration>(label, decoration));
  }
}

class RenderSliverDecoratedBox extends RenderProxySliver {
  RenderSliverDecoratedBox({
    required Decoration decoration,
    DecorationPosition position = DecorationPosition.background,
    ImageConfiguration configuration = ImageConfiguration.empty,
    RenderSliver? sliver,
  }) : assert(decoration != null),
        assert(position != null),
        assert(configuration != null),
        _decoration = decoration,
        _position = position,
        _configuration = configuration {
    child = sliver;
  }

  BoxPainter? _painter;

  Decoration get decoration => _decoration;
  Decoration _decoration;
  set decoration(Decoration value) {
    assert(value != null);
    if (value == _decoration)
      return;
    _painter?.dispose();
    _painter = null;
    _decoration = value;
    markNeedsPaint();
  }

  DecorationPosition get position => _position;
  DecorationPosition _position;
  set position(DecorationPosition value) {
    assert(value != null);
    if (value == _position)
      return;
    _position = value;
    markNeedsPaint();
  }

  ImageConfiguration get configuration => _configuration;
  ImageConfiguration _configuration;
  set configuration(ImageConfiguration value) {
    assert(value != null);
    if (value == _configuration)
      return;
    _configuration = value;
    markNeedsPaint();
  }

  @override
  void detach() {
    _painter?.dispose();
    _painter = null;
    super.detach();
    markNeedsPaint();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    Size size = getAbsoluteSize();
    assert(size.width != null);
    assert(size.height != null);

    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
    if (position == DecorationPosition.background) {
      int? debugSaveCount;
      assert(() {
        debugSaveCount = context.canvas.getSaveCount();
        return true;
      }());
      _painter!.paint(context.canvas, offset, filledConfiguration);
      assert(() {
        if (debugSaveCount != context.canvas.getSaveCount()) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('${_decoration.runtimeType} painter had mismatching save and restore calls.'),
            ErrorDescription(
              'Before painting the decoration, the canvas save count was $debugSaveCount. '
                  'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. '
                  'Every call to save() or saveLayer() must be matched by a call to restore().',
            ),
            DiagnosticsProperty<Decoration>('The decoration was', decoration, style: DiagnosticsTreeStyle.errorProperty),
            DiagnosticsProperty<BoxPainter>('The painter was', _painter, style: DiagnosticsTreeStyle.errorProperty),
          ]);
        }
        return true;
      }());
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
    super.paint(context, offset);
    if (position == DecorationPosition.foreground) {
      _painter!.paint(context.canvas, offset, filledConfiguration);
      if (decoration.isComplex)
        context.setIsComplexHint();
    }

    if(decoration is BoxDecoration) {
      BorderRadiusGeometry? borderRadius = (decoration as BoxDecoration).borderRadius;
      if(borderRadius != null) {
        RRect clipRect = borderRadius.resolve(configuration.textDirection).toRRect(Rect.fromLTRB(
            0, 0, constraints.crossAxisExtent, geometry!.maxPaintExtent));
        context.pushClipRRect(
          needsCompositing,
          offset,
          clipRect.outerRect,
          clipRect,
          super.paint,
        );
      }
    }
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
    properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
  }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值