| // Copyright 2017 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'; |
| |
| // All values eyeballed. |
| const Color _kScrollbarColor = Color(0x99777777); |
| const double _kScrollbarThickness = 2.5; |
| const double _kScrollbarMainAxisMargin = 4.0; |
| const double _kScrollbarCrossAxisMargin = 2.5; |
| const double _kScrollbarMinLength = 36.0; |
| const double _kScrollbarMinOverscrollLength = 8.0; |
| const Radius _kScrollbarRadius = Radius.circular(1.25); |
| const Duration _kScrollbarTimeToFade = Duration(milliseconds: 50); |
| const Duration _kScrollbarFadeDuration = Duration(milliseconds: 250); |
| |
| /// An iOS style scrollbar. |
| /// |
| /// A scrollbar indicates which portion of a [Scrollable] widget is actually |
| /// visible. |
| /// |
| /// To add a scrollbar to a [ScrollView], simply wrap the scroll view widget in |
| /// a [CupertinoScrollbar] widget. |
| /// |
| /// See also: |
| /// |
| /// * [ListView], which display a linear, scrollable list of children. |
| /// * [GridView], which display a 2 dimensional, scrollable array of children. |
| /// * [Scrollbar], a Material Design scrollbar that dynamically adapts to the |
| /// platform showing either an Android style or iOS style scrollbar. |
| class CupertinoScrollbar extends StatefulWidget { |
| /// Creates an iOS style scrollbar that wraps the given [child]. |
| /// |
| /// The [child] should be a source of [ScrollNotification] notifications, |
| /// typically a [Scrollable] widget. |
| const CupertinoScrollbar({ |
| Key key, |
| @required this.child, |
| }) : super(key: key); |
| |
| /// The subtree to place inside the [CupertinoScrollbar]. |
| /// |
| /// This should include a source of [ScrollNotification] notifications, |
| /// typically a [Scrollable] widget. |
| final Widget child; |
| |
| @override |
| _CupertinoScrollbarState createState() => _CupertinoScrollbarState(); |
| } |
| |
| class _CupertinoScrollbarState extends State<CupertinoScrollbar> with TickerProviderStateMixin { |
| ScrollbarPainter _painter; |
| TextDirection _textDirection; |
| |
| AnimationController _fadeoutAnimationController; |
| Animation<double> _fadeoutOpacityAnimation; |
| Timer _fadeoutTimer; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _fadeoutAnimationController = AnimationController( |
| vsync: this, |
| duration: _kScrollbarFadeDuration, |
| ); |
| _fadeoutOpacityAnimation = CurvedAnimation( |
| parent: _fadeoutAnimationController, |
| curve: Curves.fastOutSlowIn |
| ); |
| } |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| _textDirection = Directionality.of(context); |
| _painter = _buildCupertinoScrollbarPainter(); |
| } |
| |
| /// Returns a [ScrollbarPainter] visually styled like the iOS scrollbar. |
| ScrollbarPainter _buildCupertinoScrollbarPainter() { |
| return ScrollbarPainter( |
| color: _kScrollbarColor, |
| textDirection: _textDirection, |
| thickness: _kScrollbarThickness, |
| fadeoutOpacityAnimation: _fadeoutOpacityAnimation, |
| mainAxisMargin: _kScrollbarMainAxisMargin, |
| crossAxisMargin: _kScrollbarCrossAxisMargin, |
| radius: _kScrollbarRadius, |
| minLength: _kScrollbarMinLength, |
| minOverscrollLength: _kScrollbarMinOverscrollLength, |
| ); |
| } |
| |
| bool _handleScrollNotification(ScrollNotification notification) { |
| if (notification is ScrollUpdateNotification || |
| notification is OverscrollNotification) { |
| // Any movements always makes the scrollbar start showing up. |
| if (_fadeoutAnimationController.status != AnimationStatus.forward) { |
| _fadeoutAnimationController.forward(); |
| } |
| |
| _fadeoutTimer?.cancel(); |
| _painter.update(notification.metrics, notification.metrics.axisDirection); |
| } else if (notification is ScrollEndNotification) { |
| // On iOS, the scrollbar can only go away once the user lifted the finger. |
| |
| _fadeoutTimer?.cancel(); |
| _fadeoutTimer = Timer(_kScrollbarTimeToFade, () { |
| _fadeoutAnimationController.reverse(); |
| _fadeoutTimer = null; |
| }); |
| } |
| return false; |
| } |
| |
| @override |
| void dispose() { |
| _fadeoutAnimationController.dispose(); |
| _fadeoutTimer?.cancel(); |
| _painter.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return NotificationListener<ScrollNotification>( |
| onNotification: _handleScrollNotification, |
| child: RepaintBoundary( |
| child: CustomPaint( |
| foregroundPainter: _painter, |
| child: RepaintBoundary( |
| child: widget.child, |
| ), |
| ), |
| ), |
| ); |
| } |
| } |