ente/lib/ui/huge_listview/draggable_scrollbar.dart

232 lines
6.3 KiB
Dart
Raw Normal View History

// @dart=2.9
2021-04-20 19:39:45 +00:00
import 'dart:async';
2021-04-20 09:22:03 +00:00
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2021-04-20 19:39:45 +00:00
import 'package:photos/ui/huge_listview/scroll_bar_thumb.dart';
2021-04-20 09:22:03 +00:00
class DraggableScrollbar extends StatefulWidget {
final Widget child;
final Color backgroundColor;
final Color drawColor;
final double heightScrollThumb;
final EdgeInsetsGeometry padding;
final int totalCount;
final int initialScrollIndex;
final double bottomSafeArea;
2021-04-20 09:22:03 +00:00
final int currentFirstIndex;
final ValueChanged<double> onChange;
2021-04-20 19:39:45 +00:00
final String Function(int) labelTextBuilder;
final bool isEnabled;
2021-04-20 09:22:03 +00:00
const DraggableScrollbar({
2021-04-20 09:22:03 +00:00
Key key,
@required this.child,
this.backgroundColor = Colors.white,
this.drawColor = Colors.grey,
2021-04-20 20:34:24 +00:00
this.heightScrollThumb = 80.0,
this.bottomSafeArea = 120,
2021-04-20 09:22:03 +00:00
this.padding,
this.totalCount = 1,
this.initialScrollIndex = 0,
this.currentFirstIndex = 0,
2021-04-20 19:39:45 +00:00
@required this.labelTextBuilder,
2021-04-20 09:22:03 +00:00
this.onChange,
this.isEnabled = true,
2021-04-20 09:22:03 +00:00
}) : super(key: key);
@override
DraggableScrollbarState createState() => DraggableScrollbarState();
}
class DraggableScrollbarState extends State<DraggableScrollbar>
with TickerProviderStateMixin {
2022-07-04 06:02:17 +00:00
static const thumbAnimationDuration = Duration(milliseconds: 1000);
static const labelAnimationDuration = Duration(milliseconds: 1000);
2021-04-20 09:22:03 +00:00
double thumbOffset = 0.0;
bool isDragging = false;
int currentFirstIndex;
2021-04-20 09:22:03 +00:00
double get thumbMin => 0.0;
double get thumbMax =>
context.size.height - widget.heightScrollThumb - widget.bottomSafeArea;
2021-04-20 09:22:03 +00:00
AnimationController _thumbAnimationController;
Animation<double> _thumbAnimation;
2021-04-20 19:39:45 +00:00
AnimationController _labelAnimationController;
Animation<double> _labelAnimation;
Timer _fadeoutTimer;
2021-04-20 09:22:03 +00:00
@override
void initState() {
super.initState();
currentFirstIndex = widget.currentFirstIndex;
2021-04-20 09:22:03 +00:00
if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
2022-06-11 08:23:52 +00:00
setState(
() => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
(thumbMax - thumbMin),
);
2021-04-20 09:22:03 +00:00
});
}
2021-04-20 19:39:45 +00:00
_thumbAnimationController = AnimationController(
vsync: this,
duration: thumbAnimationDuration,
);
_thumbAnimation = CurvedAnimation(
parent: _thumbAnimationController,
curve: Curves.fastOutSlowIn,
);
2021-04-20 19:39:45 +00:00
_labelAnimationController = AnimationController(
vsync: this,
duration: labelAnimationDuration,
2021-04-20 19:39:45 +00:00
);
_labelAnimation = CurvedAnimation(
parent: _labelAnimationController,
curve: Curves.fastOutSlowIn,
);
2021-04-20 09:22:03 +00:00
}
@override
2021-04-20 19:39:45 +00:00
void dispose() {
_thumbAnimationController.dispose();
2021-04-20 19:39:45 +00:00
_labelAnimationController.dispose();
_fadeoutTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.isEnabled) {
return Stack(
children: [
RepaintBoundary(child: widget.child),
2021-05-03 15:25:31 +00:00
RepaintBoundary(child: buildThumb()),
],
);
} else {
return widget.child;
}
2021-04-20 19:39:45 +00:00
}
2021-04-20 09:22:03 +00:00
Widget buildKeyboard() {
if (defaultTargetPlatform == TargetPlatform.windows) {
2021-04-20 09:22:03 +00:00
return RawKeyboardListener(
focusNode: FocusNode(),
onKey: keyHandler,
2021-05-03 15:25:31 +00:00
child: buildThumb(),
2021-04-20 09:22:03 +00:00
);
} else {
2021-05-03 15:25:31 +00:00
return buildThumb();
}
2021-04-20 09:22:03 +00:00
}
Widget buildThumb() => Padding(
2021-05-03 15:25:31 +00:00
padding: widget.padding,
child: Container(
alignment: Alignment.topRight,
margin: EdgeInsets.only(top: thumbOffset),
child: ScrollBarThumb(
widget.backgroundColor,
widget.drawColor,
widget.heightScrollThumb,
widget.labelTextBuilder.call(currentFirstIndex),
_labelAnimation,
_thumbAnimation,
onDragStart,
onDragUpdate,
onDragEnd,
),
2021-04-20 09:22:03 +00:00
),
);
void setPosition(double position, int currentFirstIndex) {
2021-04-20 09:22:03 +00:00
setState(() {
this.currentFirstIndex = currentFirstIndex;
2021-04-20 09:22:03 +00:00
thumbOffset = position * (thumbMax - thumbMin);
if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward();
}
2021-04-20 19:39:45 +00:00
_fadeoutTimer?.cancel();
_fadeoutTimer = Timer(thumbAnimationDuration, () {
_thumbAnimationController.reverse();
2021-04-20 19:39:45 +00:00
_labelAnimationController.reverse();
_fadeoutTimer = null;
});
2021-04-20 09:22:03 +00:00
});
}
void onDragStart(DragStartDetails details) {
2021-04-20 19:39:45 +00:00
setState(() {
isDragging = true;
_labelAnimationController.forward();
_fadeoutTimer?.cancel();
});
2021-04-20 09:22:03 +00:00
}
void onDragUpdate(DragUpdateDetails details) {
setState(() {
if (_thumbAnimationController.status != AnimationStatus.forward) {
_thumbAnimationController.forward();
}
2021-04-20 09:22:03 +00:00
if (isDragging && details.delta.dy != 0) {
thumbOffset += details.delta.dy;
thumbOffset = thumbOffset.clamp(thumbMin, thumbMax);
2022-08-29 14:43:31 +00:00
final double position = thumbOffset / (thumbMax - thumbMin);
2021-04-20 09:22:03 +00:00
widget.onChange?.call(position);
}
});
}
void onDragEnd(DragEndDetails details) {
_fadeoutTimer = Timer(thumbAnimationDuration, () {
_thumbAnimationController.reverse();
2021-04-20 19:39:45 +00:00
_labelAnimationController.reverse();
_fadeoutTimer = null;
});
2021-04-20 09:22:03 +00:00
setState(() => isDragging = false);
}
void keyHandler(RawKeyEvent value) {
if (value.runtimeType == RawKeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowDown) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, 2),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, -2),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageDown) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, 25),
2022-06-11 08:23:52 +00:00
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageUp) {
2022-06-11 08:23:52 +00:00
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
2022-07-04 06:02:17 +00:00
delta: const Offset(0, -25),
2022-06-11 08:23:52 +00:00
),
);
}
2021-04-20 09:22:03 +00:00
}
}
}