Added OverscrollIndicator, removed OverscrollIndicatorPainter (#3220)
* Added OverscrollIndicator, removed OverscrollIndicatorPainter
This commit is contained in:
parent
1311ae6ffe
commit
34f23cc456
@ -189,12 +189,14 @@ class ListDemoState extends State<ListDemo> {
|
||||
)
|
||||
]
|
||||
),
|
||||
body: new Scrollbar(
|
||||
child: new MaterialList(
|
||||
type: _itemType,
|
||||
scrollablePadding: new EdgeInsets.all(_dense ? 4.0 : 8.0),
|
||||
clampOverscrolls: true,
|
||||
children: listItems
|
||||
body: new OverscrollIndicator(
|
||||
child: new Scrollbar(
|
||||
child: new MaterialList(
|
||||
type: _itemType,
|
||||
scrollablePadding: new EdgeInsets.all(_dense ? 4.0 : 8.0),
|
||||
clampOverscrolls: true,
|
||||
children: listItems
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -40,7 +40,7 @@ export 'src/material/input.dart';
|
||||
export 'src/material/list.dart';
|
||||
export 'src/material/list_item.dart';
|
||||
export 'src/material/material.dart';
|
||||
export 'src/material/overscroll_painter.dart';
|
||||
export 'src/material/overscroll_indicator.dart';
|
||||
export 'src/material/page.dart';
|
||||
export 'src/material/popup_menu.dart';
|
||||
export 'src/material/progress_indicator.dart';
|
||||
|
@ -4,9 +4,6 @@
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'overscroll_painter.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
enum MaterialListType {
|
||||
oneLine,
|
||||
oneLineWithAvatar,
|
||||
@ -46,16 +43,6 @@ class MaterialList extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MaterialListState extends State<MaterialList> {
|
||||
ScrollableListPainter _overscrollPainter;
|
||||
|
||||
Color _getOverscrollIndicatorColor() => Theme.of(context).accentColor.withOpacity(0.35);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_overscrollPainter = new OverscrollPainter(getIndicatorColor: _getOverscrollIndicatorColor);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableList(
|
||||
@ -66,7 +53,6 @@ class _MaterialListState extends State<MaterialList> {
|
||||
onScroll: config.onScroll,
|
||||
itemExtent: kListItemExtent[config.type],
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0) + config.scrollablePadding,
|
||||
scrollableListPainter: config.clampOverscrolls ? _overscrollPainter : null,
|
||||
children: config.children
|
||||
);
|
||||
}
|
||||
|
209
packages/flutter/lib/src/material/overscroll_indicator.dart
Normal file
209
packages/flutter/lib/src/material/overscroll_indicator.dart
Normal file
@ -0,0 +1,209 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async' show Timer;
|
||||
|
||||
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;
|
||||
const Duration _kIndicatorHideDuration = const Duration(milliseconds: 200);
|
||||
const Duration _kIndicatorTimeoutDuration = const Duration(seconds: 1);
|
||||
final Tween<double> _kIndicatorOpacity = new Tween<double>(begin: 0.0, end: 0.3);
|
||||
|
||||
class _Painter extends CustomPainter {
|
||||
_Painter({
|
||||
this.scrollDirection,
|
||||
this.extent, // Indicator width or height, per scrollDirection.
|
||||
this.isLeading, // Similarly true if the indicator appears at the top/left.
|
||||
this.color
|
||||
});
|
||||
|
||||
final Axis scrollDirection;
|
||||
final double extent;
|
||||
final bool isLeading;
|
||||
final Color color;
|
||||
|
||||
void paintIndicator(Canvas canvas, Size size) {
|
||||
final double rectBias = extent / 2.0;
|
||||
final double arcBias = extent;
|
||||
|
||||
final Path path = new Path();
|
||||
switch(scrollDirection) {
|
||||
case Axis.vertical:
|
||||
final double width = size.width;
|
||||
if (isLeading) {
|
||||
path.moveTo(0.0, 0.0);
|
||||
path.relativeLineTo(width, 0.0);
|
||||
path.relativeLineTo(0.0, rectBias);
|
||||
path.relativeQuadraticBezierTo(width / -2.0, arcBias, -width, 0.0);
|
||||
} else {
|
||||
path.moveTo(0.0, size.height);
|
||||
path.relativeLineTo(width, 0.0);
|
||||
path.relativeLineTo(0.0, -rectBias);
|
||||
path.relativeQuadraticBezierTo(width / -2.0, -arcBias, -width, 0.0);
|
||||
}
|
||||
break;
|
||||
case Axis.horizontal:
|
||||
final double height = size.height;
|
||||
if (isLeading) {
|
||||
path.moveTo(0.0, 0.0);
|
||||
path.relativeLineTo(0.0, height);
|
||||
path.relativeLineTo(rectBias, 0.0);
|
||||
path.relativeQuadraticBezierTo(arcBias, height / -2.0, 0.0, -height);
|
||||
} else {
|
||||
path.moveTo(size.width, 0.0);
|
||||
path.relativeLineTo(0.0, height);
|
||||
path.relativeLineTo(-rectBias, 0.0);
|
||||
path.relativeQuadraticBezierTo(-arcBias, height / -2.0, 0.0, -height);
|
||||
}
|
||||
break;
|
||||
}
|
||||
path.close();
|
||||
|
||||
final Paint paint = new Paint()..color = color;
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (color.alpha == 0)
|
||||
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 {
|
||||
OverscrollIndicator({ Key key, this.scrollableKey, this.child }) : super(key: key) {
|
||||
assert(child != null);
|
||||
}
|
||||
|
||||
final Key scrollableKey;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_OverscrollIndicatorState createState() => new _OverscrollIndicatorState();
|
||||
}
|
||||
|
||||
class _OverscrollIndicatorState extends State<OverscrollIndicator> {
|
||||
final AnimationController _extentAnimation = new AnimationController(
|
||||
lowerBound: _kMinIndicatorExtent,
|
||||
upperBound: _kMaxIndicatorExtent,
|
||||
duration: _kIndicatorHideDuration
|
||||
);
|
||||
|
||||
Timer _hideTimer;
|
||||
Axis _scrollDirection;
|
||||
double _scrollOffset;
|
||||
double _minScrollOffset;
|
||||
double _maxScrollOffset;
|
||||
|
||||
void _hide() {
|
||||
_hideTimer?.cancel();
|
||||
_hideTimer = null;
|
||||
_extentAnimation.reverse();
|
||||
}
|
||||
|
||||
void _updateState(ScrollableState scrollable) {
|
||||
if (scrollable.scrollBehavior is! ExtentScrollBehavior)
|
||||
return;
|
||||
final ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
|
||||
_scrollDirection = scrollable.config.scrollDirection;
|
||||
_scrollOffset = scrollable.scrollOffset;
|
||||
_minScrollOffset = scrollBehavior.minScrollOffset;
|
||||
_maxScrollOffset = scrollBehavior.maxScrollOffset;
|
||||
}
|
||||
|
||||
void _onScrollStarted(ScrollableState scrollable) {
|
||||
_updateState(scrollable);
|
||||
}
|
||||
|
||||
void _onScrollUpdated(ScrollableState scrollable) {
|
||||
final double value = scrollable.scrollOffset;
|
||||
if ((value < _minScrollOffset || value > _maxScrollOffset) &&
|
||||
((value - _scrollOffset).abs() > kPixelScrollTolerance.distance)) {
|
||||
_hideTimer?.cancel();
|
||||
_hideTimer = new Timer(_kIndicatorTimeoutDuration, _hide);
|
||||
// Changing the animation's value causes an implicit setState().
|
||||
_extentAnimation.value = value < _minScrollOffset ? _minScrollOffset - value : value - _maxScrollOffset;
|
||||
}
|
||||
_updateState(scrollable);
|
||||
}
|
||||
|
||||
void _onScrollEnded(ScrollableState scrollable) {
|
||||
_updateState(scrollable);
|
||||
_hide();
|
||||
}
|
||||
|
||||
bool _handleScrollNotification(ScrollNotification notification) {
|
||||
if (config.scrollableKey == null || config.scrollableKey == notification.scrollable.config.key) {
|
||||
final ScrollableState scrollable = notification.scrollable;
|
||||
switch(notification.kind) {
|
||||
case ScrollNotificationKind.started:
|
||||
_onScrollStarted(scrollable);
|
||||
break;
|
||||
case ScrollNotificationKind.updated:
|
||||
_onScrollUpdated(scrollable);
|
||||
break;
|
||||
case ScrollNotificationKind.ended:
|
||||
_onScrollEnded(scrollable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hideTimer?.cancel();
|
||||
_hideTimer = null;
|
||||
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) {
|
||||
if (_scrollDirection == null) // Haven't seen a scroll yet.
|
||||
return child;
|
||||
return new CustomPaint(
|
||||
foregroundPainter: new _Painter(
|
||||
scrollDirection: _scrollDirection,
|
||||
extent: _extentAnimation.value,
|
||||
isLeading: _scrollOffset < _minScrollOffset,
|
||||
color: _indicatorColor
|
||||
),
|
||||
child: child
|
||||
);
|
||||
},
|
||||
child: config.child
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
const double _kMinIndicatorLength = 0.0;
|
||||
const double _kMaxIndicatorLength = 64.0;
|
||||
const double _kMinIndicatorOpacity = 0.0;
|
||||
const double _kMaxIndicatorOpacity = 0.25;
|
||||
const Duration _kIndicatorVanishDuration = const Duration(milliseconds: 200);
|
||||
const Duration _kIndicatorTimeoutDuration = const Duration(seconds: 1);
|
||||
final Tween<double> _kIndicatorOpacity = new Tween<double>(begin: 0.0, end: 0.3);
|
||||
|
||||
typedef Color GetOverscrollIndicatorColor();
|
||||
|
||||
class OverscrollPainter extends ScrollableListPainter {
|
||||
OverscrollPainter({ GetOverscrollIndicatorColor getIndicatorColor }) {
|
||||
this.getIndicatorColor = getIndicatorColor ?? _defaultIndicatorColor;
|
||||
}
|
||||
|
||||
GetOverscrollIndicatorColor getIndicatorColor;
|
||||
bool _indicatorActive = false;
|
||||
AnimationController _indicatorLength;
|
||||
Timer _indicatorTimer;
|
||||
|
||||
Color _defaultIndicatorColor() => const Color(0xFF00FF00);
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_indicatorLength == null || (scrollOffset >= _minScrollOffset && scrollOffset <= _maxScrollOffset))
|
||||
return;
|
||||
|
||||
final double rectBias = _indicatorLength.value / 2.0;
|
||||
final double arcBias = _indicatorLength.value;
|
||||
final Rect viewportRect = offset & viewportSize;
|
||||
|
||||
final Path path = new Path();
|
||||
switch(scrollDirection) {
|
||||
case Axis.vertical:
|
||||
final double width = viewportRect.width;
|
||||
if (scrollOffset < _minScrollOffset) {
|
||||
path.moveTo(viewportRect.left, viewportRect.top);
|
||||
path.relativeLineTo(width, 0.0);
|
||||
path.relativeLineTo(0.0, rectBias);
|
||||
path.relativeQuadraticBezierTo(width / -2.0, arcBias, -width, 0.0);
|
||||
} else {
|
||||
path.moveTo(viewportRect.left, viewportRect.bottom);
|
||||
path.relativeLineTo(width, 0.0);
|
||||
path.relativeLineTo(0.0, -rectBias);
|
||||
path.relativeQuadraticBezierTo(width / -2.0, -arcBias, -width, 0.0);
|
||||
}
|
||||
break;
|
||||
case Axis.horizontal:
|
||||
final double height = viewportRect.height;
|
||||
if (scrollOffset < _minScrollOffset) {
|
||||
path.moveTo(viewportRect.left, viewportRect.top);
|
||||
path.relativeLineTo(0.0, height);
|
||||
path.relativeLineTo(rectBias, 0.0);
|
||||
path.relativeQuadraticBezierTo(arcBias, height / -2.0, 0.0, -height);
|
||||
} else {
|
||||
path.moveTo(viewportRect.right, viewportRect.top);
|
||||
path.relativeLineTo(0.0, height);
|
||||
path.relativeLineTo(-rectBias, 0.0);
|
||||
path.relativeQuadraticBezierTo(-arcBias, height / -2.0, 0.0, -height);
|
||||
}
|
||||
break;
|
||||
}
|
||||
path.close();
|
||||
|
||||
final double t = (_indicatorLength.value - _kMinIndicatorLength) / (_kMaxIndicatorLength - _kMinIndicatorLength);
|
||||
final Paint paint = new Paint()
|
||||
..color = getIndicatorColor().withOpacity(_kIndicatorOpacity.lerp(Curves.easeIn.transform(t)));
|
||||
context.canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
void _hide() {
|
||||
_indicatorTimer?.cancel();
|
||||
_indicatorTimer = null;
|
||||
_indicatorActive = false;
|
||||
_indicatorLength?.reverse();
|
||||
}
|
||||
|
||||
double get _minScrollOffset => 0.0;
|
||||
|
||||
double get _maxScrollOffset {
|
||||
switch(scrollDirection) {
|
||||
case Axis.vertical:
|
||||
return contentExtent - viewportSize.height;
|
||||
case Axis.horizontal:
|
||||
return contentExtent - viewportSize.width;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void scrollStarted() {
|
||||
_indicatorActive = true;
|
||||
_indicatorLength ??= new AnimationController(
|
||||
lowerBound: _kMinIndicatorLength,
|
||||
upperBound: _kMaxIndicatorLength,
|
||||
duration: _kIndicatorVanishDuration
|
||||
)
|
||||
..addListener(() {
|
||||
renderObject?.markNeedsPaint();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void set scrollOffset (double value) {
|
||||
if (_indicatorActive &&
|
||||
(value < _minScrollOffset || value > _maxScrollOffset) &&
|
||||
((value - scrollOffset).abs() > kPixelScrollTolerance.distance)) {
|
||||
_indicatorTimer?.cancel();
|
||||
_indicatorTimer = new Timer(_kIndicatorTimeoutDuration, _hide);
|
||||
_indicatorLength.value = value < _minScrollOffset ? _minScrollOffset - value : value - _maxScrollOffset;
|
||||
}
|
||||
super.scrollOffset = value;
|
||||
}
|
||||
|
||||
@override
|
||||
void scrollEnded() {
|
||||
_hide();
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
super.detach();
|
||||
_indicatorTimer?.cancel();
|
||||
_indicatorTimer = null;
|
||||
_indicatorLength?.stop();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user