mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
opt: image viewer (#1837)
* opt: image * opt: MatrixTransition * update --------- Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
committed by
GitHub
parent
9c7c6f9e4e
commit
85292a3df2
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import 'dart:io' show File, Platform;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart';
|
||||
@@ -26,6 +25,7 @@ import 'package:PiliPlus/common/widgets/image_viewer/loading_indicator.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_viewer/viewer.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||
import 'package:PiliPlus/utils/extension/string_ext.dart';
|
||||
import 'package:PiliPlus/utils/image_utils.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
@@ -70,7 +70,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
late Size _containerSize;
|
||||
late final int _quality;
|
||||
late final RxInt _currIndex;
|
||||
late final List<GlobalKey> _keys;
|
||||
GlobalKey? _key;
|
||||
|
||||
Player? _player;
|
||||
Player get _effectivePlayer => _player ??= Player();
|
||||
@@ -85,7 +85,6 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
_horizontalDragGestureRecognizer;
|
||||
late final LongPressGestureRecognizer _longPressGestureRecognizer;
|
||||
|
||||
final Rx<Matrix4> _matrix = Rx(Matrix4.identity());
|
||||
late final AnimationController _animateController;
|
||||
late final Animation<Decoration> _opacityAnimation;
|
||||
double dx = 0, dy = 0;
|
||||
@@ -106,8 +105,13 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
super.initState();
|
||||
_quality = Pref.previewQ;
|
||||
_currIndex = widget.initIndex.obs;
|
||||
_playIfNeeded(widget.initIndex);
|
||||
_keys = List.generate(widget.sources.length, (_) => GlobalKey());
|
||||
final item = widget.sources[widget.initIndex];
|
||||
_playIfNeeded(item);
|
||||
|
||||
if (!item.isLongPic) {
|
||||
_key = GlobalKey();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _key = null);
|
||||
}
|
||||
|
||||
_pageController = PageController(initialPage: widget.initIndex);
|
||||
|
||||
@@ -134,27 +138,23 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
end: const BoxDecoration(color: Colors.transparent),
|
||||
),
|
||||
);
|
||||
|
||||
_animateController.addListener(_updateTransformation);
|
||||
}
|
||||
|
||||
void _updateTransformation() {
|
||||
final val = _animateController.value;
|
||||
final scale = ui.lerpDouble(1.0, 0.25, val)!;
|
||||
Matrix4 _onTransform(double val) {
|
||||
final scale = val.lerp(1.0, 0.25);
|
||||
|
||||
// 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)
|
||||
// ..scaleByDouble(scale, scale, scale, 1)
|
||||
// ..translateByDouble(-size.width / 2, -size.height / 2, 0, 1);
|
||||
|
||||
final tmp = (1.0 - scale) / 2.0;
|
||||
_matrix.value = Matrix4.diagonal3Values(scale, scale, scale)
|
||||
..setTranslationRaw(
|
||||
_containerSize.width * (val * dx + tmp),
|
||||
_containerSize.height * (val * dy + tmp),
|
||||
0,
|
||||
);
|
||||
return Matrix4.diagonal3Values(scale, scale, scale)..setTranslationRaw(
|
||||
_containerSize.width * (val * dx + tmp),
|
||||
_containerSize.height * (val * dy + tmp),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateMoveAnimation() {
|
||||
@@ -217,13 +217,10 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
_player = null;
|
||||
_videoController = null;
|
||||
_pageController.dispose();
|
||||
_animateController
|
||||
..removeListener(_updateTransformation)
|
||||
..dispose();
|
||||
_animateController.dispose();
|
||||
_tapGestureRecognizer.dispose();
|
||||
_longPressGestureRecognizer.dispose();
|
||||
_currIndex.close();
|
||||
_matrix.close();
|
||||
if (widget.quality != _quality) {
|
||||
for (final item in widget.sources) {
|
||||
if (item.sourceType == SourceType.networkImage) {
|
||||
@@ -252,21 +249,20 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
_containerSize = constraints.biggest;
|
||||
return Obx(
|
||||
() => Transform(
|
||||
transform: _matrix.value,
|
||||
child:
|
||||
PageView<ImageHorizontalDragGestureRecognizer>.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: _onPageChanged,
|
||||
physics: const CustomTabBarViewScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics(),
|
||||
),
|
||||
itemCount: widget.sources.length,
|
||||
itemBuilder: _itemBuilder,
|
||||
horizontalDragGestureRecognizer: () =>
|
||||
_horizontalDragGestureRecognizer,
|
||||
),
|
||||
return MatrixTransition(
|
||||
alignment: .topLeft,
|
||||
animation: _animateController,
|
||||
onTransform: _onTransform,
|
||||
child: PageView<ImageHorizontalDragGestureRecognizer>.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: _onPageChanged,
|
||||
physics: const CustomTabBarViewScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics(),
|
||||
),
|
||||
itemCount: widget.sources.length,
|
||||
itemBuilder: _itemBuilder,
|
||||
horizontalDragGestureRecognizer: () =>
|
||||
_horizontalDragGestureRecognizer,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -308,8 +304,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
),
|
||||
);
|
||||
|
||||
void _playIfNeeded(int index) {
|
||||
final item = widget.sources[index];
|
||||
void _playIfNeeded(SourceModel item) {
|
||||
if (item.sourceType == .livePhoto) {
|
||||
_effectivePlayer.open(Media(item.liveUrl!));
|
||||
}
|
||||
@@ -317,7 +312,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
|
||||
void _onPageChanged(int index) {
|
||||
_player?.pause();
|
||||
_playIfNeeded(index);
|
||||
_playIfNeeded(widget.sources[index]);
|
||||
_currIndex.value = index;
|
||||
}
|
||||
|
||||
@@ -340,11 +335,11 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
|
||||
Widget _itemBuilder(BuildContext context, int index) {
|
||||
final item = widget.sources[index];
|
||||
Widget child;
|
||||
final Widget child;
|
||||
switch (item.sourceType) {
|
||||
case SourceType.fileImage:
|
||||
child = Image.file(
|
||||
key: _keys[index],
|
||||
key: _key,
|
||||
File(item.url),
|
||||
filterQuality: .low,
|
||||
minScale: widget.minScale,
|
||||
@@ -360,7 +355,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
case SourceType.networkImage:
|
||||
final isLongPic = item.isLongPic;
|
||||
child = Image(
|
||||
key: _keys[index],
|
||||
key: _key,
|
||||
image: CachedNetworkImageProvider(_getActualUrl(item.url)),
|
||||
minScale: widget.minScale,
|
||||
maxScale: widget.maxScale,
|
||||
@@ -414,7 +409,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
}
|
||||
case SourceType.livePhoto:
|
||||
child = Obx(
|
||||
key: _keys[index],
|
||||
key: _key,
|
||||
() => _currIndex.value == index
|
||||
? Viewer(
|
||||
minScale: widget.minScale,
|
||||
|
||||
@@ -99,13 +99,18 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
)..addListener(_listener);
|
||||
late final _tween = Matrix4Tween();
|
||||
late final _animatable = _tween.chain(CurveTween(curve: Curves.easeOut));
|
||||
|
||||
late double _scaleFrom, _scaleTo;
|
||||
late Offset _positionFrom, _positionTo;
|
||||
|
||||
Matrix4 get _matrix =>
|
||||
Matrix4.translationValues(_position.dx, _position.dy, 0.0)
|
||||
..scaleByDouble(_scale, _scale, _scale, 1.0);
|
||||
|
||||
void _listener() {
|
||||
final storage = _animatable.evaluate(_effectiveAnimationController);
|
||||
_scale = storage[0];
|
||||
_position = Offset(storage[12], storage[13]);
|
||||
final t = Curves.easeOut.transform(_effectiveAnimationController.value);
|
||||
_scale = t.lerp(_scaleFrom, _scaleTo);
|
||||
_position = Offset.lerp(_positionFrom, _positionTo, t)!;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -178,24 +183,29 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
|
||||
Offset _clampPosition(Offset offset, double scale) {
|
||||
final containerSize = widget.containerSize;
|
||||
final containerWidth = containerSize.width;
|
||||
final containerHeight = containerSize.height;
|
||||
final imageWidth = _imageSize.width * scale;
|
||||
final imageHeight = _imageSize.height * scale;
|
||||
|
||||
final dx = (1 - scale) * containerWidth / 2;
|
||||
final dxOffset = (imageWidth - containerWidth) / 2;
|
||||
final center = containerSize * (1 - scale) / 2;
|
||||
|
||||
final dy = (1 - scale) * containerHeight / 2;
|
||||
final dyOffset = (imageHeight - containerHeight) / 2;
|
||||
final dxOffset = (imageWidth - containerSize.width) / 2;
|
||||
final dyOffset = (imageHeight - containerSize.height) / 2;
|
||||
|
||||
return Offset(
|
||||
imageWidth > containerWidth
|
||||
? clampDouble(offset.dx, dx - dxOffset, dx + dxOffset)
|
||||
: dx,
|
||||
imageHeight > containerHeight
|
||||
? clampDouble(offset.dy, dy - dyOffset, dy + dyOffset)
|
||||
: dy,
|
||||
imageWidth > containerSize.width
|
||||
? clampDouble(
|
||||
offset.dx,
|
||||
center.width - dxOffset,
|
||||
center.width + dxOffset,
|
||||
)
|
||||
: center.width,
|
||||
imageHeight > containerSize.height
|
||||
? clampDouble(
|
||||
offset.dy,
|
||||
center.height - dyOffset,
|
||||
center.height + dyOffset,
|
||||
)
|
||||
: center.height,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,9 +229,9 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
void _handleDoubleTap() {
|
||||
final begin = Matrix4.identity()
|
||||
..translateByDouble(_position.dx, _position.dy, 0.0, 1.0)
|
||||
..scaleByDouble(_scale, _scale, _scale, 1.0);
|
||||
if (_effectiveAnimationController.isAnimating) return;
|
||||
_scaleFrom = _scale;
|
||||
_positionFrom = _position;
|
||||
|
||||
double endScale;
|
||||
if (_scale == widget.minScale) {
|
||||
@@ -233,16 +243,13 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
endScale = widget.minScale;
|
||||
}
|
||||
final position = _clampPosition(
|
||||
(_downPos! * (_scale - endScale) + _position * endScale) / _scale,
|
||||
Offset.lerp(_downPos!, _position, endScale / _scale)!,
|
||||
endScale,
|
||||
);
|
||||
final end = Matrix4.identity()
|
||||
..translateByDouble(position.dx, position.dy, 0.0, 1.0)
|
||||
..scaleByDouble(endScale, endScale, endScale, 1.0);
|
||||
|
||||
_tween
|
||||
..begin = begin
|
||||
..end = end;
|
||||
_scaleTo = endScale;
|
||||
_positionTo = position;
|
||||
|
||||
_effectiveAnimationController
|
||||
..duration = const Duration(milliseconds: 300)
|
||||
..forward(from: 0);
|
||||
@@ -254,10 +261,12 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
final imageHeight = _scale * _imageSize.height;
|
||||
final containerHeight = widget.containerSize.height;
|
||||
if (_scalePos != null &&
|
||||
(_round(_position.dy) ==
|
||||
_round((imageHeight - _scale * containerHeight) / 2) &&
|
||||
(_position.dy.equals(
|
||||
(imageHeight - _scale * containerHeight) / 2,
|
||||
1e-6,
|
||||
) &&
|
||||
details.focalPoint.dy > _scalePos!.dy) ||
|
||||
(_round(_position.dy) == _round(containerHeight - imageHeight) &&
|
||||
(_position.dy.equals(containerHeight - imageHeight, 1e-6) &&
|
||||
details.focalPoint.dy < _scalePos!.dy)) {
|
||||
_gestureType = .drag;
|
||||
widget.onDragStart?.call(details);
|
||||
@@ -328,13 +337,11 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
Offset(frictionSimulationX.finalX, frictionSimulationY.finalX),
|
||||
_scale,
|
||||
);
|
||||
_tween
|
||||
..begin = (Matrix4.identity()
|
||||
..translateByDouble(_position.dx, _position.dy, 0.0, 1.0)
|
||||
..scaleByDouble(_scale, _scale, _scale, 1.0))
|
||||
..end = (Matrix4.identity()
|
||||
..translateByDouble(position.dx, position.dy, 0.0, 1.0)
|
||||
..scaleByDouble(_scale, _scale, _scale, 1.0));
|
||||
|
||||
_scaleFrom = _scaleTo = _scale;
|
||||
_positionFrom = _position;
|
||||
_positionTo = position;
|
||||
|
||||
_effectiveAnimationController
|
||||
..duration = Duration(milliseconds: (tFinal * 1000).round())
|
||||
..forward(from: 0);
|
||||
@@ -373,9 +380,6 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final matrix = Matrix4.identity()
|
||||
..translateByDouble(_position.dx, _position.dy, 0.0, 1.0)
|
||||
..scaleByDouble(_scale, _scale, _scale, 1.0);
|
||||
return Listener(
|
||||
behavior: .opaque,
|
||||
onPointerDown: _onPointerDown,
|
||||
@@ -383,7 +387,7 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
onPointerSignal: _onPointerSignal,
|
||||
child: ClipRRect(
|
||||
child: Transform(
|
||||
transform: matrix,
|
||||
transform: _matrix,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
@@ -419,9 +423,9 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
final dx = (1 - _scale) * containerWidth / 2;
|
||||
final dxOffset = (imageWidth - containerWidth) / 2;
|
||||
if (initialPosition.dx < lastPosition.global.dx) {
|
||||
return _round(_position.dx) == _round(dx + dxOffset);
|
||||
return _position.dx.equals(dx + dxOffset);
|
||||
} else {
|
||||
return _round(_position.dx) == _round(dx - dxOffset);
|
||||
return _position.dx.equals(dx - dxOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,8 +451,6 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
double _round(double value) => value.toPrecision(6);
|
||||
|
||||
enum _GestureType { pan, scale, drag }
|
||||
|
||||
double _getFinalTime(
|
||||
|
||||
Reference in New Issue
Block a user