allow changing the paint offset of a GlowingOverscrollIndicator (#55829)
This commit is contained in:
parent
93d7af7314
commit
362a557365
1
AUTHORS
1
AUTHORS
@ -54,4 +54,5 @@ Cédric Wyss <cedi.wyss@gmail.com>
|
|||||||
Michel Feinstein <michel@feinstein.com.br>
|
Michel Feinstein <michel@feinstein.com.br>
|
||||||
Michael Lee <ckmichael8@gmail.com>
|
Michael Lee <ckmichael8@gmail.com>
|
||||||
Katarina Sheremet <katarina@sheremet.ch>
|
Katarina Sheremet <katarina@sheremet.ch>
|
||||||
|
Nicolas Schneider <nioncode+git@gmail.com>
|
||||||
Mikhail Zotyev <mbixjkee1392@gmail.com>
|
Mikhail Zotyev <mbixjkee1392@gmail.com>
|
||||||
|
@ -33,14 +33,53 @@ import 'ticker_provider.dart';
|
|||||||
///
|
///
|
||||||
/// In a [MaterialApp], the edge glow color is the [ThemeData.accentColor].
|
/// In a [MaterialApp], the edge glow color is the [ThemeData.accentColor].
|
||||||
///
|
///
|
||||||
|
/// ## Customizing the Glow Position for Advanced Scroll Views
|
||||||
|
///
|
||||||
/// When building a [CustomScrollView] with a [GlowingOverscrollIndicator], the
|
/// When building a [CustomScrollView] with a [GlowingOverscrollIndicator], the
|
||||||
/// indicator will apply to the entire scrollable area, regardless of what
|
/// indicator will apply to the entire scrollable area, regardless of what
|
||||||
/// slivers the CustomScrollView contains.
|
/// slivers the CustomScrollView contains.
|
||||||
///
|
///
|
||||||
/// For example, if your CustomScrollView contains a SliverAppBar in the first
|
/// For example, if your CustomScrollView contains a SliverAppBar in the first
|
||||||
/// position, the GlowingOverscrollIndicator will overlay the SliverAppBar. To
|
/// position, the GlowingOverscrollIndicator will overlay the SliverAppBar. To
|
||||||
/// manipulate the position of the GlowingOverscrollIndicator in this case, use
|
/// manipulate the position of the GlowingOverscrollIndicator in this case,
|
||||||
/// a [NestedScrollView].
|
/// you can either make use of a [NotificationListener] and provide a
|
||||||
|
/// [OverscrollIndicatorNotification.paintOffset] to the
|
||||||
|
/// notification, or use a [NestedScrollView].
|
||||||
|
///
|
||||||
|
/// {@tool dartpad --template=stateless_widget_scaffold}
|
||||||
|
///
|
||||||
|
/// This example demonstrates how to use a [NotificationListener] to manipulate
|
||||||
|
/// the placement of a [GlowingOverscrollIndicator] when building a
|
||||||
|
/// [CustomScrollView]. Drag the scrollable to see the bounds of the overscroll
|
||||||
|
/// indicator.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// double leadingPaintOffset = MediaQuery.of(context).padding.top + AppBar().preferredSize.height;
|
||||||
|
/// return NotificationListener<OverscrollIndicatorNotification>(
|
||||||
|
/// onNotification: (notification) {
|
||||||
|
/// if (notification.leading) {
|
||||||
|
/// notification.paintOffset = leadingPaintOffset;
|
||||||
|
/// }
|
||||||
|
/// return false;
|
||||||
|
/// },
|
||||||
|
/// child: CustomScrollView(
|
||||||
|
/// slivers: [
|
||||||
|
/// SliverAppBar(title: Text('Custom PaintOffset')),
|
||||||
|
/// SliverToBoxAdapter(
|
||||||
|
/// child: Container(
|
||||||
|
/// color: Colors.amberAccent,
|
||||||
|
/// height: 100,
|
||||||
|
/// child: Center(child: Text('Glow all day!')),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// SliverFillRemaining(child: FlutterLogo()),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
/// {@tool dartpad --template=stateless_widget_scaffold}
|
/// {@tool dartpad --template=stateless_widget_scaffold}
|
||||||
///
|
///
|
||||||
@ -73,6 +112,13 @@ import 'ticker_provider.dart';
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [OverscrollIndicatorNotification], which can be used to manipulate the
|
||||||
|
/// glow position or prevent the glow from being painted at all
|
||||||
|
/// * [NotificationListener], to listen for the
|
||||||
|
/// [OverscrollIndicatorNotification]
|
||||||
class GlowingOverscrollIndicator extends StatefulWidget {
|
class GlowingOverscrollIndicator extends StatefulWidget {
|
||||||
/// Creates a visual indication that a scroll view has overscrolled.
|
/// Creates a visual indication that a scroll view has overscrolled.
|
||||||
///
|
///
|
||||||
@ -199,6 +245,16 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
|
|||||||
bool _handleScrollNotification(ScrollNotification notification) {
|
bool _handleScrollNotification(ScrollNotification notification) {
|
||||||
if (!widget.notificationPredicate(notification))
|
if (!widget.notificationPredicate(notification))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Update the paint offset with the current scroll position. This makes
|
||||||
|
// sure that the glow effect correctly scrolls in line with the current
|
||||||
|
// scroll, e.g. when scrolling in the opposite direction again to hide
|
||||||
|
// the glow. Otherwise, the glow would always stay in a fixed position,
|
||||||
|
// even if the top of the content already scrolled away.
|
||||||
|
_leadingController._paintOffsetScrollPixels = -notification.metrics.pixels;
|
||||||
|
_trailingController._paintOffsetScrollPixels =
|
||||||
|
-(notification.metrics.maxScrollExtent - notification.metrics.pixels);
|
||||||
|
|
||||||
if (notification is OverscrollNotification) {
|
if (notification is OverscrollNotification) {
|
||||||
_GlowController controller;
|
_GlowController controller;
|
||||||
if (notification.overscroll < 0.0) {
|
if (notification.overscroll < 0.0) {
|
||||||
@ -213,6 +269,9 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
|
|||||||
final OverscrollIndicatorNotification confirmationNotification = OverscrollIndicatorNotification(leading: isLeading);
|
final OverscrollIndicatorNotification confirmationNotification = OverscrollIndicatorNotification(leading: isLeading);
|
||||||
confirmationNotification.dispatch(context);
|
confirmationNotification.dispatch(context);
|
||||||
_accepted[isLeading] = confirmationNotification._accepted;
|
_accepted[isLeading] = confirmationNotification._accepted;
|
||||||
|
if (_accepted[isLeading]) {
|
||||||
|
controller._paintOffset = confirmationNotification.paintOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(controller != null);
|
assert(controller != null);
|
||||||
assert(notification.metrics.axis == widget.axis);
|
assert(notification.metrics.axis == widget.axis);
|
||||||
@ -309,6 +368,8 @@ class _GlowController extends ChangeNotifier {
|
|||||||
_GlowState _state = _GlowState.idle;
|
_GlowState _state = _GlowState.idle;
|
||||||
AnimationController _glowController;
|
AnimationController _glowController;
|
||||||
Timer _pullRecedeTimer;
|
Timer _pullRecedeTimer;
|
||||||
|
double _paintOffset = 0.0;
|
||||||
|
double _paintOffsetScrollPixels = 0.0;
|
||||||
|
|
||||||
// animation values
|
// animation values
|
||||||
final Tween<double> _glowOpacityTween = Tween<double>(begin: 0.0, end: 0.0);
|
final Tween<double> _glowOpacityTween = Tween<double>(begin: 0.0, end: 0.0);
|
||||||
@ -490,6 +551,7 @@ class _GlowController extends ChangeNotifier {
|
|||||||
final Offset center = Offset((size.width / 2.0) * (0.5 + _displacement), height - radius);
|
final Offset center = Offset((size.width / 2.0) * (0.5 + _displacement), height - radius);
|
||||||
final Paint paint = Paint()..color = color.withOpacity(_glowOpacity.value);
|
final Paint paint = Paint()..color = color.withOpacity(_glowOpacity.value);
|
||||||
canvas.save();
|
canvas.save();
|
||||||
|
canvas.translate(0.0, _paintOffset + _paintOffsetScrollPixels);
|
||||||
canvas.scale(1.0, scaleY);
|
canvas.scale(1.0, scaleY);
|
||||||
canvas.clipRect(rect);
|
canvas.clipRect(rect);
|
||||||
canvas.drawCircle(center, radius, paint);
|
canvas.drawCircle(center, radius, paint);
|
||||||
@ -588,6 +650,18 @@ class OverscrollIndicatorNotification extends Notification with ViewportNotifica
|
|||||||
/// view.
|
/// view.
|
||||||
final bool leading;
|
final bool leading;
|
||||||
|
|
||||||
|
/// Controls at which offset the glow should be drawn.
|
||||||
|
///
|
||||||
|
/// A positive offset will move the glow away from its edge,
|
||||||
|
/// i.e. for a vertical, [leading] indicator, a [paintOffset] of 100.0 will
|
||||||
|
/// draw the indicator 100.0 pixels from the top of the edge.
|
||||||
|
/// For a vertical indicator with [leading] set to `false`, a [paintOffset]
|
||||||
|
/// of 100.0 will draw the indicator 100.0 pixels from the bottom instead.
|
||||||
|
///
|
||||||
|
/// A negative [paintOffset] is generally not useful, since the glow will be
|
||||||
|
/// clipped.
|
||||||
|
double paintOffset = 0.0;
|
||||||
|
|
||||||
bool _accepted = true;
|
bool _accepted = true;
|
||||||
|
|
||||||
/// Call this method if the glow should be prevented.
|
/// Call this method if the glow should be prevented.
|
||||||
|
@ -320,6 +320,68 @@ void main() {
|
|||||||
expect(painter, paints..rotate(angle: math.pi / 2.0)..circle(color: const Color(0x0A0000FF))..saveRestore());
|
expect(painter, paints..rotate(angle: math.pi / 2.0)..circle(color: const Color(0x0A0000FF))..saveRestore());
|
||||||
expect(painter, isNot(paints..circle()..circle()));
|
expect(painter, isNot(paints..circle()..circle()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Modify glow position', () {
|
||||||
|
testWidgets('Leading', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||||
|
onNotification: (OverscrollIndicatorNotification notification) {
|
||||||
|
if (notification.leading) {
|
||||||
|
notification.paintOffset = 50.0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
|
||||||
|
await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
|
||||||
|
expect(painter, paints..save()..translate(y: 50.0)..scale()..circle());
|
||||||
|
// Reverse scroll direction.
|
||||||
|
await tester.dragFrom(const Offset(200.0, 200.0), const Offset(0.0, -30.0));
|
||||||
|
await tester.pump();
|
||||||
|
// The painter should follow the scroll direction.
|
||||||
|
expect(painter, paints..save()..translate(y: 50.0 - 30.0)..scale()..circle());
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Trailing', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||||
|
onNotification: (OverscrollIndicatorNotification notification) {
|
||||||
|
if (!notification.leading) {
|
||||||
|
notification.paintOffset = 50.0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
|
||||||
|
await tester.dragFrom(const Offset(200.0, 200.0), const Offset(200.0, -10000.0));
|
||||||
|
await tester.pump();
|
||||||
|
await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, -5.0));
|
||||||
|
expect(painter, paints..scale(y: -1.0)..save()..translate(y: 50.0)..scale()..circle());
|
||||||
|
// Reverse scroll direction.
|
||||||
|
await tester.dragFrom(const Offset(200.0, 200.0), const Offset(0.0, 30.0));
|
||||||
|
await tester.pump();
|
||||||
|
// The painter should follow the scroll direction.
|
||||||
|
expect(painter, paints..scale(y: -1.0)..save()..translate(y: 50.0 - 30.0)..scale()..circle());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestScrollBehavior1 extends ScrollBehavior {
|
class TestScrollBehavior1 extends ScrollBehavior {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user