blob: de7446931072a787c0d09b0a8a1c95075eaffdc4 [file] [log] [blame]
// 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/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
const double _kScrollbarThickness = 6.0;
const Duration _kScrollbarFadeDuration = const Duration(milliseconds: 300);
const Duration _kScrollbarTimeToFade = const Duration(milliseconds: 600);
/// A material design scrollbar.
///
/// A scrollbar indicates which portion of a [Scrollable] widget is actually
/// visible.
///
/// Dynamically changes to a iOS style scrollbar that looks like
/// [CupertinoScrollbar] on iOS platform.
///
/// To add a scrollbar to a [ScrollView], simply wrap the scroll view widget in
/// a [Scrollbar] widget.
///
/// See also:
///
/// * [ListView], which display a linear, scrollable list of children.
/// * [GridView], which display a 2 dimensional, scrollable array of children.
class Scrollbar extends StatefulWidget {
/// Creates a material design scrollbar that wraps the given [child].
///
/// The [child] should be a source of [ScrollNotification] notifications,
/// typically a [Scrollable] widget.
const Scrollbar({
Key key,
@required this.child,
}) : super(key: key);
/// The widget below this widget in the tree.
///
/// The scrollbar will be stacked on top of this child. This child (and its
/// subtree) should include a source of [ScrollNotification] notifications.
///
/// Typically a [ListView] or [CustomScrollView].
final Widget child;
@override
_ScrollbarState createState() => new _ScrollbarState();
}
class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
ScrollbarPainter _materialPainter;
TargetPlatform _currentPlatform;
TextDirection _textDirection;
Color _themeColor;
AnimationController _fadeoutAnimationController;
Animation<double> _fadeoutOpacityAnimation;
Timer _fadeoutTimer;
@override
void initState() {
super.initState();
_fadeoutAnimationController = new AnimationController(
vsync: this,
duration: _kScrollbarFadeDuration,
);
_fadeoutOpacityAnimation = new CurvedAnimation(
parent: _fadeoutAnimationController,
curve: Curves.fastOutSlowIn
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ThemeData theme = Theme.of(context);
_currentPlatform = theme.platform;
switch (_currentPlatform) {
case TargetPlatform.iOS:
// On iOS, stop all local animations. CupertinoScrollbar has its own
// animations.
_fadeoutTimer?.cancel();
_fadeoutTimer = null;
_fadeoutAnimationController.reset();
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
_themeColor = theme.highlightColor.withOpacity(1.0);
_textDirection = Directionality.of(context);
_materialPainter = _buildMaterialScrollbarPainter();
break;
}
}
ScrollbarPainter _buildMaterialScrollbarPainter() {
return new ScrollbarPainter(
color: _themeColor,
textDirection: _textDirection,
thickness: _kScrollbarThickness,
fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
);
}
bool _handleScrollNotification(ScrollNotification notification) {
// iOS sub-delegates to the CupertinoScrollbar instead and doesn't handle
// scroll notifications here.
if (_currentPlatform != TargetPlatform.iOS
&& (notification is ScrollUpdateNotification
|| notification is OverscrollNotification)) {
if (_fadeoutAnimationController.status != AnimationStatus.forward) {
_fadeoutAnimationController.forward();
}
_materialPainter.update(notification.metrics, notification.metrics.axisDirection);
_fadeoutTimer?.cancel();
_fadeoutTimer = new Timer(_kScrollbarTimeToFade, () {
_fadeoutAnimationController.reverse();
_fadeoutTimer = null;
});
}
return false;
}
@override
void dispose() {
_fadeoutAnimationController.dispose();
_fadeoutTimer?.cancel();
_materialPainter?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
switch (_currentPlatform) {
case TargetPlatform.iOS:
return new CupertinoScrollbar(
child: widget.child,
);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return new NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: new RepaintBoundary(
child: new CustomPaint(
foregroundPainter: _materialPainter,
child: new RepaintBoundary(
child: widget.child,
),
),
),
);
}
throw new FlutterError('Unknown platform for scrollbar insertion');
}
}