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>
|
||||
Michael Lee <ckmichael8@gmail.com>
|
||||
Katarina Sheremet <katarina@sheremet.ch>
|
||||
Nicolas Schneider <nioncode+git@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].
|
||||
///
|
||||
/// ## Customizing the Glow Position for Advanced Scroll Views
|
||||
///
|
||||
/// When building a [CustomScrollView] with a [GlowingOverscrollIndicator], the
|
||||
/// indicator will apply to the entire scrollable area, regardless of what
|
||||
/// slivers the CustomScrollView contains.
|
||||
///
|
||||
/// For example, if your CustomScrollView contains a SliverAppBar in the first
|
||||
/// position, the GlowingOverscrollIndicator will overlay the SliverAppBar. To
|
||||
/// manipulate the position of the GlowingOverscrollIndicator in this case, use
|
||||
/// a [NestedScrollView].
|
||||
/// manipulate the position of the GlowingOverscrollIndicator in this case,
|
||||
/// 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}
|
||||
///
|
||||
@ -73,6 +112,13 @@ import 'ticker_provider.dart';
|
||||
/// }
|
||||
/// ```
|
||||
/// {@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 {
|
||||
/// Creates a visual indication that a scroll view has overscrolled.
|
||||
///
|
||||
@ -199,6 +245,16 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
|
||||
bool _handleScrollNotification(ScrollNotification notification) {
|
||||
if (!widget.notificationPredicate(notification))
|
||||
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) {
|
||||
_GlowController controller;
|
||||
if (notification.overscroll < 0.0) {
|
||||
@ -213,6 +269,9 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
|
||||
final OverscrollIndicatorNotification confirmationNotification = OverscrollIndicatorNotification(leading: isLeading);
|
||||
confirmationNotification.dispatch(context);
|
||||
_accepted[isLeading] = confirmationNotification._accepted;
|
||||
if (_accepted[isLeading]) {
|
||||
controller._paintOffset = confirmationNotification.paintOffset;
|
||||
}
|
||||
}
|
||||
assert(controller != null);
|
||||
assert(notification.metrics.axis == widget.axis);
|
||||
@ -309,6 +368,8 @@ class _GlowController extends ChangeNotifier {
|
||||
_GlowState _state = _GlowState.idle;
|
||||
AnimationController _glowController;
|
||||
Timer _pullRecedeTimer;
|
||||
double _paintOffset = 0.0;
|
||||
double _paintOffsetScrollPixels = 0.0;
|
||||
|
||||
// animation values
|
||||
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 Paint paint = Paint()..color = color.withOpacity(_glowOpacity.value);
|
||||
canvas.save();
|
||||
canvas.translate(0.0, _paintOffset + _paintOffsetScrollPixels);
|
||||
canvas.scale(1.0, scaleY);
|
||||
canvas.clipRect(rect);
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
@ -588,6 +650,18 @@ class OverscrollIndicatorNotification extends Notification with ViewportNotifica
|
||||
/// view.
|
||||
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;
|
||||
|
||||
/// 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, 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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user