diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart index 0cd5b451..3c2150cb 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart @@ -64,6 +64,10 @@ class _KlineChartWidgetState extends State { bool _showCrossLine = false; int _crossLineIndex = -1; + // 平移控制:用户是否已手动平移 + bool _userHasPanned = false; + int _panStartIndex = 0; // 平移开始时的起始索引 + @override void initState() { super.initState(); @@ -130,6 +134,8 @@ class _KlineChartWidgetState extends State { // 当 K 线数据变化时,重新初始化 if (oldWidget.klines.length != widget.klines.length) { _initialized = false; + _userHasPanned = false; // 重置平移状态 + _scrollX = 0; } } @@ -496,23 +502,27 @@ class _KlineChartWidgetState extends State { ); } - // 手势处理 - 大厂标准做法:基于像素的滚动和缩放 + // 手势处理 void _onScaleStart(ScaleStartDetails details) { _prevCandleWidth = _candleWidth; - _startScrollX = _scrollX; _startFocalPoint = details.focalPoint; - // 检测是否为两指缩放(pointerCount > 1表示多指触摸) _isScaling = details.pointerCount > 1; + + // 如果是首次平移,需要初始化 _scrollX + if (!_userHasPanned && !_isScaling) { + final int halfScreenCount = (_chartWidth / 2 / _candleWidth).ceil(); + final int startIndex = math.max(0, widget.klines.length - halfScreenCount); + _scrollX = startIndex * _candleWidth; + } + _startScrollX = _scrollX; } void _onScaleUpdate(ScaleUpdateDetails details) { setState(() { - // 两指缩放:调整K线宽度,围绕焦点缩放 - // 只有在明确是缩放模式(多指触摸)时才进行缩放 + // 两指缩放 if (_isScaling) { final newCandleWidth = (_prevCandleWidth * details.scale).clamp(_minCandleWidth, _maxCandleWidth); - // 围绕焦点缩放:调整滚动位置以保持焦点处的K线位置不变 if (_startFocalPoint != null && _chartWidth > 0) { final focalRatio = _startFocalPoint!.dx / _chartWidth; final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth; @@ -524,12 +534,15 @@ class _KlineChartWidgetState extends State { } _candleWidth = newCandleWidth; + _userHasPanned = true; // 缩放后也标记为已操作 } else if (_startFocalPoint != null) { - // 单指平移 - 直接使用像素位移 + // 单指平移 final dx = details.focalPoint.dx - _startFocalPoint!.dx; - final totalWidth = widget.klines.length * _candleWidth; - final maxScroll = math.max(0.0, totalWidth - _chartWidth); + // 最大滚动位置:让最新K线在屏幕中心 + final int halfScreenCount = (_chartWidth / 2 / _candleWidth).ceil(); + final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * _candleWidth); _scrollX = (_startScrollX - dx).clamp(0.0, maxScroll); + _userHasPanned = true; } }); } @@ -569,7 +582,6 @@ class _KlineChartWidgetState extends State { } // 数据处理 - 计算要显示的K线数据 - // 逻辑:最新K线在屏幕中心,只取中心到左边缘能显示的数量 _VisibleData _getVisibleData(double chartWidth) { if (widget.klines.isEmpty || chartWidth == 0) { return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth); @@ -578,7 +590,7 @@ class _KlineChartWidgetState extends State { // 计算屏幕中心到左边缘能显示多少根K线 final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil(); - // 数据量不够半屏时,显示全部 + // 数据量不够半屏时,显示全部,从左开始 if (widget.klines.length <= halfScreenCount) { return _VisibleData( klines: widget.klines, @@ -587,13 +599,26 @@ class _KlineChartWidgetState extends State { ); } - // 数据量足够时,只取最近的 halfScreenCount 根,让最新那根在屏幕中心 - final int startIndex = widget.klines.length - halfScreenCount; - return _VisibleData( - klines: widget.klines.sublist(startIndex), - startIndex: startIndex, - candleWidth: _candleWidth, - ); + // 数据量足够时 + if (_userHasPanned) { + // 用户已平移,根据 _scrollX 计算起始索引 + final int fullScreenCount = (chartWidth / _candleWidth).ceil() + 1; + final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, widget.klines.length - 1); + final int endIndex = math.min(startIndex + fullScreenCount, widget.klines.length); + return _VisibleData( + klines: widget.klines.sublist(startIndex, endIndex), + startIndex: startIndex, + candleWidth: _candleWidth, + ); + } else { + // 初始状态:只取最近的 halfScreenCount 根,让最新那根在屏幕中心 + final int startIndex = widget.klines.length - halfScreenCount; + return _VisibleData( + klines: widget.klines.sublist(startIndex), + startIndex: startIndex, + candleWidth: _candleWidth, + ); + } } int _getVisibleCount() {