赞
踩
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中的效果:
直接拷贝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)); } }
直接拷贝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)); } }
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], ), ], ), ), ), ], ),
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)); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。