mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 11:08:03 +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;
|
||||
|
||||
final double width;
|
||||
double width;
|
||||
final TransformationController transformationController;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer.dart'
|
||||
as custom;
|
||||
import 'package:PiliPlus/common/widgets/only_layout_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// https://github.com/qq326646683/interactiveviewer_gallery
|
||||
@@ -53,15 +56,16 @@ class InteractiveViewerBoundary extends StatefulWidget {
|
||||
|
||||
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TransformationController _controller;
|
||||
late AnimationController _animateController;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<Decoration> _opacityAnimation;
|
||||
late final TransformationController _controller;
|
||||
late final AnimationController _animateController;
|
||||
late final Animation<Decoration> _opacityAnimation;
|
||||
double dx = 0, dy = 0;
|
||||
|
||||
Offset _offset = Offset.zero;
|
||||
bool _dragging = false;
|
||||
|
||||
late Size _size;
|
||||
|
||||
bool get _isActive => _dragging || _animateController.isAnimating;
|
||||
|
||||
@override
|
||||
@@ -74,35 +78,42 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_updateMoveAnimation();
|
||||
|
||||
_scaleAnimation = _animateController.drive(
|
||||
Tween<double>(begin: 1.0, end: 0.25),
|
||||
);
|
||||
|
||||
_opacityAnimation = _animateController.drive(
|
||||
DecorationTween(
|
||||
begin: const BoxDecoration(color: Colors.black),
|
||||
end: const BoxDecoration(color: Colors.transparent),
|
||||
),
|
||||
);
|
||||
|
||||
_animateController.addListener(_updateTransformation);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animateController.dispose();
|
||||
super.dispose();
|
||||
void _updateTransformation() {
|
||||
final val = _animateController.value;
|
||||
final scale = ui.lerpDouble(1.0, 0.25, val)!;
|
||||
|
||||
// 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() {
|
||||
final double endX = _offset.dx.sign * (_offset.dx.abs() / _offset.dy.abs());
|
||||
final double endY = _offset.dy.sign;
|
||||
_slideAnimation = _animateController.drive(
|
||||
Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: Offset(endX, endY),
|
||||
),
|
||||
);
|
||||
dy = _offset.dy.sign;
|
||||
if (dy == 0) {
|
||||
dx = 0;
|
||||
} else {
|
||||
dx = _offset.dx / _offset.dy.abs();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragStart(ScaleStartDetails details) {
|
||||
@@ -114,7 +125,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
_offset = Offset.zero;
|
||||
_animateController.value = 0.0;
|
||||
}
|
||||
setState(_updateMoveAnimation);
|
||||
_updateMoveAnimation();
|
||||
}
|
||||
|
||||
void _handleDragUpdate(ScaleUpdateDetails details) {
|
||||
@@ -123,11 +134,10 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
}
|
||||
|
||||
_offset += details.focalPointDelta;
|
||||
|
||||
setState(_updateMoveAnimation);
|
||||
_updateMoveAnimation();
|
||||
|
||||
if (!_animateController.isAnimating) {
|
||||
_animateController.value = _offset.dy.abs() / context.size!.height;
|
||||
_animateController.value = _offset.dy.abs() / _size.height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,29 +163,32 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
}
|
||||
}
|
||||
|
||||
Widget get content => DecoratedBoxTransition(
|
||||
decoration: _opacityAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
@override
|
||||
void dispose() {
|
||||
_animateController
|
||||
..removeListener(_updateTransformation)
|
||||
..dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return custom.InteractiveViewer(
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
transformationController: _controller,
|
||||
onPanStart: _handleDragStart,
|
||||
onPanUpdate: _handleDragUpdate,
|
||||
onPanEnd: _handleDragEnd,
|
||||
onInteractionEnd: widget.onInteractionEnd,
|
||||
isAnimating: () => _animateController.value != 0,
|
||||
child: content,
|
||||
return LayoutSizeWidget(
|
||||
onPerformLayout: (size) => _size = size,
|
||||
child: DecoratedBoxTransition(
|
||||
decoration: _opacityAnimation,
|
||||
child: custom.InteractiveViewer(
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
transformationController: _controller,
|
||||
onPanStart: _handleDragStart,
|
||||
onPanUpdate: _handleDragUpdate,
|
||||
onPanEnd: _handleDragEnd,
|
||||
onInteractionEnd: widget.onInteractionEnd,
|
||||
isAnimating: () => _animateController.value != 0,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,20 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
/// The controller to animate the transformation value of the
|
||||
/// [InteractiveViewer] when it should reset.
|
||||
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 double _width;
|
||||
|
||||
late final RxInt currentIndex = widget.initIndex.obs;
|
||||
|
||||
late final int _quality = Pref.previewQ;
|
||||
@@ -113,7 +123,16 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
|
||||
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
|
||||
@@ -125,6 +144,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
..removeListener(listener)
|
||||
..dispose();
|
||||
_transformationController.dispose();
|
||||
_horizontalDragGestureRecognizer.dispose();
|
||||
if (widget.quality != _quality) {
|
||||
for (final item in widget.sources) {
|
||||
if (item.sourceType == SourceType.networkImage) {
|
||||
@@ -159,13 +179,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
if (_transformationController.value != Matrix4.identity()) {
|
||||
// animate the reset for the transformation of the interactive viewer
|
||||
|
||||
_animation = _animationController.drive(
|
||||
Matrix4Tween(
|
||||
begin: _transformationController.value,
|
||||
end: Matrix4.identity(),
|
||||
).chain(CurveTween(curve: Curves.easeOut)),
|
||||
);
|
||||
|
||||
_tween
|
||||
..begin = _transformationController.value.clone()
|
||||
..end = Matrix4.identity();
|
||||
_animationController.forward(from: 0);
|
||||
}
|
||||
}
|
||||
@@ -181,13 +197,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.widthOf(context);
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
InteractiveViewerBoundary(
|
||||
controller: _transformationController,
|
||||
boundaryWidth: width,
|
||||
boundaryWidth: _width,
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
onDismissed: Get.back,
|
||||
@@ -231,11 +246,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
: _itemBuilder(index, item),
|
||||
);
|
||||
},
|
||||
horizontalDragGestureRecognizer:
|
||||
ImageHorizontalDragGestureRecognizer(
|
||||
width: width,
|
||||
transformationController: _transformationController,
|
||||
),
|
||||
horizontalDragGestureRecognizer: _horizontalDragGestureRecognizer,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@@ -315,8 +326,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
|
||||
void onDoubleTap() {
|
||||
Matrix4 matrix = _transformationController.value.clone();
|
||||
double currentScale = matrix.storage[0];
|
||||
final matrix = _transformationController.value.clone();
|
||||
final currentScale = matrix.storage[0];
|
||||
|
||||
double targetScale = widget.minScale;
|
||||
|
||||
@@ -324,38 +335,26 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
targetScale = widget.maxScale * 0.4;
|
||||
}
|
||||
|
||||
double offSetX = targetScale == 1.0
|
||||
? 0.0
|
||||
: -_doubleTapLocalPosition.dx * (targetScale - 1);
|
||||
double offSetY = targetScale == 1.0
|
||||
? 0.0
|
||||
: -_doubleTapLocalPosition.dy * (targetScale - 1);
|
||||
final double dx, dy;
|
||||
if (targetScale == 1.0) {
|
||||
dx = dy = 0;
|
||||
} else {
|
||||
final tmp = 1 - targetScale;
|
||||
dx = _doubleTapLocalPosition.dx * tmp;
|
||||
dy = _doubleTapLocalPosition.dy * tmp;
|
||||
}
|
||||
|
||||
matrix = Matrix4.fromList([
|
||||
targetScale,
|
||||
matrix.row1.x,
|
||||
matrix.row2.x,
|
||||
matrix.row3.x,
|
||||
matrix.row0.y,
|
||||
targetScale,
|
||||
matrix.row2.y,
|
||||
matrix.row3.y,
|
||||
matrix.row0.z,
|
||||
matrix.row1.z,
|
||||
targetScale,
|
||||
matrix.row3.z,
|
||||
offSetX,
|
||||
offSetY,
|
||||
matrix.row2.w,
|
||||
matrix.row3.w,
|
||||
]);
|
||||
matrix
|
||||
..[0] = targetScale
|
||||
..[5] = targetScale
|
||||
..[10] = targetScale
|
||||
..[12] = dx
|
||||
..[13] = dy;
|
||||
|
||||
_tween
|
||||
..begin = _transformationController.value.clone()
|
||||
..end = matrix;
|
||||
|
||||
_animation = _animationController.drive(
|
||||
Matrix4Tween(
|
||||
begin: _transformationController.value,
|
||||
end: matrix,
|
||||
).chain(CurveTween(curve: Curves.easeOut)),
|
||||
);
|
||||
_animationController
|
||||
.forward(from: 0)
|
||||
.whenComplete(() => _onScaleChanged(targetScale));
|
||||
|
||||
@@ -15,17 +15,20 @@ class OnlyLayoutWidget extends SingleChildRenderObjectWidget {
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) =>
|
||||
Layout(onPerformLayout: onPerformLayout);
|
||||
NoRenderLayoutBox(onPerformLayout: onPerformLayout);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, Layout renderObject) {
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
NoRenderLayoutBox renderObject,
|
||||
) {
|
||||
super.updateRenderObject(context, renderObject);
|
||||
renderObject.onPerformLayout = onPerformLayout;
|
||||
}
|
||||
}
|
||||
|
||||
class Layout extends RenderProxyBox {
|
||||
Layout({required this.onPerformLayout});
|
||||
class NoRenderLayoutBox extends RenderProxyBox {
|
||||
NoRenderLayoutBox({required this.onPerformLayout});
|
||||
|
||||
LayoutCallback onPerformLayout;
|
||||
|
||||
@@ -40,3 +43,42 @@ class Layout extends RenderProxyBox {
|
||||
@override
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user