mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 16:48:16 +08:00
opt: matrix anim (#1829)
This commit is contained in:
committed by
GitHub
parent
4ac855d393
commit
8234b7ac92
@@ -13,7 +13,7 @@ class ImageHorizontalDragGestureRecognizer
|
|||||||
|
|
||||||
Offset? _initialPosition;
|
Offset? _initialPosition;
|
||||||
|
|
||||||
final double width;
|
double width;
|
||||||
final TransformationController transformationController;
|
final TransformationController transformationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer.dart'
|
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer.dart'
|
||||||
as custom;
|
as custom;
|
||||||
|
import 'package:PiliPlus/common/widgets/only_layout_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// https://github.com/qq326646683/interactiveviewer_gallery
|
/// https://github.com/qq326646683/interactiveviewer_gallery
|
||||||
@@ -53,15 +56,16 @@ class InteractiveViewerBoundary extends StatefulWidget {
|
|||||||
|
|
||||||
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late TransformationController _controller;
|
late final TransformationController _controller;
|
||||||
late AnimationController _animateController;
|
late final AnimationController _animateController;
|
||||||
late Animation<Offset> _slideAnimation;
|
late final Animation<Decoration> _opacityAnimation;
|
||||||
late Animation<double> _scaleAnimation;
|
double dx = 0, dy = 0;
|
||||||
late Animation<Decoration> _opacityAnimation;
|
|
||||||
|
|
||||||
Offset _offset = Offset.zero;
|
Offset _offset = Offset.zero;
|
||||||
bool _dragging = false;
|
bool _dragging = false;
|
||||||
|
|
||||||
|
late Size _size;
|
||||||
|
|
||||||
bool get _isActive => _dragging || _animateController.isAnimating;
|
bool get _isActive => _dragging || _animateController.isAnimating;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -74,35 +78,42 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
|||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
_updateMoveAnimation();
|
|
||||||
|
|
||||||
_scaleAnimation = _animateController.drive(
|
|
||||||
Tween<double>(begin: 1.0, end: 0.25),
|
|
||||||
);
|
|
||||||
|
|
||||||
_opacityAnimation = _animateController.drive(
|
_opacityAnimation = _animateController.drive(
|
||||||
DecorationTween(
|
DecorationTween(
|
||||||
begin: const BoxDecoration(color: Colors.black),
|
begin: const BoxDecoration(color: Colors.black),
|
||||||
end: const BoxDecoration(color: Colors.transparent),
|
end: const BoxDecoration(color: Colors.transparent),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_animateController.addListener(_updateTransformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _updateTransformation() {
|
||||||
void dispose() {
|
final val = _animateController.value;
|
||||||
_animateController.dispose();
|
final scale = ui.lerpDouble(1.0, 0.25, val)!;
|
||||||
super.dispose();
|
|
||||||
|
// Matrix4.identity()
|
||||||
|
// ..translateByDouble(size.width / 2, size.height / 2, 0, 1)
|
||||||
|
// ..translateByDouble(size.width * val * dx, size.height * val * dy, 0, 1)
|
||||||
|
// ..scaleByDouble(scale, scale, 1, 1)
|
||||||
|
// ..translateByDouble(-size.width / 2, -size.height / 2, 0, 1);
|
||||||
|
|
||||||
|
final tmp = (1.0 - scale) / 2.0;
|
||||||
|
_controller.value = Matrix4.diagonal3Values(scale, scale, scale)
|
||||||
|
..setTranslationRaw(
|
||||||
|
_size.width * (val * dx + tmp),
|
||||||
|
_size.height * (val * dy + tmp),
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateMoveAnimation() {
|
void _updateMoveAnimation() {
|
||||||
final double endX = _offset.dx.sign * (_offset.dx.abs() / _offset.dy.abs());
|
dy = _offset.dy.sign;
|
||||||
final double endY = _offset.dy.sign;
|
if (dy == 0) {
|
||||||
_slideAnimation = _animateController.drive(
|
dx = 0;
|
||||||
Tween<Offset>(
|
} else {
|
||||||
begin: Offset.zero,
|
dx = _offset.dx / _offset.dy.abs();
|
||||||
end: Offset(endX, endY),
|
}
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragStart(ScaleStartDetails details) {
|
void _handleDragStart(ScaleStartDetails details) {
|
||||||
@@ -114,7 +125,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
|||||||
_offset = Offset.zero;
|
_offset = Offset.zero;
|
||||||
_animateController.value = 0.0;
|
_animateController.value = 0.0;
|
||||||
}
|
}
|
||||||
setState(_updateMoveAnimation);
|
_updateMoveAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragUpdate(ScaleUpdateDetails details) {
|
void _handleDragUpdate(ScaleUpdateDetails details) {
|
||||||
@@ -123,11 +134,10 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
|||||||
}
|
}
|
||||||
|
|
||||||
_offset += details.focalPointDelta;
|
_offset += details.focalPointDelta;
|
||||||
|
_updateMoveAnimation();
|
||||||
setState(_updateMoveAnimation);
|
|
||||||
|
|
||||||
if (!_animateController.isAnimating) {
|
if (!_animateController.isAnimating) {
|
||||||
_animateController.value = _offset.dy.abs() / context.size!.height;
|
_animateController.value = _offset.dy.abs() / _size.height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,20 +163,21 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget get content => DecoratedBoxTransition(
|
@override
|
||||||
decoration: _opacityAnimation,
|
void dispose() {
|
||||||
child: SlideTransition(
|
_animateController
|
||||||
position: _slideAnimation,
|
..removeListener(_updateTransformation)
|
||||||
child: ScaleTransition(
|
..dispose();
|
||||||
scale: _scaleAnimation,
|
super.dispose();
|
||||||
child: widget.child,
|
}
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return custom.InteractiveViewer(
|
return LayoutSizeWidget(
|
||||||
|
onPerformLayout: (size) => _size = size,
|
||||||
|
child: DecoratedBoxTransition(
|
||||||
|
decoration: _opacityAnimation,
|
||||||
|
child: custom.InteractiveViewer(
|
||||||
maxScale: widget.maxScale,
|
maxScale: widget.maxScale,
|
||||||
minScale: widget.minScale,
|
minScale: widget.minScale,
|
||||||
transformationController: _controller,
|
transformationController: _controller,
|
||||||
@@ -175,7 +186,9 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
|||||||
onPanEnd: _handleDragEnd,
|
onPanEnd: _handleDragEnd,
|
||||||
onInteractionEnd: widget.onInteractionEnd,
|
onInteractionEnd: widget.onInteractionEnd,
|
||||||
isAnimating: () => _animateController.value != 0,
|
isAnimating: () => _animateController.value != 0,
|
||||||
child: content,
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,10 +83,20 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
/// The controller to animate the transformation value of the
|
/// The controller to animate the transformation value of the
|
||||||
/// [InteractiveViewer] when it should reset.
|
/// [InteractiveViewer] when it should reset.
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
Animation<Matrix4>? _animation;
|
|
||||||
|
late final _tween = Matrix4Tween();
|
||||||
|
late final _animatable = _tween.chain(CurveTween(curve: Curves.easeOut));
|
||||||
|
|
||||||
|
late final _horizontalDragGestureRecognizer =
|
||||||
|
ImageHorizontalDragGestureRecognizer(
|
||||||
|
width: 0,
|
||||||
|
transformationController: _transformationController,
|
||||||
|
);
|
||||||
|
|
||||||
late Offset _doubleTapLocalPosition;
|
late Offset _doubleTapLocalPosition;
|
||||||
|
|
||||||
|
late double _width;
|
||||||
|
|
||||||
late final RxInt currentIndex = widget.initIndex.obs;
|
late final RxInt currentIndex = widget.initIndex.obs;
|
||||||
|
|
||||||
late final int _quality = Pref.previewQ;
|
late final int _quality = Pref.previewQ;
|
||||||
@@ -113,7 +123,16 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void listener() {
|
void listener() {
|
||||||
_transformationController.value = _animation?.value ?? Matrix4.identity();
|
_transformationController.value = _animatable.evaluate(
|
||||||
|
_animationController,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_width = MediaQuery.widthOf(context);
|
||||||
|
_horizontalDragGestureRecognizer.width = _width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -125,6 +144,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
..removeListener(listener)
|
..removeListener(listener)
|
||||||
..dispose();
|
..dispose();
|
||||||
_transformationController.dispose();
|
_transformationController.dispose();
|
||||||
|
_horizontalDragGestureRecognizer.dispose();
|
||||||
if (widget.quality != _quality) {
|
if (widget.quality != _quality) {
|
||||||
for (final item in widget.sources) {
|
for (final item in widget.sources) {
|
||||||
if (item.sourceType == SourceType.networkImage) {
|
if (item.sourceType == SourceType.networkImage) {
|
||||||
@@ -159,13 +179,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
if (_transformationController.value != Matrix4.identity()) {
|
if (_transformationController.value != Matrix4.identity()) {
|
||||||
// animate the reset for the transformation of the interactive viewer
|
// animate the reset for the transformation of the interactive viewer
|
||||||
|
|
||||||
_animation = _animationController.drive(
|
_tween
|
||||||
Matrix4Tween(
|
..begin = _transformationController.value.clone()
|
||||||
begin: _transformationController.value,
|
..end = Matrix4.identity();
|
||||||
end: Matrix4.identity(),
|
|
||||||
).chain(CurveTween(curve: Curves.easeOut)),
|
|
||||||
);
|
|
||||||
|
|
||||||
_animationController.forward(from: 0);
|
_animationController.forward(from: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,13 +197,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final width = MediaQuery.widthOf(context);
|
|
||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
InteractiveViewerBoundary(
|
InteractiveViewerBoundary(
|
||||||
controller: _transformationController,
|
controller: _transformationController,
|
||||||
boundaryWidth: width,
|
boundaryWidth: _width,
|
||||||
maxScale: widget.maxScale,
|
maxScale: widget.maxScale,
|
||||||
minScale: widget.minScale,
|
minScale: widget.minScale,
|
||||||
onDismissed: Get.back,
|
onDismissed: Get.back,
|
||||||
@@ -231,11 +246,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
: _itemBuilder(index, item),
|
: _itemBuilder(index, item),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
horizontalDragGestureRecognizer:
|
horizontalDragGestureRecognizer: _horizontalDragGestureRecognizer,
|
||||||
ImageHorizontalDragGestureRecognizer(
|
|
||||||
width: width,
|
|
||||||
transformationController: _transformationController,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -315,8 +326,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onDoubleTap() {
|
void onDoubleTap() {
|
||||||
Matrix4 matrix = _transformationController.value.clone();
|
final matrix = _transformationController.value.clone();
|
||||||
double currentScale = matrix.storage[0];
|
final currentScale = matrix.storage[0];
|
||||||
|
|
||||||
double targetScale = widget.minScale;
|
double targetScale = widget.minScale;
|
||||||
|
|
||||||
@@ -324,38 +335,26 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
|||||||
targetScale = widget.maxScale * 0.4;
|
targetScale = widget.maxScale * 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
double offSetX = targetScale == 1.0
|
final double dx, dy;
|
||||||
? 0.0
|
if (targetScale == 1.0) {
|
||||||
: -_doubleTapLocalPosition.dx * (targetScale - 1);
|
dx = dy = 0;
|
||||||
double offSetY = targetScale == 1.0
|
} else {
|
||||||
? 0.0
|
final tmp = 1 - targetScale;
|
||||||
: -_doubleTapLocalPosition.dy * (targetScale - 1);
|
dx = _doubleTapLocalPosition.dx * tmp;
|
||||||
|
dy = _doubleTapLocalPosition.dy * tmp;
|
||||||
|
}
|
||||||
|
|
||||||
matrix = Matrix4.fromList([
|
matrix
|
||||||
targetScale,
|
..[0] = targetScale
|
||||||
matrix.row1.x,
|
..[5] = targetScale
|
||||||
matrix.row2.x,
|
..[10] = targetScale
|
||||||
matrix.row3.x,
|
..[12] = dx
|
||||||
matrix.row0.y,
|
..[13] = dy;
|
||||||
targetScale,
|
|
||||||
matrix.row2.y,
|
_tween
|
||||||
matrix.row3.y,
|
..begin = _transformationController.value.clone()
|
||||||
matrix.row0.z,
|
..end = matrix;
|
||||||
matrix.row1.z,
|
|
||||||
targetScale,
|
|
||||||
matrix.row3.z,
|
|
||||||
offSetX,
|
|
||||||
offSetY,
|
|
||||||
matrix.row2.w,
|
|
||||||
matrix.row3.w,
|
|
||||||
]);
|
|
||||||
|
|
||||||
_animation = _animationController.drive(
|
|
||||||
Matrix4Tween(
|
|
||||||
begin: _transformationController.value,
|
|
||||||
end: matrix,
|
|
||||||
).chain(CurveTween(curve: Curves.easeOut)),
|
|
||||||
);
|
|
||||||
_animationController
|
_animationController
|
||||||
.forward(from: 0)
|
.forward(from: 0)
|
||||||
.whenComplete(() => _onScaleChanged(targetScale));
|
.whenComplete(() => _onScaleChanged(targetScale));
|
||||||
|
|||||||
@@ -15,17 +15,20 @@ class OnlyLayoutWidget extends SingleChildRenderObjectWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) =>
|
RenderObject createRenderObject(BuildContext context) =>
|
||||||
Layout(onPerformLayout: onPerformLayout);
|
NoRenderLayoutBox(onPerformLayout: onPerformLayout);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(BuildContext context, Layout renderObject) {
|
void updateRenderObject(
|
||||||
|
BuildContext context,
|
||||||
|
NoRenderLayoutBox renderObject,
|
||||||
|
) {
|
||||||
super.updateRenderObject(context, renderObject);
|
super.updateRenderObject(context, renderObject);
|
||||||
renderObject.onPerformLayout = onPerformLayout;
|
renderObject.onPerformLayout = onPerformLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Layout extends RenderProxyBox {
|
class NoRenderLayoutBox extends RenderProxyBox {
|
||||||
Layout({required this.onPerformLayout});
|
NoRenderLayoutBox({required this.onPerformLayout});
|
||||||
|
|
||||||
LayoutCallback onPerformLayout;
|
LayoutCallback onPerformLayout;
|
||||||
|
|
||||||
@@ -40,3 +43,42 @@ class Layout extends RenderProxyBox {
|
|||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {}
|
void paint(PaintingContext context, Offset offset) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LayoutSizeWidget extends SingleChildRenderObjectWidget {
|
||||||
|
const LayoutSizeWidget({
|
||||||
|
super.key,
|
||||||
|
super.child,
|
||||||
|
required this.onPerformLayout,
|
||||||
|
});
|
||||||
|
|
||||||
|
final LayoutCallback onPerformLayout;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) =>
|
||||||
|
RenderLayoutBox(onPerformLayout: onPerformLayout);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(
|
||||||
|
BuildContext context,
|
||||||
|
RenderLayoutBox renderObject,
|
||||||
|
) {
|
||||||
|
super.updateRenderObject(context, renderObject);
|
||||||
|
renderObject.onPerformLayout = onPerformLayout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderLayoutBox extends RenderProxyBox {
|
||||||
|
RenderLayoutBox({required this.onPerformLayout});
|
||||||
|
|
||||||
|
LayoutCallback onPerformLayout;
|
||||||
|
|
||||||
|
Size? _size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
super.performLayout();
|
||||||
|
if (_size != size) {
|
||||||
|
onPerformLayout(_size = size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ class AppBarAni extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SlideTransition(
|
return SlideTransition(
|
||||||
position: isTop
|
position: controller.drive(isTop ? _topPos : _bottomPos),
|
||||||
? controller.drive(_topPos)
|
|
||||||
: controller.drive(_bottomPos),
|
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: isTop ? _topDecoration : _bottomDecoration,
|
gradient: isTop ? _topDecoration : _bottomDecoration,
|
||||||
|
|||||||
@@ -575,24 +575,17 @@ abstract final class PageUtils {
|
|||||||
List<SourceModel> imgList,
|
List<SourceModel> imgList,
|
||||||
int index,
|
int index,
|
||||||
) {
|
) {
|
||||||
final animController = AnimationController(
|
|
||||||
vsync: state,
|
|
||||||
duration: Duration.zero,
|
|
||||||
reverseDuration: Duration.zero,
|
|
||||||
);
|
|
||||||
state.showBottomSheet(
|
state.showBottomSheet(
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
(context) => InteractiveviewerGallery(
|
(context) => InteractiveviewerGallery(
|
||||||
sources: imgList,
|
sources: imgList,
|
||||||
initIndex: index,
|
initIndex: index,
|
||||||
quality: GlobalData().imgQuality,
|
quality: GlobalData().imgQuality,
|
||||||
onClose: animController.dispose,
|
|
||||||
),
|
),
|
||||||
enableDrag: false,
|
enableDrag: false,
|
||||||
elevation: 0.0,
|
elevation: 0.0,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
transitionAnimationController: animController,
|
sheetAnimationStyle: AnimationStyle.noAnimation,
|
||||||
sheetAnimationStyle: const AnimationStyle(duration: Duration.zero),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user