do not show scrollbar thumb for non scrollable views

This commit is contained in:
Thibault Deckers 2021-01-20 10:55:00 +09:00
parent c840190aec
commit 695f8de2a4
8 changed files with 409 additions and 39 deletions

View file

@ -89,12 +89,6 @@ class Constants {
licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE',
sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon',
),
Dependency(
name: 'Draggable Scrollbar',
license: 'MIT',
licenseUrl: 'https://github.com/fluttercommunity/flutter-draggable-scrollbar/blob/master/LICENSE',
sourceUrl: 'https://github.com/fluttercommunity/flutter-draggable-scrollbar',
),
Dependency(
name: 'Event Bus',
license: 'MIT',

View file

@ -16,6 +16,7 @@ import 'package:aves/widgets/collection/grid/section_layout.dart';
import 'package:aves/widgets/collection/grid/selector.dart';
import 'package:aves/widgets/collection/grid/thumbnail.dart';
import 'package:aves/widgets/collection/thumbnail/decorated.dart';
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -26,7 +27,6 @@ import 'package:aves/widgets/common/identity/scroll_thumb.dart';
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
import 'package:aves/widgets/common/scaling.dart';
import 'package:aves/widgets/common/tile_extent_manager.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
@ -237,8 +237,8 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
selector: (context, mq) => mq.effectiveBottomPadding,
builder: (context, mqPaddingBottom, child) => DraggableScrollbar(
heightScrollThumb: avesScrollThumbHeight,
backgroundColor: Colors.white,
scrollThumbHeight: avesScrollThumbHeight,
scrollThumbBuilder: avesScrollThumbBuilder(
height: avesScrollThumbHeight,
backgroundColor: Colors.white,

View file

@ -0,0 +1,388 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/*
This is derived from `draggable_scrollbar` package v0.0.4:
- removed default thumb builders
- allow any `ScrollView` as child
- allow any `Widget` as label content
- moved out constraints responsibility
- various extent & thumb positioning fixes
*/
/// Build the Scroll Thumb and label using the current configuration
typedef ScrollThumbBuilder = Widget Function(
Color backgroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
Widget labelText,
});
/// Build a Text widget using the current scroll offset
typedef LabelTextBuilder = Widget Function(double offsetY);
/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
/// for quick navigation of the BoxScrollView.
class DraggableScrollbar extends StatefulWidget {
/// The background color of the label and thumb
final Color backgroundColor;
/// The height of the scroll thumb
final double scrollThumbHeight;
/// A function that builds a thumb using the current configuration
final ScrollThumbBuilder scrollThumbBuilder;
/// The amount of padding that should surround the thumb
final EdgeInsetsGeometry padding;
/// Determines how quickly the scrollbar will animate in and out
final Duration scrollbarAnimationDuration;
/// How long should the thumb be visible before fading out
final Duration scrollbarTimeToFade;
/// Build a Text widget from the current offset in the BoxScrollView
final LabelTextBuilder labelTextBuilder;
/// The ScrollController for the BoxScrollView
final ScrollController controller;
/// The view that will be scrolled with the scroll thumb
final ScrollView child;
DraggableScrollbar({
Key key,
@required this.backgroundColor,
@required this.scrollThumbHeight,
@required this.scrollThumbBuilder,
@required this.controller,
this.padding,
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
this.labelTextBuilder,
@required this.child,
}) : assert(controller != null),
assert(scrollThumbBuilder != null),
assert(child.scrollDirection == Axis.vertical),
super(key: key);
@override
_DraggableScrollbarState createState() => _DraggableScrollbarState();
static Widget buildScrollThumbAndLabel({
@required Widget scrollThumb,
@required Color backgroundColor,
@required Animation<double> thumbAnimation,
@required Animation<double> labelAnimation,
@required Widget labelText,
}) {
final scrollThumbAndLabel = labelText == null
? scrollThumb
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
ScrollLabel(
animation: labelAnimation,
child: labelText,
backgroundColor: backgroundColor,
),
scrollThumb,
],
);
return SlideFadeTransition(
animation: thumbAnimation,
child: scrollThumbAndLabel,
);
}
}
class ScrollLabel extends StatelessWidget {
final Animation<double> animation;
final Color backgroundColor;
final Widget child;
const ScrollLabel({
Key key,
@required this.child,
@required this.animation,
@required this.backgroundColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animation,
child: Container(
margin: EdgeInsets.only(right: 12.0),
child: Material(
elevation: 4.0,
color: backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(16.0)),
child: child,
),
),
);
}
}
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0);
bool _isDragInProcess = false;
AnimationController _thumbAnimationController;
Animation<double> _thumbAnimation;
AnimationController _labelAnimationController;
Animation<double> _labelAnimation;
Timer _fadeoutTimer;
@override
void initState() {
super.initState();
_thumbAnimationController = AnimationController(
vsync: this,
duration: widget.scrollbarAnimationDuration,
);
_thumbAnimation = CurvedAnimation(
parent: _thumbAnimationController,
curve: Curves.fastOutSlowIn,
);
_labelAnimationController = AnimationController(
vsync: this,
duration: widget.scrollbarAnimationDuration,
);
_labelAnimation = CurvedAnimation(
parent: _labelAnimationController,
curve: Curves.fastOutSlowIn,
);
}
@override
void dispose() {
_thumbAnimationController.dispose();
_labelAnimationController.dispose();
_fadeoutTimer?.cancel();
super.dispose();
}
ScrollController get controller => widget.controller;
double get thumbMaxScrollExtent => context.size.height - widget.scrollThumbHeight - (widget.padding?.vertical ?? 0.0);
double get thumbMinScrollExtent => 0.0;
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
_onScrollNotification(notification);
return false;
},
child: Stack(
children: [
RepaintBoundary(
child: widget.child,
),
RepaintBoundary(
child: GestureDetector(
onVerticalDragStart: _onVerticalDragStart,
onVerticalDragUpdate: _onVerticalDragUpdate,
onVerticalDragEnd: _onVerticalDragEnd,
child: ValueListenableBuilder(
valueListenable: _thumbOffsetNotifier,
builder: (context, thumbOffset, child) => Container(
alignment: AlignmentDirectional.topEnd,
padding: EdgeInsets.only(top: thumbOffset) + widget.padding,
child: widget.scrollThumbBuilder(
widget.backgroundColor,
_thumbAnimation,
_labelAnimation,
widget.scrollThumbHeight,
labelText: (widget.labelTextBuilder != null && _isDragInProcess)
? ValueListenableBuilder(
valueListenable: _viewOffsetNotifier,
builder: (context, viewOffset, child) => widget.labelTextBuilder(viewOffset + thumbOffset),
)
: null,
),
),
),
),
),
],
),
);
}
void _onScrollNotification(ScrollNotification notification) {
final scrollMetrics = notification.metrics;
// do not update the thumb if we cannot actually scroll
if (scrollMetrics.minScrollExtent >= scrollMetrics.maxScrollExtent) return;
_viewOffsetNotifier.value = scrollMetrics.pixels;
// we update the thumb position from the scrolled offset
// when the user is not dragging the thumb
if (!_isDragInProcess) {
if (notification is ScrollUpdateNotification) {
_thumbOffsetNotifier.value = (scrollMetrics.pixels / scrollMetrics.maxScrollExtent * thumbMaxScrollExtent).clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
}
if (notification is ScrollUpdateNotification || notification is OverscrollNotification) {
_showThumb();
_scheduleFadeout();
}
}
}
void _onVerticalDragStart(DragStartDetails details) {
_labelAnimationController.forward();
_fadeoutTimer?.cancel();
setState(() => _isDragInProcess = true);
}
void _onVerticalDragUpdate(DragUpdateDetails details) {
_showThumb();
if (_isDragInProcess) {
// thumb offset
_thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + details.delta.dy).clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
// scroll offset
final min = controller.position.minScrollExtent;
final max = controller.position.maxScrollExtent;
controller.jumpTo((_thumbOffsetNotifier.value / thumbMaxScrollExtent * max).clamp(min, max));
}
}
void _onVerticalDragEnd(DragEndDetails details) {
_scheduleFadeout();
setState(() => _isDragInProcess = false);
}
void _showThumb() {
if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward();
}
}
void _scheduleFadeout() {
_fadeoutTimer?.cancel();
_fadeoutTimer = Timer(widget.scrollbarTimeToFade, () {
_thumbAnimationController.reverse();
_labelAnimationController.reverse();
_fadeoutTimer = null;
});
}
}
/// Draws 2 triangles like arrow up and arrow down
class ArrowCustomPainter extends CustomPainter {
Color color;
ArrowCustomPainter(this.color);
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
const width = 12.0;
const height = 8.0;
final baseX = size.width / 2;
final baseY = size.height / 2;
canvas.drawPath(
_trianglePath(Offset(baseX, baseY - 2.0), width, height, true),
paint,
);
canvas.drawPath(
_trianglePath(Offset(baseX, baseY + 2.0), width, height, false),
paint,
);
}
static Path _trianglePath(Offset o, double width, double height, bool isUp) {
return Path()
..moveTo(o.dx, o.dy)
..lineTo(o.dx + width, o.dy)
..lineTo(o.dx + (width / 2), isUp ? o.dy - height : o.dy + height)
..close();
}
}
///This cut 2 lines in arrow shape
class ArrowClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0.0);
path.lineTo(0.0, 0.0);
path.close();
const arrowWidth = 8.0;
final startPointX = (size.width - arrowWidth) / 2;
var startPointY = size.height / 2 - arrowWidth / 2;
path.moveTo(startPointX, startPointY);
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2);
path.lineTo(startPointX + arrowWidth, startPointY);
path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
path.lineTo(startPointX, startPointY + 1.0);
path.close();
startPointY = size.height / 2 + arrowWidth / 2;
path.moveTo(startPointX + arrowWidth, startPointY);
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2);
path.lineTo(startPointX, startPointY);
path.lineTo(startPointX, startPointY - 1.0);
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
class SlideFadeTransition extends StatelessWidget {
final Animation<double> animation;
final Widget child;
const SlideFadeTransition({
Key key,
@required this.animation,
@required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) => animation.value == 0.0 ? Container() : child,
child: SlideTransition(
position: Tween(
begin: Offset(0.3, 0.0),
end: Offset(0.0, 0.0),
).animate(animation),
child: FadeTransition(
opacity: animation,
child: child,
),
),
);
}
}

