Create a new scrollbar tha works like a normal scrollbar but intended to also show scroll positions of different years. UI works, need to next show the actual values at positions
This commit is contained in:
@@ -17,8 +17,8 @@ import 'package:photos/ui/common/loading_widget.dart';
|
||||
import "package:photos/ui/viewer/gallery/component/group/group_header_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/sectioned_sliver_list.dart";
|
||||
import "package:photos/ui/viewer/gallery/custom_scroll_bar.dart";
|
||||
import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import "package:photos/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/inherited_search_filter_data.dart";
|
||||
@@ -477,9 +477,8 @@ class GalleryState extends State<Gallery> {
|
||||
// widget.showSelectAllByDefault && widget.groupType.showGroupHeader(),
|
||||
// isScrollablePositionedList: widget.isScrollablePositionedList,
|
||||
// ),
|
||||
child: CustomScrollBar(
|
||||
child: CustomScrollBar2(
|
||||
scrollController: _scrollController,
|
||||
galleryGroups: galleryGroups,
|
||||
child: Stack(
|
||||
key: _stackKey,
|
||||
clipBehavior: Clip.none,
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
// Modified CupertinoScrollbar that accepts a ValueNotifier<bool> to indicate
|
||||
// if the scrollbar is in use. In flutter CupertinoScrollbar is in a different
|
||||
// file where as MaterialScrollbar is in the same file as ScrollBar. So
|
||||
// following the same convention by create a separate file for
|
||||
// CupertinoScrollbarWithUseNotifier.
|
||||
|
||||
// Copyright 2014 The Flutter 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 "package:flutter/cupertino.dart";
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
// All values eyeballed.
|
||||
const double _kScrollbarMinLength = 36.0;
|
||||
const double _kScrollbarMinOverscrollLength = 8.0;
|
||||
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 1200);
|
||||
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 250);
|
||||
const Duration _kScrollbarResizeDuration = Duration(milliseconds: 100);
|
||||
|
||||
// Extracted from iOS 13.1 beta using Debug View Hierarchy.
|
||||
const Color _kScrollbarColor = CupertinoDynamicColor.withBrightness(
|
||||
color: Color(0x59000000),
|
||||
darkColor: Color(0x80FFFFFF),
|
||||
);
|
||||
|
||||
// This is the amount of space from the top of a vertical scrollbar to the
|
||||
// top edge of the scrollable, measured when the vertical scrollbar overscrolls
|
||||
// to the top.
|
||||
// TODO(LongCatIsLooong): fix https://github.com/flutter/flutter/issues/32175
|
||||
const double _kScrollbarMainAxisMargin = 3.0;
|
||||
const double _kScrollbarCrossAxisMargin = 3.0;
|
||||
|
||||
/// An iOS style scrollbar.
|
||||
///
|
||||
/// To add a scrollbar to a [ScrollView], wrap the scroll view widget in
|
||||
/// a [CupertinoScrollbarWithUseNotifier] widget.
|
||||
///
|
||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=DbkIQSvwnZc}
|
||||
///
|
||||
/// {@macro flutter.widgets.Scrollbar}
|
||||
///
|
||||
/// When dragging a [CupertinoScrollbarWithUseNotifier] thumb, the thickness and radius will
|
||||
/// animate from [thickness] and [radius] to [thicknessWhileDragging] and
|
||||
/// [radiusWhileDragging], respectively.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample shows a [CupertinoScrollbarWithUseNotifier] that fades in and out of view as scrolling occurs.
|
||||
/// The scrollbar will fade into view as the user scrolls, and fade out when scrolling stops.
|
||||
/// The `thickness` of the scrollbar will animate from 6 pixels to the `thicknessWhileDragging` of 10
|
||||
/// when it is dragged by the user. The `radius` of the scrollbar thumb corners will animate from 34
|
||||
/// to the `radiusWhileDragging` of 0 when the scrollbar is being dragged by the user.
|
||||
///
|
||||
/// ** See code in examples/api/lib/cupertino/scrollbar/cupertino_scrollbar.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// When [thumbVisibility] is true, the scrollbar thumb will remain visible without the
|
||||
/// fade animation. This requires that a [ScrollController] is provided to controller,
|
||||
/// or that the [PrimaryScrollController] is available.
|
||||
///
|
||||
/// ** See code in examples/api/lib/cupertino/scrollbar/cupertino_scrollbar.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ListView], which displays a linear, scrollable list of children.
|
||||
/// * [GridView], which displays a 2 dimensional, scrollable array of children.
|
||||
/// * [Scrollbar], a Material Design scrollbar.
|
||||
/// * [RawScrollbar], a basic scrollbar that fades in and out, extended
|
||||
/// by this class to add more animations and behaviors.
|
||||
class CupertinoScrollbarWithUseNotifier extends RawScrollbar {
|
||||
/// Creates an iOS style scrollbar that wraps the given [child].
|
||||
///
|
||||
/// The [child] should be a source of [ScrollNotification] notifications,
|
||||
/// typically a [Scrollable] widget.
|
||||
|
||||
final ValueNotifier<bool> inUseNotifier;
|
||||
const CupertinoScrollbarWithUseNotifier({
|
||||
super.key,
|
||||
required super.child,
|
||||
required this.inUseNotifier,
|
||||
super.controller,
|
||||
bool? thumbVisibility,
|
||||
double super.thickness = defaultThickness,
|
||||
this.thicknessWhileDragging = defaultThicknessWhileDragging,
|
||||
Radius super.radius = defaultRadius,
|
||||
this.radiusWhileDragging = defaultRadiusWhileDragging,
|
||||
ScrollNotificationPredicate? notificationPredicate,
|
||||
super.scrollbarOrientation,
|
||||
}) : assert(thickness < double.infinity),
|
||||
assert(thicknessWhileDragging < double.infinity),
|
||||
super(
|
||||
thumbVisibility: thumbVisibility ?? false,
|
||||
fadeDuration: _kScrollbarFadeDuration,
|
||||
timeToFade: _kScrollbarTimeToFade,
|
||||
pressDuration: const Duration(milliseconds: 100),
|
||||
notificationPredicate:
|
||||
notificationPredicate ?? defaultScrollNotificationPredicate,
|
||||
);
|
||||
|
||||
/// Default value for [thickness] if it's not specified in [CupertinoScrollbarWithUseNotifier].
|
||||
static const double defaultThickness = 3;
|
||||
|
||||
/// Default value for [thicknessWhileDragging] if it's not specified in
|
||||
/// [CupertinoScrollbarWithUseNotifier].
|
||||
static const double defaultThicknessWhileDragging = 8.0;
|
||||
|
||||
/// Default value for [radius] if it's not specified in [CupertinoScrollbarWithUseNotifier].
|
||||
static const Radius defaultRadius = Radius.circular(1.5);
|
||||
|
||||
/// Default value for [radiusWhileDragging] if it's not specified in
|
||||
/// [CupertinoScrollbarWithUseNotifier].
|
||||
static const Radius defaultRadiusWhileDragging = Radius.circular(4.0);
|
||||
|
||||
/// The thickness of the scrollbar when it's being dragged by the user.
|
||||
///
|
||||
/// When the user starts dragging the scrollbar, the thickness will animate
|
||||
/// from [thickness] to this value, then animate back when the user stops
|
||||
/// dragging the scrollbar.
|
||||
final double thicknessWhileDragging;
|
||||
|
||||
/// The radius of the scrollbar edges when the scrollbar is being dragged by
|
||||
/// the user.
|
||||
///
|
||||
/// When the user starts dragging the scrollbar, the radius will animate
|
||||
/// from [radius] to this value, then animate back when the user stops
|
||||
/// dragging the scrollbar.
|
||||
final Radius radiusWhileDragging;
|
||||
|
||||
@override
|
||||
RawScrollbarState<CupertinoScrollbarWithUseNotifier> createState() =>
|
||||
_CupertinoScrollbarState();
|
||||
}
|
||||
|
||||
class _CupertinoScrollbarState
|
||||
extends RawScrollbarState<CupertinoScrollbarWithUseNotifier> {
|
||||
late AnimationController _thicknessAnimationController;
|
||||
|
||||
double get _thickness {
|
||||
return widget.thickness! +
|
||||
_thicknessAnimationController.value *
|
||||
(widget.thicknessWhileDragging - widget.thickness!);
|
||||
}
|
||||
|
||||
Radius get _radius {
|
||||
return Radius.lerp(
|
||||
widget.radius,
|
||||
widget.radiusWhileDragging,
|
||||
_thicknessAnimationController.value,
|
||||
)!;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_thicknessAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: _kScrollbarResizeDuration,
|
||||
);
|
||||
_thicknessAnimationController.addListener(() {
|
||||
updateScrollbarPainter();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void updateScrollbarPainter() {
|
||||
scrollbarPainter
|
||||
..color = CupertinoDynamicColor.resolve(_kScrollbarColor, context)
|
||||
..textDirection = Directionality.of(context)
|
||||
..thickness = _thickness
|
||||
..mainAxisMargin = _kScrollbarMainAxisMargin
|
||||
..crossAxisMargin = _kScrollbarCrossAxisMargin
|
||||
..radius = _radius
|
||||
..padding = MediaQuery.paddingOf(context)
|
||||
..minLength = _kScrollbarMinLength
|
||||
..minOverscrollLength = _kScrollbarMinOverscrollLength
|
||||
..scrollbarOrientation = widget.scrollbarOrientation;
|
||||
}
|
||||
|
||||
double _pressStartAxisPosition = 0.0;
|
||||
|
||||
// Long press event callbacks handle the gesture where the user long presses
|
||||
// on the scrollbar thumb and then drags the scrollbar without releasing.
|
||||
|
||||
@override
|
||||
void handleThumbPressStart(Offset localPosition) {
|
||||
super.handleThumbPressStart(localPosition);
|
||||
widget.inUseNotifier.value = true;
|
||||
final Axis? direction = getScrollbarDirection();
|
||||
if (direction == null) {
|
||||
return;
|
||||
}
|
||||
_pressStartAxisPosition = switch (direction) {
|
||||
Axis.vertical => localPosition.dy,
|
||||
Axis.horizontal => localPosition.dx,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void handleThumbPress() {
|
||||
if (getScrollbarDirection() == null) {
|
||||
return;
|
||||
}
|
||||
super.handleThumbPress();
|
||||
_thicknessAnimationController.forward().then<void>(
|
||||
(_) => HapticFeedback.mediumImpact(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
|
||||
widget.inUseNotifier.value = false;
|
||||
final Axis? direction = getScrollbarDirection();
|
||||
if (direction == null) {
|
||||
return;
|
||||
}
|
||||
_thicknessAnimationController.reverse();
|
||||
super.handleThumbPressEnd(localPosition, velocity);
|
||||
final (double axisPosition, double axisVelocity) = switch (direction) {
|
||||
Axis.horizontal => (localPosition.dx, velocity.pixelsPerSecond.dx),
|
||||
Axis.vertical => (localPosition.dy, velocity.pixelsPerSecond.dy),
|
||||
};
|
||||
if (axisPosition != _pressStartAxisPosition && axisVelocity.abs() < 10) {
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_thicknessAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/ui/viewer/gallery/scrollbar/scroll_bar_with_use_notifier.dart";
|
||||
|
||||
class CustomScrollBar2 extends StatefulWidget {
|
||||
final Widget child;
|
||||
final ScrollController scrollController;
|
||||
const CustomScrollBar2({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.scrollController,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomScrollBar2> createState() => _CustomScrollBar2State();
|
||||
}
|
||||
|
||||
class _CustomScrollBar2State extends State<CustomScrollBar2> {
|
||||
final inUseNotifier = ValueNotifier<bool>(false);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
ScrollbarWithUseNotifer(
|
||||
controller: widget.scrollController,
|
||||
interactive: true,
|
||||
inUseNotifier: inUseNotifier,
|
||||
child: widget.child,
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: inUseNotifier,
|
||||
builder: (context, inUse, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: !inUse
|
||||
? const SizedBox.shrink()
|
||||
: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 50,
|
||||
right: 24,
|
||||
child: Container(
|
||||
color: Colors.teal,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Item 1',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 100,
|
||||
right: 24,
|
||||
child: Container(
|
||||
color: Colors.teal,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Item 2',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 250,
|
||||
right: 24,
|
||||
child: Container(
|
||||
color: Colors.teal,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Item 3',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 400,
|
||||
right: 24,
|
||||
child: Container(
|
||||
color: Colors.teal,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Item 4',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
// Modified Scrollbar that accepts a ValueNotifier<bool> to indicate if the
|
||||
// scrollbar is in use
|
||||
|
||||
// Copyright 2014 The Flutter 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 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/ui/viewer/gallery/scrollbar/cupertino_scroll_bar_with_use_notifier.dart";
|
||||
|
||||
const double _kScrollbarThickness = 8.0;
|
||||
const double _kScrollbarThicknessWithTrack = 12.0;
|
||||
const double _kScrollbarMargin = 2.0;
|
||||
const double _kScrollbarMinLength = 48.0;
|
||||
const Radius _kScrollbarRadius = Radius.circular(8.0);
|
||||
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
|
||||
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
|
||||
|
||||
/// A Material Design scrollbar.
|
||||
///
|
||||
/// To add a scrollbar to a [ScrollView], wrap the scroll view
|
||||
/// widget in a [ScrollbarWithUseNotifer] widget.
|
||||
///
|
||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=DbkIQSvwnZc}
|
||||
///
|
||||
/// {@macro flutter.widgets.Scrollbar}
|
||||
///
|
||||
/// Dynamically changes to a [CupertinoScrollbar], an iOS style scrollbar, by
|
||||
/// default on the iOS platform.
|
||||
///
|
||||
/// The color of the Scrollbar thumb will change when [MaterialState.dragged],
|
||||
/// or [MaterialState.hovered] on desktop and web platforms. These stateful
|
||||
/// color choices can be changed using [ScrollbarThemeData.thumbColor].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample shows a [ScrollbarWithUseNotifer] that executes a fade animation as scrolling
|
||||
/// occurs. The Scrollbar will fade into view as the user scrolls, and fade out
|
||||
/// when scrolling stops.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/scrollbar/scrollbar.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// When [thumbVisibility] is true, the scrollbar thumb will remain visible
|
||||
/// without the fade animation. This requires that a [ScrollController] is
|
||||
/// provided to controller, or that the [PrimaryScrollController] is available.
|
||||
///
|
||||
/// When a [ScrollView.scrollDirection] is [Axis.horizontal], it is recommended
|
||||
/// that the [ScrollbarWithUseNotifer] is always visible, since scrolling in the horizontal
|
||||
/// axis is less discoverable.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/scrollbar/scrollbar.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// A scrollbar track can be added using [trackVisibility]. This can also be
|
||||
/// drawn when triggered by a hover event, or based on any [MaterialState] by
|
||||
/// using [ScrollbarThemeData.trackVisibility].
|
||||
///
|
||||
/// The [thickness] of the track and scrollbar thumb can be changed dynamically
|
||||
/// in response to [MaterialState]s using [ScrollbarThemeData.thickness].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RawScrollbar], a basic scrollbar that fades in and out, extended
|
||||
/// by this class to add more animations and behaviors.
|
||||
/// * [ScrollbarTheme], which configures the Scrollbar's appearance.
|
||||
/// * [CupertinoScrollbar], an iOS style scrollbar.
|
||||
/// * [ListView], which displays a linear, scrollable list of children.
|
||||
/// * [GridView], which displays a 2 dimensional, scrollable array of children.
|
||||
class ScrollbarWithUseNotifer extends StatelessWidget {
|
||||
/// Creates a Material Design scrollbar that by default will connect to the
|
||||
/// closest Scrollable descendant of [child].
|
||||
///
|
||||
/// The [child] should be a source of [ScrollNotification] notifications,
|
||||
/// typically a [Scrollable] widget.
|
||||
///
|
||||
/// If the [controller] is null, the default behavior is to
|
||||
/// enable scrollbar dragging using the [PrimaryScrollController].
|
||||
///
|
||||
/// When null, [thickness] defaults to 8.0 pixels on desktop and web, and 4.0
|
||||
/// pixels when on mobile platforms. A null [radius] will result in a default
|
||||
/// of an 8.0 pixel circular radius about the corners of the scrollbar thumb,
|
||||
/// except for when executing on [TargetPlatform.android], which will render the
|
||||
/// thumb without a radius.
|
||||
const ScrollbarWithUseNotifer({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.inUseNotifier,
|
||||
this.controller,
|
||||
this.thumbVisibility,
|
||||
this.trackVisibility,
|
||||
this.thickness,
|
||||
this.radius,
|
||||
this.notificationPredicate,
|
||||
this.interactive,
|
||||
this.scrollbarOrientation,
|
||||
});
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.controller}
|
||||
final ScrollController? controller;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.thumbVisibility}
|
||||
///
|
||||
/// If this property is null, then [ScrollbarThemeData.thumbVisibility] of
|
||||
/// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
|
||||
/// is false.
|
||||
///
|
||||
/// If the thumb visibility is related to the scrollbar's material state,
|
||||
/// use the global [ScrollbarThemeData.thumbVisibility] or override the
|
||||
/// sub-tree's theme data.
|
||||
final bool? thumbVisibility;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.trackVisibility}
|
||||
///
|
||||
/// If this property is null, then [ScrollbarThemeData.trackVisibility] of
|
||||
/// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
|
||||
/// is false.
|
||||
///
|
||||
/// If the track visibility is related to the scrollbar's material state,
|
||||
/// use the global [ScrollbarThemeData.trackVisibility] or override the
|
||||
/// sub-tree's theme data.
|
||||
final bool? trackVisibility;
|
||||
|
||||
/// The thickness of the scrollbar in the cross axis of the scrollable.
|
||||
///
|
||||
/// If null, the default value is platform dependent. On [TargetPlatform.android],
|
||||
/// the default thickness is 4.0 pixels. On [TargetPlatform.iOS],
|
||||
/// [CupertinoScrollbar.defaultThickness] is used. The remaining platforms have a
|
||||
/// default thickness of 8.0 pixels.
|
||||
final double? thickness;
|
||||
|
||||
/// The [Radius] of the scrollbar thumb's rounded rectangle corners.
|
||||
///
|
||||
/// If null, the default value is platform dependent. On [TargetPlatform.android],
|
||||
/// no radius is applied to the scrollbar thumb. On [TargetPlatform.iOS],
|
||||
/// [CupertinoScrollbar.defaultRadius] is used. The remaining platforms have a
|
||||
/// default [Radius.circular] of 8.0 pixels.
|
||||
final Radius? radius;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.interactive}
|
||||
final bool? interactive;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.notificationPredicate}
|
||||
final ScrollNotificationPredicate? notificationPredicate;
|
||||
|
||||
/// {@macro flutter.widgets.Scrollbar.scrollbarOrientation}
|
||||
final ScrollbarOrientation? scrollbarOrientation;
|
||||
|
||||
final ValueNotifier<bool> inUseNotifier;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Theme.of(context).platform == TargetPlatform.iOS) {
|
||||
return CupertinoScrollbarWithUseNotifier(
|
||||
thumbVisibility: thumbVisibility ?? false,
|
||||
thickness: thickness ?? CupertinoScrollbar.defaultThickness,
|
||||
thicknessWhileDragging:
|
||||
thickness ?? CupertinoScrollbar.defaultThicknessWhileDragging,
|
||||
radius: radius ?? CupertinoScrollbar.defaultRadius,
|
||||
radiusWhileDragging:
|
||||
radius ?? CupertinoScrollbar.defaultRadiusWhileDragging,
|
||||
controller: controller,
|
||||
notificationPredicate: notificationPredicate,
|
||||
scrollbarOrientation: scrollbarOrientation,
|
||||
inUseNotifier: inUseNotifier,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return _MaterialScrollbar(
|
||||
controller: controller,
|
||||
thumbVisibility: thumbVisibility,
|
||||
trackVisibility: trackVisibility,
|
||||
thickness: thickness,
|
||||
radius: radius,
|
||||
notificationPredicate: notificationPredicate,
|
||||
interactive: interactive,
|
||||
scrollbarOrientation: scrollbarOrientation,
|
||||
inUseNotifier: inUseNotifier,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MaterialScrollbar extends RawScrollbar {
|
||||
final ValueNotifier<bool> inUseNotifier;
|
||||
const _MaterialScrollbar({
|
||||
required super.child,
|
||||
required this.inUseNotifier,
|
||||
super.controller,
|
||||
super.thumbVisibility,
|
||||
super.trackVisibility,
|
||||
super.thickness,
|
||||
super.radius,
|
||||
ScrollNotificationPredicate? notificationPredicate,
|
||||
super.interactive,
|
||||
super.scrollbarOrientation,
|
||||
}) : super(
|
||||
fadeDuration: _kScrollbarFadeDuration,
|
||||
timeToFade: _kScrollbarTimeToFade,
|
||||
pressDuration: Duration.zero,
|
||||
notificationPredicate:
|
||||
notificationPredicate ?? defaultScrollNotificationPredicate,
|
||||
);
|
||||
|
||||
@override
|
||||
_MaterialScrollbarState createState() => _MaterialScrollbarState();
|
||||
}
|
||||
|
||||
class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
|
||||
late AnimationController _hoverAnimationController;
|
||||
bool _dragIsActive = false;
|
||||
bool _hoverIsActive = false;
|
||||
late ColorScheme _colorScheme;
|
||||
late ScrollbarThemeData _scrollbarTheme;
|
||||
// On Android, scrollbars should match native appearance.
|
||||
late bool _useAndroidScrollbar;
|
||||
|
||||
@override
|
||||
bool get showScrollbar =>
|
||||
widget.thumbVisibility ??
|
||||
_scrollbarTheme.thumbVisibility?.resolve(_states) ??
|
||||
false;
|
||||
|
||||
@override
|
||||
bool get enableGestures =>
|
||||
widget.interactive ??
|
||||
_scrollbarTheme.interactive ??
|
||||
!_useAndroidScrollbar;
|
||||
|
||||
MaterialStateProperty<bool> get _trackVisibility =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
return widget.trackVisibility ??
|
||||
_scrollbarTheme.trackVisibility?.resolve(states) ??
|
||||
false;
|
||||
});
|
||||
|
||||
Set<MaterialState> get _states => <MaterialState>{
|
||||
if (_dragIsActive) MaterialState.dragged,
|
||||
if (_hoverIsActive) MaterialState.hovered,
|
||||
};
|
||||
|
||||
MaterialStateProperty<Color> get _thumbColor {
|
||||
final Color onSurface = _colorScheme.onSurface;
|
||||
final Brightness brightness = _colorScheme.brightness;
|
||||
late Color dragColor;
|
||||
late Color hoverColor;
|
||||
late Color idleColor;
|
||||
switch (brightness) {
|
||||
case Brightness.light:
|
||||
dragColor = onSurface.withOpacity(0.6);
|
||||
hoverColor = onSurface.withOpacity(0.5);
|
||||
idleColor = _useAndroidScrollbar
|
||||
? Theme.of(context).highlightColor.withOpacity(1.0)
|
||||
: onSurface.withOpacity(0.1);
|
||||
case Brightness.dark:
|
||||
dragColor = onSurface.withOpacity(0.75);
|
||||
hoverColor = onSurface.withOpacity(0.65);
|
||||
idleColor = _useAndroidScrollbar
|
||||
? Theme.of(context).highlightColor.withOpacity(1.0)
|
||||
: onSurface.withOpacity(0.3);
|
||||
}
|
||||
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.dragged)) {
|
||||
return _scrollbarTheme.thumbColor?.resolve(states) ?? dragColor;
|
||||
}
|
||||
|
||||
// If the track is visible, the thumb color hover animation is ignored and
|
||||
// changes immediately.
|
||||
if (_trackVisibility.resolve(states)) {
|
||||
return _scrollbarTheme.thumbColor?.resolve(states) ?? hoverColor;
|
||||
}
|
||||
|
||||
return Color.lerp(
|
||||
_scrollbarTheme.thumbColor?.resolve(states) ?? idleColor,
|
||||
_scrollbarTheme.thumbColor?.resolve(states) ?? hoverColor,
|
||||
_hoverAnimationController.value,
|
||||
)!;
|
||||
});
|
||||
}
|
||||
|
||||
MaterialStateProperty<Color> get _trackColor {
|
||||
final Color onSurface = _colorScheme.onSurface;
|
||||
final Brightness brightness = _colorScheme.brightness;
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (showScrollbar && _trackVisibility.resolve(states)) {
|
||||
return _scrollbarTheme.trackColor?.resolve(states) ??
|
||||
switch (brightness) {
|
||||
Brightness.light => onSurface.withOpacity(0.03),
|
||||
Brightness.dark => onSurface.withOpacity(0.05),
|
||||
};
|
||||
}
|
||||
return const Color(0x00000000);
|
||||
});
|
||||
}
|
||||
|
||||
MaterialStateProperty<Color> get _trackBorderColor {
|
||||
final Color onSurface = _colorScheme.onSurface;
|
||||
final Brightness brightness = _colorScheme.brightness;
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (showScrollbar && _trackVisibility.resolve(states)) {
|
||||
return _scrollbarTheme.trackBorderColor?.resolve(states) ??
|
||||
switch (brightness) {
|
||||
Brightness.light => onSurface.withOpacity(0.1),
|
||||
Brightness.dark => onSurface.withOpacity(0.25),
|
||||
};
|
||||
}
|
||||
return const Color(0x00000000);
|
||||
});
|
||||
}
|
||||
|
||||
MaterialStateProperty<double> get _thickness {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
_trackVisibility.resolve(states)) {
|
||||
return widget.thickness ??
|
||||
_scrollbarTheme.thickness?.resolve(states) ??
|
||||
_kScrollbarThicknessWithTrack;
|
||||
}
|
||||
// The default scrollbar thickness is smaller on mobile.
|
||||
return widget.thickness ??
|
||||
_scrollbarTheme.thickness?.resolve(states) ??
|
||||
(_kScrollbarThickness / (_useAndroidScrollbar ? 2 : 1));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hoverAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
_hoverAnimationController.addListener(() {
|
||||
updateScrollbarPainter();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
_colorScheme = theme.colorScheme;
|
||||
_scrollbarTheme = ScrollbarTheme.of(context);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.android:
|
||||
_useAndroidScrollbar = true;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
_useAndroidScrollbar = false;
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void updateScrollbarPainter() {
|
||||
scrollbarPainter
|
||||
..color = _thumbColor.resolve(_states)
|
||||
..trackColor = _trackColor.resolve(_states)
|
||||
..trackBorderColor = _trackBorderColor.resolve(_states)
|
||||
..textDirection = Directionality.of(context)
|
||||
..thickness = _thickness.resolve(_states)
|
||||
..radius = widget.radius ??
|
||||
_scrollbarTheme.radius ??
|
||||
(_useAndroidScrollbar ? null : _kScrollbarRadius)
|
||||
..crossAxisMargin = _scrollbarTheme.crossAxisMargin ??
|
||||
(_useAndroidScrollbar ? 0.0 : _kScrollbarMargin)
|
||||
..mainAxisMargin = _scrollbarTheme.mainAxisMargin ?? 0.0
|
||||
..minLength = _scrollbarTheme.minThumbLength ?? _kScrollbarMinLength
|
||||
..padding = MediaQuery.paddingOf(context)
|
||||
..scrollbarOrientation = widget.scrollbarOrientation
|
||||
..ignorePointer = !enableGestures;
|
||||
}
|
||||
|
||||
@override
|
||||
void handleThumbPressStart(Offset localPosition) {
|
||||
super.handleThumbPressStart(localPosition);
|
||||
setState(() {
|
||||
_dragIsActive = true;
|
||||
widget.inUseNotifier.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
|
||||
super.handleThumbPressEnd(localPosition, velocity);
|
||||
setState(() {
|
||||
_dragIsActive = false;
|
||||
widget.inUseNotifier.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void handleHover(PointerHoverEvent event) {
|
||||
super.handleHover(event);
|
||||
// Check if the position of the pointer falls over the painted scrollbar
|
||||
if (isPointerOverScrollbar(event.position, event.kind, forHover: true)) {
|
||||
// Pointer is hovering over the scrollbar
|
||||
setState(() {
|
||||
_hoverIsActive = true;
|
||||
});
|
||||
_hoverAnimationController.forward();
|
||||
} else if (_hoverIsActive) {
|
||||
// Pointer was, but is no longer over painted scrollbar.
|
||||
setState(() {
|
||||
_hoverIsActive = false;
|
||||
});
|
||||
_hoverAnimationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleHoverExit(PointerExitEvent event) {
|
||||
super.handleHoverExit(event);
|
||||
setState(() {
|
||||
_hoverIsActive = false;
|
||||
});
|
||||
_hoverAnimationController.reverse();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hoverAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user