Commit 36f64051 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Remove OverscrollIndicator (#8134)

This feature is wired in differently in the new scrolling world.
parent 7f0b908d
...@@ -57,7 +57,6 @@ export 'src/material/list.dart'; ...@@ -57,7 +57,6 @@ export 'src/material/list.dart';
export 'src/material/list_item.dart'; export 'src/material/list_item.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/mergeable_material.dart'; export 'src/material/mergeable_material.dart';
export 'src/material/overscroll_indicator.dart';
export 'src/material/page.dart'; export 'src/material/page.dart';
export 'src/material/paginated_data_table.dart'; export 'src/material/paginated_data_table.dart';
export 'src/material/popup_menu.dart'; export 'src/material/popup_menu.dart';
......
...@@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart'; ...@@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart';
import 'arc.dart'; import 'arc.dart';
import 'colors.dart'; import 'colors.dart';
import 'overscroll_indicator.dart';
import 'page.dart'; import 'page.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -190,30 +189,6 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate { ...@@ -190,30 +189,6 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
@override @override
ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.android); ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.android);
ScrollableEdge _overscrollIndicatorEdge(ScrollableEdge edge) {
switch (edge) {
case ScrollableEdge.leading:
return ScrollableEdge.trailing;
case ScrollableEdge.trailing:
return ScrollableEdge.leading;
case ScrollableEdge.both:
return ScrollableEdge.none;
case ScrollableEdge.none:
return ScrollableEdge.both;
}
return ScrollableEdge.both;
}
@override
Widget wrapScrollWidget(BuildContext context, Widget scrollWidget) {
// Only introduce an overscroll indicator for the edges of the scrollable
// that aren't already clamped.
return new OverscrollIndicator(
edge: _overscrollIndicatorEdge(ClampOverscrolls.of(context)?.edge),
child: scrollWidget
);
}
@override @override
bool updateShouldNotify(ScrollConfigurationDelegate old) => false; bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
} }
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:async' show Timer;
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
const double _kMinIndicatorExtent = 0.0;
const double _kMaxIndicatorExtent = 64.0;
const double _kMinIndicatorOpacity = 0.0;
const double _kMaxIndicatorOpacity = 0.25;
final Tween<double> _kIndicatorOpacity = new Tween<double>(begin: 0.0, end: 0.3);
// If an overscroll gesture lasts longer than this the hide timer will
// cause the indicator to fade-out.
const Duration _kTimeoutDuration = const Duration(milliseconds: 500);
// Fade-out duration if the fade-out was triggered by the timer.
const Duration _kTimeoutHideDuration = const Duration(milliseconds: 2000);
// Fade-out duration if the fade-out was triggered by an input gesture.
const Duration _kNormalHideDuration = const Duration(milliseconds: 600);
class _Painter extends CustomPainter {
_Painter({
this.scrollDirection,
this.extent, // Indicator width or height, per scrollDirection.
this.dragPosition,
this.isLeading, // Similarly true if the indicator appears at the top/left.
this.color
});
// See EdgeEffect setSize() in https://github.com/android
static final double _kSizeToRadius = 0.75 / math.sin(math.PI / 6.0);
final Axis scrollDirection;
final double extent;
final bool isLeading;
final Color color;
final Point dragPosition;
void paintIndicator(Canvas canvas, Size size) {
final Paint paint = new Paint()..color = color;
final double width = size.width;
final double height = size.height;
switch (scrollDirection) {
case Axis.vertical:
final double radius = width * _kSizeToRadius;
final double centerX = width / 2.0;
final double centerY = isLeading ? extent - radius : height - extent + radius;
final double eventX = dragPosition?.x ?? 0.0;
final double biasX = (0.5 - (1.0 - eventX / width)) * centerX;
canvas.drawCircle(new Point(centerX + biasX, centerY), radius, paint);
break;
case Axis.horizontal:
final double radius = height * _kSizeToRadius;
final double centerX = isLeading ? extent - radius : width - extent + radius;
final double centerY = height / 2.0;
final double eventY = dragPosition?.y ?? 0.0;
final double biasY = (0.5 - (1.0 - eventY / height)) * centerY;
canvas.drawCircle(new Point(centerX, centerY + biasY), radius, paint);
break;
}
}
@override
void paint(Canvas canvas, Size size) {
if (color.alpha == 0 || size.isEmpty)
return;
paintIndicator(canvas, size);
}
@override
bool shouldRepaint(_Painter oldPainter) {
return oldPainter.scrollDirection != scrollDirection
|| oldPainter.extent != extent
|| oldPainter.isLeading != isLeading
|| oldPainter.color != color;
}
}
/// When the child's Scrollable descendant overscrolls, displays a
/// a translucent arc over the affected edge of the child.
/// If the OverscrollIndicator's child has more than one Scrollable descendant
/// the scrollableKey parameter can be used to identify the one to track.
class OverscrollIndicator extends StatefulWidget {
/// Creates an overscroll indicator.
///
/// The [child] argument must not be null.
OverscrollIndicator({
Key key,
this.scrollableKey,
this.edge: ScrollableEdge.both,
@required this.child,
}) : super(key: key) {
assert(child != null);
assert(edge != null);
}
/// Identifies the [Scrollable] descendant of child that the overscroll
/// indicator will track. Can be null if there's only one [Scrollable]
/// descendant.
final Key scrollableKey;
/// Where the overscroll indicator should appear.
final ScrollableEdge edge;
/// The overscroll indicator will be stacked on top of this child. The
/// indicator will appear when child's [Scrollable] descendant is
/// over-scrolled.
final Widget child;
@override
_OverscrollIndicatorState createState() => new _OverscrollIndicatorState();
}
class _OverscrollIndicatorState extends State<OverscrollIndicator> with SingleTickerProviderStateMixin {
AnimationController _extentAnimation;
bool _scrollUnderway = false;
Timer _hideTimer;
Axis _scrollDirection;
double _scrollOffset;
double _minScrollOffset;
double _maxScrollOffset;
Point _dragPosition;
@override
void initState() {
super.initState();
_extentAnimation = new AnimationController(
lowerBound: _kMinIndicatorExtent,
upperBound: _kMaxIndicatorExtent,
duration: _kNormalHideDuration,
vsync: this,
);
}
void _hide([Duration duration=_kTimeoutHideDuration]) {
_scrollUnderway = false;
_hideTimer?.cancel();
_hideTimer = null;
if (!_extentAnimation.isAnimating) {
_extentAnimation.duration = duration;
_extentAnimation.reverse();
}
}
void _updateState(ScrollableState scrollable) {
if (scrollable.scrollBehavior is! ExtentScrollBehavior)
return;
final ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
_scrollDirection = scrollable.config.scrollDirection;
_scrollOffset = scrollable.virtualScrollOffset;
_minScrollOffset = scrollBehavior.minScrollOffset;
_maxScrollOffset = scrollBehavior.maxScrollOffset;
}
void _onScrollStarted(ScrollableState scrollable) {
assert(_scrollUnderway == false);
_scrollUnderway = true;
_updateState(scrollable);
}
void _onScrollUpdated(ScrollableState scrollable, DragUpdateDetails details) {
if (!_scrollUnderway) // The hide timer has run.
return;
final double value = scrollable.virtualScrollOffset;
if (_isOverscroll(value)) {
_refreshHideTimer();
// Hide the indicator as soon as user starts scrolling in the reverse direction of overscroll.
if (_isReverseScroll(value)) {
_hide(_kNormalHideDuration);
} else if (_isMatchingOverscrollEdge(value)) {
// Changing the animation's value causes an implicit setState().
_dragPosition = details?.globalPosition ?? Point.origin;
_extentAnimation.value = value < _minScrollOffset ? _minScrollOffset - value : value - _maxScrollOffset;
} else {
_hide(_kNormalHideDuration);
}
}
_updateState(scrollable);
}
void _onScrollEnded(ScrollableState scrollable) {
if (!_scrollUnderway) // The hide timer has run.
return;
_updateState(scrollable);
_hide(_kNormalHideDuration);
}
void _refreshHideTimer() {
_hideTimer?.cancel();
_hideTimer = new Timer(_kTimeoutDuration, _hide);
}
bool _isOverscroll(double scrollOffset) {
return (scrollOffset < _minScrollOffset || scrollOffset > _maxScrollOffset) &&
((scrollOffset - _scrollOffset).abs() > kPixelScrollTolerance.distance);
}
bool _isMatchingOverscrollEdge(double scrollOffset) {
switch (config.edge) {
case ScrollableEdge.both:
return true;
case ScrollableEdge.leading:
return scrollOffset < _minScrollOffset;
case ScrollableEdge.trailing:
return scrollOffset > _maxScrollOffset;
case ScrollableEdge.none:
return false;
}
return false;
}
bool _isReverseScroll(double scrollOffset) {
final double delta = _scrollOffset - scrollOffset;
return scrollOffset < _minScrollOffset ? delta < 0.0 : delta > 0.0;
}
bool _handleScrollNotification(ScrollNotification notification) {
if (config.scrollableKey == null) {
if (notification.depth != 0)
return false;
} else if (config.scrollableKey != notification.scrollable.config.key) {
return false;
}
final ScrollableState scrollable = notification.scrollable;
switch (notification.kind) {
case ScrollNotificationKind.started:
_onScrollStarted(scrollable);
break;
case ScrollNotificationKind.updated:
_onScrollUpdated(scrollable, notification.dragUpdateDetails);
break;
case ScrollNotificationKind.ended:
_onScrollEnded(scrollable);
break;
}
return false;
}
@override
void dispose() {
_hideTimer?.cancel();
_hideTimer = null;
_extentAnimation.dispose();
super.dispose();
}
Color get _indicatorColor {
final Color accentColor = Theme.of(context).accentColor.withOpacity(0.35);
final double t = (_extentAnimation.value - _kMinIndicatorExtent) / (_kMaxIndicatorExtent - _kMinIndicatorExtent);
return accentColor.withOpacity(_kIndicatorOpacity.lerp(Curves.easeIn.transform(t)));
}
@override
Widget build(BuildContext context) {
return new NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: new AnimatedBuilder(
animation: _extentAnimation,
builder: (BuildContext context, Widget child) {
// We keep the same widget hierarchy here, even when we're not
// painting anything, to avoid rebuilding the children.
return new CustomPaint(
foregroundPainter: _scrollDirection == null ? null : new _Painter(
scrollDirection: _scrollDirection,
extent: _extentAnimation.value,
dragPosition: _dragPosition,
isLeading: _scrollOffset < _minScrollOffset,
color: _indicatorColor
),
child: child
);
},
child: new ClampOverscrolls.inherit(
context: context,
edge: config.edge,
child: config.child,
)
)
);
}
}
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