logo和名称替换
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgmj/assets_tool/app_colors.dart';
|
||||
import 'package:hgmj/assets_tool/images.dart';
|
||||
import 'package:hgmj/hj_utils/image_util.dart';
|
||||
import 'package:hgmj/tools_base/cache/image_cache_manager.dart';
|
||||
import 'package:hgmj/tools_base/loading/loading_alert_widget.dart';
|
||||
import 'package:hgmj/tools_base/toast.dart';
|
||||
import 'package:hgmj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:wydj/assets_tool/app_colors.dart';
|
||||
import 'package:wydj/assets_tool/images.dart';
|
||||
import 'package:wydj/hj_utils/image_util.dart';
|
||||
import 'package:wydj/tools_base/cache/image_cache_manager.dart';
|
||||
import 'package:wydj/tools_base/loading/loading_alert_widget.dart';
|
||||
import 'package:wydj/tools_base/toast.dart';
|
||||
import 'package:wydj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
/// 微信风格图片浏览器
|
||||
/// 横滑切图 + 双指/双击缩放 + 上下滑拖拽退出(图片跟手、背景随拖动渐隐露出下层页面) + 长按保存
|
||||
@@ -20,18 +20,26 @@ class ImageBrowserPage extends StatefulWidget {
|
||||
final int initialIndex;
|
||||
final bool showSaveButton; // 右上角显式"保存到相册"按钮(AI 生成图等场景);默认只支持长按保存
|
||||
|
||||
const ImageBrowserPage({super.key, required this.images, this.initialIndex = 0, this.showSaveButton = false});
|
||||
const ImageBrowserPage(
|
||||
{super.key,
|
||||
required this.images,
|
||||
this.initialIndex = 0,
|
||||
this.showSaveButton = false});
|
||||
|
||||
/// 打开浏览器(透明路由 + 淡入)
|
||||
/// [showSaveButton] 显示右上角"保存到相册"按钮
|
||||
static void open(List<String> images, {int index = 0, bool showSaveButton = false}) {
|
||||
static void open(List<String> images,
|
||||
{int index = 0, bool showSaveButton = false}) {
|
||||
final valid = images.where((e) => e.isNotEmpty).toList();
|
||||
if (valid.isEmpty) return;
|
||||
// fullscreenDialog:true → GetX canTransitionTo 返回 false,下层页面不做外出转场,
|
||||
// 保持完整渲染 → 拖拽偷看时看到的是整页而非"pop 一半"。
|
||||
// 瞬时 fadeIn:打开无残影。仍是 Get.to → Get.back 能正常 pop,下滑退出正常
|
||||
Get.to(
|
||||
() => ImageBrowserPage(images: valid, initialIndex: index.clamp(0, valid.length - 1), showSaveButton: showSaveButton),
|
||||
() => ImageBrowserPage(
|
||||
images: valid,
|
||||
initialIndex: index.clamp(0, valid.length - 1),
|
||||
showSaveButton: showSaveButton),
|
||||
opaque: false,
|
||||
transition: Transition.fadeIn,
|
||||
duration: Duration.zero,
|
||||
@@ -43,7 +51,8 @@ class ImageBrowserPage extends StatefulWidget {
|
||||
State<ImageBrowserPage> createState() => _ImageBrowserPageState();
|
||||
}
|
||||
|
||||
class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProviderStateMixin {
|
||||
class _ImageBrowserPageState extends State<ImageBrowserPage>
|
||||
with TickerProviderStateMixin {
|
||||
// ========== 分页 ==========
|
||||
late final PageController _pageCtr;
|
||||
late int _curIndex;
|
||||
@@ -72,11 +81,17 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
super.initState();
|
||||
_curIndex = widget.initialIndex;
|
||||
_pageCtr = PageController(initialPage: _curIndex);
|
||||
_transCtrs = List.generate(widget.images.length, (_) => TransformationController());
|
||||
_resetCtr = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_zoomCtr = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_enterCtr = AnimationController(vsync: this, duration: const Duration(milliseconds: 220))..forward();
|
||||
_exitCtr = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_transCtrs =
|
||||
List.generate(widget.images.length, (_) => TransformationController());
|
||||
_resetCtr = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_zoomCtr = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_enterCtr = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 220))
|
||||
..forward();
|
||||
_exitCtr = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -97,10 +112,12 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
bool get _isZoomed => _transCtrs[_curIndex].value.getMaxScaleOnAxis() > 1.05;
|
||||
|
||||
// 背景不透明度:随竖直拖动距离渐隐(下拉偷看下层)
|
||||
double get _bgOpacity => (1 - _dragY.abs() / (Get.height * 0.6)).clamp(0.0, 1.0);
|
||||
double get _bgOpacity =>
|
||||
(1 - _dragY.abs() / (Get.height * 0.6)).clamp(0.0, 1.0);
|
||||
|
||||
// 拖动时图片轻微缩小
|
||||
double get _dragScale => (1 - _dragY.abs() / (Get.height * 2)).clamp(0.85, 1.0);
|
||||
double get _dragScale =>
|
||||
(1 - _dragY.abs() / (Get.height * 2)).clamp(0.85, 1.0);
|
||||
|
||||
// 退出淡出系数:1→0
|
||||
double get _exitFactor => 1 - _exitCtr.value;
|
||||
@@ -119,8 +136,8 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
_exit();
|
||||
return;
|
||||
}
|
||||
final anim =
|
||||
Tween<double>(begin: _dragY, end: 0).animate(CurvedAnimation(parent: _resetCtr, curve: Curves.easeOut));
|
||||
final anim = Tween<double>(begin: _dragY, end: 0)
|
||||
.animate(CurvedAnimation(parent: _resetCtr, curve: Curves.easeOut));
|
||||
void listener() => setState(() => _dragY = anim.value);
|
||||
anim.addListener(listener);
|
||||
// whenComplete 在 dispose 取消 ticker 时也会触发,一律先判 mounted
|
||||
@@ -156,8 +173,8 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
-pos.dx * (scale - 1), -pos.dy * (scale - 1), 0, 1, //
|
||||
);
|
||||
}
|
||||
final anim =
|
||||
Matrix4Tween(begin: ctr.value, end: target).animate(CurvedAnimation(parent: _zoomCtr, curve: Curves.easeOut));
|
||||
final anim = Matrix4Tween(begin: ctr.value, end: target)
|
||||
.animate(CurvedAnimation(parent: _zoomCtr, curve: Curves.easeOut));
|
||||
void listener() => ctr.value = anim.value;
|
||||
anim.addListener(listener);
|
||||
_zoomCtr.forward(from: 0).whenComplete(() {
|
||||
@@ -195,7 +212,10 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
builder: (_, __) => Stack(
|
||||
children: [
|
||||
// 黑色背景,随拖动/退出渐隐
|
||||
Positioned.fill(child: ColoredBox(color: Colors.black.withValues(alpha: _bgOpacity * _exitFactor))),
|
||||
Positioned.fill(
|
||||
child: ColoredBox(
|
||||
color: Colors.black
|
||||
.withValues(alpha: _bgOpacity * _exitFactor))),
|
||||
GestureDetector(
|
||||
onLongPress: _saveCurrent, // 长按保存
|
||||
onDoubleTapDown: (d) => _tapPos = d.localPosition,
|
||||
@@ -209,7 +229,8 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
opacity: _enterCtr,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, _dragY),
|
||||
child: Transform.scale(scale: _dragScale, child: _pageView()),
|
||||
child:
|
||||
Transform.scale(scale: _dragScale, child: _pageView()),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -226,7 +247,9 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
if (_isSaving)
|
||||
const Positioned.fill(
|
||||
child: AbsorbPointer(
|
||||
child: ColoredBox(color: Color(0x80000000), child: LoadingAlertWidget(title: '保存中...')),
|
||||
child: ColoredBox(
|
||||
color: Color(0x80000000),
|
||||
child: LoadingAlertWidget(title: '保存中...')),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -272,7 +295,8 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
right: 20,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 2),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white, borderRadius: BorderRadius.circular(20)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -287,7 +311,8 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
: (isIncoming ? const Offset(0, -1) : const Offset(0, 1));
|
||||
return ClipRect(
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(begin: begin, end: Offset.zero).animate(anim),
|
||||
position: Tween<Offset>(begin: begin, end: Offset.zero)
|
||||
.animate(anim),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
@@ -295,10 +320,12 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
child: Text(
|
||||
'${_curIndex + 1}',
|
||||
key: ValueKey(_curIndex),
|
||||
style: const TextStyle(color: AppColors.actionRed, fontSize: 12),
|
||||
style:
|
||||
const TextStyle(color: AppColors.actionRed, fontSize: 12),
|
||||
),
|
||||
),
|
||||
Text('/${widget.images.length}', style: const TextStyle(color: Color(0xff3D3D3D), fontSize: 12)),
|
||||
Text('/${widget.images.length}',
|
||||
style: const TextStyle(color: Color(0xff3D3D3D), fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -331,8 +358,11 @@ class _ImageBrowserPageState extends State<ImageBrowserPage> with TickerProvider
|
||||
onTap: _saveCurrent,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), color: const Color(0xFFE57310)),
|
||||
child: const Text('保存到相册', style: TextStyle(color: Colors.white, fontSize: 14)),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: const Color(0xFFE57310)),
|
||||
child: const Text('保存到相册',
|
||||
style: TextStyle(color: Colors.white, fontSize: 14)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user