Commit 2e194d5b authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Allow ChangeNotifiers to be CustomPainters (#9584)

Fixes https://github.com/flutter/flutter/issues/7648
parent 4e7a38b1
...@@ -43,25 +43,25 @@ class Scrollbar extends StatefulWidget { ...@@ -43,25 +43,25 @@ class Scrollbar extends StatefulWidget {
} }
class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin { class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
_ScrollbarController _controller; _ScrollbarPainter _painter;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_controller ??= new _ScrollbarController(this); _painter ??= new _ScrollbarPainter(this);
_controller.color = Theme.of(context).highlightColor; _painter.color = Theme.of(context).highlightColor;
} }
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification || if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification) notification is OverscrollNotification)
_controller.update(notification.metrics, notification.metrics.axisDirection); _painter.update(notification.metrics, notification.metrics.axisDirection);
return false; return false;
} }
@override @override
void dispose() { void dispose() {
_controller.dispose(); _painter.dispose();
super.dispose(); super.dispose();
} }
...@@ -73,7 +73,7 @@ class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin { ...@@ -73,7 +73,7 @@ class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
// boundaries when the scroll bars are invisible. // boundaries when the scroll bars are invisible.
child: new RepaintBoundary( child: new RepaintBoundary(
child: new CustomPaint( child: new CustomPaint(
foregroundPainter: new _ScrollbarPainter(_controller), foregroundPainter: _painter,
child: new RepaintBoundary( child: new RepaintBoundary(
child: widget.child, child: widget.child,
), ),
...@@ -83,8 +83,8 @@ class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin { ...@@ -83,8 +83,8 @@ class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
} }
} }
class _ScrollbarController extends ChangeNotifier { class _ScrollbarPainter extends ChangeNotifier implements CustomPainter {
_ScrollbarController(TickerProvider vsync) { _ScrollbarPainter(TickerProvider vsync) {
assert(vsync != null); assert(vsync != null);
_fadeController = new AnimationController(duration: _kThumbFadeDuration, vsync: vsync); _fadeController = new AnimationController(duration: _kThumbFadeDuration, vsync: vsync);
_opacity = new CurvedAnimation(parent: _fadeController, curve: Curves.fastOutSlowIn) _opacity = new CurvedAnimation(parent: _fadeController, curve: Curves.fastOutSlowIn)
...@@ -161,6 +161,7 @@ class _ScrollbarController extends ChangeNotifier { ...@@ -161,6 +161,7 @@ class _ScrollbarController extends ChangeNotifier {
painter(canvas, size, thumbOffset, thumbExtent); painter(canvas, size, thumbOffset, thumbExtent);
} }
@override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (_lastAxisDirection == null || _lastMetrics == null || _opacity.value == 0.0) if (_lastAxisDirection == null || _lastMetrics == null || _opacity.value == 0.0)
return; return;
...@@ -179,20 +180,10 @@ class _ScrollbarController extends ChangeNotifier { ...@@ -179,20 +180,10 @@ class _ScrollbarController extends ChangeNotifier {
break; break;
} }
} }
}
class _ScrollbarPainter extends CustomPainter {
_ScrollbarPainter(this.controller) : super(repaint: controller);
final _ScrollbarController controller;
@override @override
void paint(Canvas canvas, Size size) { bool hitTest(Offset position) => null;
controller.paint(canvas, size);
}
@override @override
bool shouldRepaint(_ScrollbarPainter oldDelegate) { bool shouldRepaint(_ScrollbarPainter oldDelegate) => false;
return oldDelegate.controller != controller;
}
} }
...@@ -1788,10 +1788,10 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -1788,10 +1788,10 @@ class RenderFractionalTranslation extends RenderProxyBox {
/// The interface used by [CustomPaint] (in the widgets library) and /// The interface used by [CustomPaint] (in the widgets library) and
/// [RenderCustomPaint] (in the rendering library). /// [RenderCustomPaint] (in the rendering library).
/// ///
/// To implement a custom painter, subclass this interface to define your custom /// To implement a custom painter, either subclass or implement this interface
/// paint delegate. [CustomPaint] subclasses must implement the [paint] and /// to define your custom paint delegate. [CustomPaint] subclasses must
/// [shouldRepaint] methods, and may optionally also implement the [hitTest] /// implement the [paint] and [shouldRepaint] methods, and may optionally also
/// method. /// implement the [hitTest] method.
/// ///
/// The [paint] method is called whenever the custom object needs to be repainted. /// The [paint] method is called whenever the custom object needs to be repainted.
/// ///
...@@ -1799,21 +1799,42 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -1799,21 +1799,42 @@ class RenderFractionalTranslation extends RenderProxyBox {
/// is provided, to check if the new instance actually represents different /// is provided, to check if the new instance actually represents different
/// information. /// information.
/// ///
/// The most efficient way to trigger a repaint is to supply a repaint argument /// The most efficient way to trigger a repaint is to either extend this class
/// to the constructor of the [CustomPainter]. The custom object will listen to /// and supply a `repaint` argument to the constructor of the [CustomPainter],
/// this animation and repaint whenever the animation ticks, avoiding both the /// where that object notifies its listeners when it is time to repaint, or to
/// build and layout phases of the pipeline. /// extend [Listenable] (e.g. via [ChangeNotifier]) and implement
/// [CustomPainter], so that the object itself provides the notifications
/// directly. In either case, the [CustomPaint] widget or [RenderCustomPaint]
/// render object will listen to the [Listenable] and repaint whenever the
/// animation ticks, avoiding both the build and layout phases of the pipeline.
/// ///
/// The [hitTest] method is called when the user interacts with the underlying /// The [hitTest] method is called when the user interacts with the underlying
/// render object, to determine if the user hit the object or missed it. /// render object, to determine if the user hit the object or missed it.
abstract class CustomPainter { abstract class CustomPainter extends Listenable {
/// Creates a custom painter. /// Creates a custom painter.
/// ///
/// The painter will repaint whenever [repaint] notifies its listeners. /// The painter will repaint whenever `repaint` notifies its listeners.
const CustomPainter({ Listenable repaint }) : _repaint = repaint; const CustomPainter({ Listenable repaint }) : _repaint = repaint;
final Listenable _repaint; final Listenable _repaint;
/// Register a closure to be notified when it is time to repaint.
///
/// The [CustomPainter] implementation merely forwards to the same method on
/// the [Listenable] provided to the constructor in the `repaint` argument, if
/// it was not null.
@override
void addListener(VoidCallback listener) => _repaint?.addListener(listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies when it is time to repaint.
///
/// The [CustomPainter] implementation merely forwards to the same method on
/// the [Listenable] provided to the constructor in the `repaint` argument, if
/// it was not null.
@override
void removeListener(VoidCallback listener) => _repaint?.removeListener(listener);
/// Called whenever the object needs to paint. The given [Canvas] has its /// Called whenever the object needs to paint. The given [Canvas] has its
/// coordinate space configured such that the origin is at the top left of the /// coordinate space configured such that the origin is at the top left of the
/// box. The area of the box is the size of the [size] argument. /// box. The area of the box is the size of the [size] argument.
...@@ -1884,7 +1905,7 @@ abstract class CustomPainter { ...@@ -1884,7 +1905,7 @@ abstract class CustomPainter {
bool hitTest(Offset position) => null; bool hitTest(Offset position) => null;
@override @override
String toString() => '$runtimeType#$hashCode'; String toString() => '$runtimeType#$hashCode(${ _repaint?.toString() ?? "" })';
} }
/// Provides a canvas on which to draw during the paint phase. /// Provides a canvas on which to draw during the paint phase.
...@@ -1898,7 +1919,7 @@ abstract class CustomPainter { ...@@ -1898,7 +1919,7 @@ abstract class CustomPainter {
/// those bounds, there might be insufficient memory allocated to rasterize the /// those bounds, there might be insufficient memory allocated to rasterize the
/// painting commands and the resulting behavior is undefined.) /// painting commands and the resulting behavior is undefined.)
/// ///
/// Painters are implemented by subclassing [CustomPainter]. /// Painters are implemented by subclassing or implementing [CustomPainter].
/// ///
/// Because custom paint calls its painters during paint, you cannot mark the /// Because custom paint calls its painters during paint, you cannot mark the
/// tree as needing a new layout during the callback (the layout for this frame /// tree as needing a new layout during the callback (the layout for this frame
...@@ -1986,8 +2007,8 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -1986,8 +2007,8 @@ class RenderCustomPaint extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
if (attached) { if (attached) {
oldPainter?._repaint?.removeListener(markNeedsPaint); oldPainter?.removeListener(markNeedsPaint);
newPainter?._repaint?.addListener(markNeedsPaint); newPainter?.addListener(markNeedsPaint);
} }
} }
...@@ -2011,14 +2032,14 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -2011,14 +2032,14 @@ class RenderCustomPaint extends RenderProxyBox {
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
_painter?._repaint?.addListener(markNeedsPaint); _painter?.addListener(markNeedsPaint);
_foregroundPainter?._repaint?.addListener(markNeedsPaint); _foregroundPainter?.addListener(markNeedsPaint);
} }
@override @override
void detach() { void detach() {
_painter?._repaint?.removeListener(markNeedsPaint); _painter?.removeListener(markNeedsPaint);
_foregroundPainter?._repaint?.removeListener(markNeedsPaint); _foregroundPainter?.removeListener(markNeedsPaint);
super.detach(); super.detach();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment