第 n 篇 这里假设读者已经看完了前面几篇,或者本身对 flutter 的设计、框架等比较熟悉。如果不熟悉,可以先看看前几篇文章。
进入正题……
本篇主要讲解 flutter 中,如何实现一个叶节点(没有任何子节点的节点)。flutter 代码结构本篇先不讲,下一篇会从顶层讲解到底层,方便大家学习(后续会调整文章顺序)。
节点分类 虽然不说 flutter 中的各种继承关系(这真的是 OOP 编程,通框架各种继承~),但这里会简单提一下 flutter 中组件的基本分类:
单 child 组件。及该组件有且只有一个 child。以 SingleChildRenderObjectWidget 为底层抽象类。需要子类实现:
createRenderObject (BuildContext context) 创建一个 RenderObject
updateRenderObject (BuildContext context, RenderObject renderObject) 更新旧的 RenderObject
didUnmountRenderObject 可选,当与该 Widget 关联的 RenderObject 从树中移除时的回调
多 child 组件。以 MultiChildRenderObjectWidget 为底层抽象类。同样需要子类实现上述函数。
无 child 组件。以 LeafRenderObjectWidget 为底层抽象类。同样需要子类实现上述函数。
其实从上面来看,都需要实现 createRenderObject 方法。换而言之,即是要实现对应的 RenderObject 类。今天我们就以叶节点组件来实现一个自定义渲染组件。
前置概念 既然是叶节点(无子组件),那么理所应当的不需要 layout 的相关操作(整体流程介绍放在下一篇文章)。我们只需要操作相关 paint 的方法,即绘制。
进入场景 这里假设我们要实现一个类似 flutter 原生 RangeSlider 组件(当时我们公司用的 flutter 分支是 1.5.4 的,还没有这个 RangeSlider 组件,直接拷贝不太好,索性学习着手写一个)TODO (补充图片)。开始动手吧。
Main 外层 这里省去了外层的 StatefulWidget -> State 的包裹,直接开始最关键的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class _SliderRenderObjectWidget extends LeafRenderObjectWidget { @override _RenderSlider createRenderObject(BuildContext context) { return _RenderSlider( divisions: divisions, rangeValue: startRange, ); } @override void updateRenderObject(BuildContext context, _RenderSlider renderObject) { renderObject ..rangeValue = startRange ..divisions = divisions; } }
createRenderObject 调用实际(TODO)
RenderObject 类实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class _RenderSlider extends RenderBox { _RenderSlider({ @required BdRangeSliderValue rangeValue, }) { final GestureArenaTeam team = GestureArenaTeam(); _drag = HorizontalDragGestureRecognizer() ..team = team ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd ..onCancel = _endInteraction; } @override void attach(PipelineOwner owner) { super .attach(owner); _overlayAnimation.addListener(markNeedsPaint); } @override void detach() { _overlayAnimation.removeListener(markNeedsPaint); super .detach(); } @override bool hitTestSelf(Offset position) => true ; @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { assert (debugHandleEvent(event, entry)); if (event is PointerDownEvent && isInteractive) { _drag.addPointer(event); _tap.addPointer(event); } } @override double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth; @override double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth; @override double computeMinIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight); @override double computeMaxIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight); @override bool get sizedByParent => true ; @override void performResize() { size = Size( constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth, constraints.hasBoundedHeight ? constraints.maxHeight : max(_minPreferredTrackHeight, _maxSliderPartHeight), ); } @override void paint(PaintingContext context, Offset offset) { } }