Commit 34f23cc4 authored by Hans Muller's avatar Hans Muller

Added OverscrollIndicator, removed OverscrollIndicatorPainter (#3220)

* Added OverscrollIndicator, removed OverscrollIndicatorPainter
parent 1311ae6f
......@@ -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
);
}
......
// 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
)
);
}
}
\ No newline at end of file
// 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();
}
}
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