Browse Source

feat(translation): 为翻译模式选择器添加锚点动画效果

修改翻译模式选择器的显示方式,从底部弹窗改为从按钮位置展开的动画效果
计算拖拽按钮位置作为锚点,实现更自然的用户交互体验
weicu
lxm 1 year ago
parent
commit
3469f7c298
  1. 57
      lib/modules/translation/views/dialogs/translation_mode_selector.dart
  2. 11
      lib/modules/translation/views/translation_view.dart
  3. 9
      lib/modules/translation/views/widgets/draggable_floating_button.dart

57
lib/modules/translation/views/dialogs/translation_mode_selector.dart

@ -8,16 +8,57 @@ import '../../controllers/translation_controller.dart';
/// 翻译模式选择对话框 /// 翻译模式选择对话框
class TranslationModeSelector { class TranslationModeSelector {
/// 显示翻译模式选择器 /// 显示翻译模式选择器
static void show(bool isDarkMode) { static void show(bool isDarkMode, {Offset? anchor}) {
final controller = Get.find<TranslationController>(); final controller = Get.find<TranslationController>();
Get.bottomSheet( // 计算缩放对齐点(-1..1)。如果未提供 anchor,则从底部中间展开
BackdropFilter( Alignment alignment = Alignment.bottomCenter;
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), if (anchor != null) {
child: _buildTranslationModeSelector(controller), final Size screenSize = MediaQuery.of(Get.context!).size;
), final double ax = (anchor.dx / screenSize.width) * 2 - 1;
isScrollControlled: true, final double ay = (anchor.dy / screenSize.height) * 2 - 1;
backgroundColor: Colors.transparent, alignment = Alignment(ax, ay);
}
Get.generalDialog(
barrierLabel: 'translation_mode_selector',
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.35),
transitionDuration: const Duration(milliseconds: 250),
pageBuilder: (context, animation, secondaryAnimation) {
return const SizedBox.shrink();
},
transitionBuilder: (context, animation, secondaryAnimation, child) {
final curved = CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic);
return Stack(
children: [
// 模糊背景
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(color: Colors.transparent),
),
),
// 将缩放应用在一个全屏容器上,以便 alignment 以屏幕为参考
ScaleTransition(
alignment: alignment,
scale: Tween<double>(begin: 0.0, end: 1.0).animate(curved),
child: FadeTransition(
opacity: curved,
child: SizedBox.expand(
child: Align(
alignment: Alignment.bottomCenter,
child: _buildTranslationModeSelector(controller),
),
),
),
),
],
);
},
); );
} }

11
lib/modules/translation/views/translation_view.dart

@ -144,7 +144,16 @@ class TranslationView extends GetView<TranslationController> {
title: Obx(() => GestureDetector( title: Obx(() => GestureDetector(
onTap: () { onTap: () {
// 点击标题时弹出翻译模式选择器 // 点击标题时弹出翻译模式选择器
TranslationModeSelector.show(isDarkMode); // 从拖拽按钮位置作为锚点打开
final pos = controller.floatingButtonPosition.value;
final Size screen = MediaQuery.of(Get.context!).size;
// 已保存的是距右、下的偏移,需要换算为屏幕坐标中的中心点
final double buttonSize = 56.w;
final double left = screen.width - pos.dx - buttonSize;
final double top = screen.height - pos.dy - buttonSize;
final Offset anchor =
Offset(left + buttonSize / 2, top + buttonSize / 2);
TranslationModeSelector.show(isDarkMode, anchor: anchor);
}, },
child: Text( child: Text(
controller.currentModeTitle.value.tr, controller.currentModeTitle.value.tr,

9
lib/modules/translation/views/widgets/draggable_floating_button.dart

@ -108,6 +108,13 @@ class DraggableFloatingButton extends StatelessWidget {
/// 显示翻译模式选择器 /// 显示翻译模式选择器
void _showTranslationModeSelector(bool isDarkMode) { void _showTranslationModeSelector(bool isDarkMode) {
TranslationModeSelector.show(isDarkMode); // 使用保存的位置换算按钮中心点(与标题点击一致)
final pos = controller.floatingButtonPosition.value; // 距右、下的偏移
final Size screen = MediaQuery.of(Get.context!).size;
final double buttonSize = 56.w;
final double left = screen.width - pos.dx - buttonSize;
final double top = screen.height - pos.dy - buttonSize;
final Offset anchor = Offset(left + buttonSize / 2, top + buttonSize / 2);
TranslationModeSelector.show(isDarkMode, anchor: anchor);
} }
} }

Loading…
Cancel
Save