View file

@ -1,4 +1,4 @@
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
const double avesScrollThumbHeight = 48;
@ -35,7 +35,6 @@ ScrollThumbBuilder avesScrollThumbBuilder({
thumbAnimation: thumbAnimation,
labelAnimation: labelAnimation,
labelText: labelText,
alwaysVisibleScrollThumb: false,
);
};
}

View file

@ -6,16 +6,18 @@ import 'package:aves/widgets/common/magnifier/scale/scale_level.dart';
import 'package:aves/widgets/common/magnifier/scale/state.dart';
import 'package:flutter/material.dart';
/// `Magnifier` is derived from `photo_view` package v0.9.2:
/// - removed image related aspects to focus on a general purpose pan/scale viewer (à la `InteractiveViewer`)
/// - removed rotation and many customization parameters
/// - removed ignorable/ignoring partial notifiers
/// - formatted, renamed and reorganized
/// - fixed gesture recognizers when used inside a scrollable widget like `PageView`
/// - fixed corner hit detection when in containers scrollable in both axes
/// - fixed corner hit detection issues due to imprecise double comparisons
/// - added single & double tap position feedback
/// - fixed focus when scaling by double-tap/pinch
/*
`Magnifier` is derived from `photo_view` package v0.9.2:
- removed image related aspects to focus on a general purpose pan/scale viewer (à la `InteractiveViewer`)
- removed rotation and many customization parameters
- removed ignorable/ignoring partial notifiers
- formatted, renamed and reorganized
- fixed gesture recognizers when used inside a scrollable widget like `PageView`
- fixed corner hit detection when in containers scrollable in both axes
- fixed corner hit detection issues due to imprecise double comparisons
- added single & double tap position feedback
- fixed focus when scaling by double-tap/pinch
*/
class Magnifier extends StatefulWidget {
const Magnifier({
Key key,
@ -33,16 +35,16 @@ class Magnifier extends StatefulWidget {
final Widget child;
/// The size of the custom [child]. This value is used to compute the relation between the child and the container's size to calculate the scale value.
// The size of the custom [child]. This value is used to compute the relation between the child and the container's size to calculate the scale value.
final Size childSize;
/// Defines the maximum size in which the image will be allowed to assume, it is proportional to the original image size.
// Defines the maximum size in which the image will be allowed to assume, it is proportional to the original image size.
final ScaleLevel maxScale;
/// Defines the minimum size in which the image will be allowed to assume, it is proportional to the original image size.
// Defines the minimum size in which the image will be allowed to assume, it is proportional to the original image size.
final ScaleLevel minScale;
/// Defines the size the image will assume when the component is initialized, it is proportional to the original image size.
// Defines the size the image will assume when the component is initialized, it is proportional to the original image size.
final ScaleLevel initialScale;
final MagnifierController controller;

View file

@ -4,6 +4,7 @@ import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -20,7 +21,6 @@ import 'package:aves/widgets/drawer/app_drawer.dart';
import 'package:aves/widgets/filter_grids/common/decorated_filter_chip.dart';
import 'package:aves/widgets/filter_grids/common/section_keys.dart';
import 'package:aves/widgets/filter_grids/common/section_layout.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
@ -192,8 +192,8 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
return Selector<MediaQueryData, double>(
selector: (context, mq) => mq.effectiveBottomPadding,
builder: (context, mqPaddingBottom, child) => DraggableScrollbar(
heightScrollThumb: avesScrollThumbHeight,
backgroundColor: Colors.white,
scrollThumbHeight: avesScrollThumbHeight,
scrollThumbBuilder: avesScrollThumbBuilder(
height: avesScrollThumbHeight,
backgroundColor: Colors.white,

View file

@ -148,15 +148,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
draggable_scrollbar:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "3b823ae0a9def4edec62771f18e6348312bfce15"
url: "git://github.com/deckerst/flutter-draggable-scrollbar.git"
source: git
version: "0.0.4"
event_bus:
dependency: "direct main"
description:

View file

@ -34,10 +34,6 @@ dependencies:
charts_flutter:
collection:
decorated_icon:
draggable_scrollbar:
# path: ../flutter-draggable-scrollbar
git:
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
event_bus:
expansion_tile_card:
# path: ../expansion_tile_card