Allow ChangeNotifiers to be CustomPainters (#9584)
Fixes https://github.com/flutter/flutter/issues/7648
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart
index 9c95350..4845c31 100644
--- a/packages/flutter/lib/src/material/scrollbar.dart
+++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -43,25 +43,25 @@
}
class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
- _ScrollbarController _controller;
+ _ScrollbarPainter _painter;
@override
void didChangeDependencies() {
super.didChangeDependencies();
- _controller ??= new _ScrollbarController(this);
- _controller.color = Theme.of(context).highlightColor;
+ _painter ??= new _ScrollbarPainter(this);
+ _painter.color = Theme.of(context).highlightColor;
}
bool _handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification)
- _controller.update(notification.metrics, notification.metrics.axisDirection);
+ _painter.update(notification.metrics, notification.metrics.axisDirection);
return false;
}
@override
void dispose() {
- _controller.dispose();
+ _painter.dispose();
super.dispose();
}
@@ -73,7 +73,7 @@
// boundaries when the scroll bars are invisible.
child: new RepaintBoundary(
child: new CustomPaint(
- foregroundPainter: new _ScrollbarPainter(_controller),
+ foregroundPainter: _painter,
child: new RepaintBoundary(
child: widget.child,
),
@@ -83,8 +83,8 @@
}
}
-class _ScrollbarController extends ChangeNotifier {
- _ScrollbarController(TickerProvider vsync) {
+class _ScrollbarPainter extends ChangeNotifier implements CustomPainter {
+ _ScrollbarPainter(TickerProvider vsync) {
assert(vsync != null);
_fadeController = new AnimationController(duration: _kThumbFadeDuration, vsync: vsync);
_opacity = new CurvedAnimation(parent: _fadeController, curve: Curves.fastOutSlowIn)
@@ -161,6 +161,7 @@
painter(canvas, size, thumbOffset, thumbExtent);
}
+ @override
void paint(Canvas canvas, Size size) {
if (_lastAxisDirection == null || _lastMetrics == null || _opacity.value == 0.0)
return;
@@ -179,20 +180,10 @@
break;
}
}
-}
-
-class _ScrollbarPainter extends CustomPainter {
- _ScrollbarPainter(this.controller) : super(repaint: controller);
-
- final _ScrollbarController controller;
@override
- void paint(Canvas canvas, Size size) {
- controller.paint(canvas, size);
- }
+ bool hitTest(Offset position) => null;
@override
- bool shouldRepaint(_ScrollbarPainter oldDelegate) {
- return oldDelegate.controller != controller;
- }
+ bool shouldRepaint(_ScrollbarPainter oldDelegate) => false;
}
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index d7556a3..bcbb8dc 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -1788,10 +1788,10 @@
/// The interface used by [CustomPaint] (in the widgets library) and
/// [RenderCustomPaint] (in the rendering library).
///
-/// To implement a custom painter, subclass this interface to define your custom
-/// paint delegate. [CustomPaint] subclasses must implement the [paint] and
-/// [shouldRepaint] methods, and may optionally also implement the [hitTest]
-/// method.
+/// To implement a custom painter, either subclass or implement this interface
+/// to define your custom paint delegate. [CustomPaint] subclasses must
+/// implement the [paint] and [shouldRepaint] methods, and may optionally also
+/// implement the [hitTest] method.
///
/// The [paint] method is called whenever the custom object needs to be repainted.
///
@@ -1799,21 +1799,42 @@
/// is provided, to check if the new instance actually represents different
/// information.
///
-/// The most efficient way to trigger a repaint is to supply a repaint argument
-/// to the constructor of the [CustomPainter]. The custom object will listen to
-/// this animation and repaint whenever the animation ticks, avoiding both the
-/// build and layout phases of the pipeline.
+/// The most efficient way to trigger a repaint is to either extend this class
+/// and supply a `repaint` argument to the constructor of the [CustomPainter],
+/// where that object notifies its listeners when it is time to repaint, or to
+/// 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
/// 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.
///
- /// The painter will repaint whenever [repaint] notifies its listeners.
+ /// The painter will repaint whenever `repaint` notifies its listeners.
const CustomPainter({ Listenable repaint }) : _repaint = 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
/// 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.
@@ -1884,7 +1905,7 @@
bool hitTest(Offset position) => null;
@override
- String toString() => '$runtimeType#$hashCode';
+ String toString() => '$runtimeType#$hashCode(${ _repaint?.toString() ?? "" })';
}
/// Provides a canvas on which to draw during the paint phase.
@@ -1898,7 +1919,7 @@
/// those bounds, there might be insufficient memory allocated to rasterize the
/// 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
/// tree as needing a new layout during the callback (the layout for this frame
@@ -1986,8 +2007,8 @@
markNeedsPaint();
}
if (attached) {
- oldPainter?._repaint?.removeListener(markNeedsPaint);
- newPainter?._repaint?.addListener(markNeedsPaint);
+ oldPainter?.removeListener(markNeedsPaint);
+ newPainter?.addListener(markNeedsPaint);
}
}
@@ -2011,14 +2032,14 @@
@override
void attach(PipelineOwner owner) {
super.attach(owner);
- _painter?._repaint?.addListener(markNeedsPaint);
- _foregroundPainter?._repaint?.addListener(markNeedsPaint);
+ _painter?.addListener(markNeedsPaint);
+ _foregroundPainter?.addListener(markNeedsPaint);
}
@override
void detach() {
- _painter?._repaint?.removeListener(markNeedsPaint);
- _foregroundPainter?._repaint?.removeListener(markNeedsPaint);
+ _painter?.removeListener(markNeedsPaint);
+ _foregroundPainter?.removeListener(markNeedsPaint);
super.detach();
}