- 新增模型目录占位文件与翻译类型拆分 - 调整路由配置与桌面端窗口初始化 - 移除多处冗余图表配置项 - 重构右侧面板注册表与三栏布局组件 - 添加智能AppBar、拖拽书签等新功能组件 - 优化安卓编译配置与多平台插件注册 - 新增翻译覆盖率测试与共享组件 - 格式化代码与修复静态分析警告
119 lines
3.5 KiB
Dart
119 lines
3.5 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 可拖拽分割线
|
||
/// 创建时间: 2026-05-29
|
||
/// 更新时间: 2026-05-29
|
||
/// 作用: 宽屏分屏的分割线组件,支持拖拽调整比例、hover高亮、触觉反馈
|
||
/// 上次更新: 桌面端跳过HapticFeedback触觉反馈(桌面端无振动马达)
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
import '../theme/app_theme.dart';
|
||
import '../utils/platform/platform_utils.dart' as pu;
|
||
|
||
class SplitDivider extends StatefulWidget {
|
||
const SplitDivider({
|
||
required this.onPositionChanged,
|
||
this.currentPosition = 0.4,
|
||
this.minPosition = 0.2,
|
||
this.maxPosition = 0.7,
|
||
this.isVertical = true,
|
||
super.key,
|
||
});
|
||
|
||
final ValueChanged<double> onPositionChanged;
|
||
final double currentPosition;
|
||
final double minPosition;
|
||
final double maxPosition;
|
||
final bool isVertical;
|
||
|
||
@override
|
||
State<SplitDivider> createState() => _SplitDividerState();
|
||
}
|
||
|
||
class _SplitDividerState extends State<SplitDivider> {
|
||
bool _isHovering = false;
|
||
bool _isDragging = false;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final ext = AppTheme.ext(context);
|
||
final dividerColor = ext.textHint.withValues(alpha: 0.15);
|
||
final handleColor = _isDragging
|
||
? ext.accent.withValues(alpha: 0.8)
|
||
: _isHovering
|
||
? ext.accent.withValues(alpha: 0.5)
|
||
: ext.textHint.withValues(alpha: 0.3);
|
||
|
||
return MouseRegion(
|
||
onEnter: (_) => setState(() => _isHovering = true),
|
||
onExit: (_) => setState(() => _isHovering = false),
|
||
cursor: SystemMouseCursors.resizeColumn,
|
||
child: GestureDetector(
|
||
onHorizontalDragStart: _onDragStart,
|
||
onHorizontalDragUpdate: _onDragUpdate,
|
||
onHorizontalDragEnd: _onDragEnd,
|
||
onDoubleTap: () {
|
||
if (!pu.isDesktop) {
|
||
HapticFeedback.mediumImpact();
|
||
}
|
||
widget.onPositionChanged(0.4);
|
||
},
|
||
behavior: HitTestBehavior.translucent,
|
||
child: Container(
|
||
width: 24,
|
||
alignment: Alignment.center,
|
||
child: Container(
|
||
width: 1,
|
||
color: dividerColor,
|
||
child: Center(
|
||
child: AnimatedContainer(
|
||
duration: const Duration(milliseconds: 150),
|
||
curve: Curves.easeOut,
|
||
width: _isDragging
|
||
? 6
|
||
: _isHovering
|
||
? 4
|
||
: 4,
|
||
height: 32,
|
||
decoration: BoxDecoration(
|
||
color: handleColor,
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _onDragStart(DragStartDetails details) {
|
||
setState(() => _isDragging = true);
|
||
if (!pu.isDesktop) {
|
||
HapticFeedback.selectionClick();
|
||
}
|
||
}
|
||
|
||
void _onDragUpdate(DragUpdateDetails details) {
|
||
final box = context.findRenderObject() as RenderBox;
|
||
final parent = box.parent as RenderBox;
|
||
final totalWidth = parent.size.width;
|
||
if (totalWidth <= 0) return;
|
||
|
||
final localX =
|
||
details.globalPosition.dx - parent.localToGlobal(Offset.zero).dx;
|
||
final newPosition = (localX / totalWidth).clamp(
|
||
widget.minPosition,
|
||
widget.maxPosition,
|
||
);
|
||
|
||
widget.onPositionChanged(newPosition);
|
||
}
|
||
|
||
void _onDragEnd(DragEndDetails details) {
|
||
setState(() => _isDragging = false);
|
||
}
|
||
}
|