From 48ba72ce89a37bb6d314bb88905ff187c2396590 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 19 Jan 2026 21:35:31 -0800 Subject: [PATCH] fix(kline): simplify pan logic - scrollX now always controls view position - Removed _userHasPanned flag and special handling - _scrollX is initialized in _scrollToCenter() at startup - Pan gesture directly modifies _scrollX - _getVisibleData() always uses _scrollX to calculate visible range Co-Authored-By: Claude Opus 4.5 --- .../kline_chart/kline_chart_widget.dart | 75 +++++++------------ 1 file changed, 25 insertions(+), 50 deletions(-) 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 ad7d0568..49d9faa9 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,10 +64,6 @@ class _KlineChartWidgetState extends State { bool _showCrossLine = false; int _crossLineIndex = -1; - // 平移控制:用户是否已手动平移 - bool _userHasPanned = false; - int _panStartIndex = 0; // 平移开始时的起始索引 - @override void initState() { super.initState(); @@ -98,25 +94,23 @@ class _KlineChartWidgetState extends State { /// /// 计算逻辑: /// - K线从左边开始排列 - /// - 数据不够时(总宽度 <= 屏幕宽度),不滚动,有多少显示多少 + /// - 数据不够时(总宽度 <= 屏幕宽度的一半),不滚动,有多少显示多少 /// - 数据量大时,计算滚动位置让最新K线在屏幕中心 void _scrollToCenter() { if (widget.klines.isEmpty || _chartWidth == 0) return; - final totalWidth = widget.klines.length * _candleWidth; + // 计算屏幕中心到左边缘能显示多少根K线 + final int halfScreenCount = (_chartWidth / 2 / _candleWidth).ceil(); - if (totalWidth <= _chartWidth) { - // 数据不够,不滚动,从左开始显示 + if (widget.klines.length <= halfScreenCount) { + // 数据不够半屏,不滚动,从左开始显示 _scrollX = 0; } else { - // 数据量大,计算让最新K线在屏幕中心的滚动位置 - // 最新K线的中心位置 = (K线数量 - 0.5) * 单根K线宽度 - final lastKlineCenter = (widget.klines.length - 0.5) * _candleWidth; - // 目标滚动位置 = 最新K线中心 - 屏幕宽度的一半 - final targetScroll = lastKlineCenter - _chartWidth / 2; - // 限制在有效滚动范围内 - final maxScroll = totalWidth - _chartWidth; - _scrollX = targetScroll.clamp(0.0, maxScroll); + // 数据量足够,滚动使最新K线在屏幕中心 + // startIndex = klines.length - halfScreenCount + // scrollX = startIndex * candleWidth + final int startIndex = widget.klines.length - halfScreenCount; + _scrollX = startIndex * _candleWidth; } } @@ -134,7 +128,6 @@ class _KlineChartWidgetState extends State { // 当 K 线数据变化时,重新初始化 if (oldWidget.klines.length != widget.klines.length) { _initialized = false; - _userHasPanned = false; // 重置平移状态 _scrollX = 0; } } @@ -507,13 +500,6 @@ class _KlineChartWidgetState extends State { _prevCandleWidth = _candleWidth; _startFocalPoint = details.focalPoint; _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; } @@ -527,14 +513,13 @@ class _KlineChartWidgetState extends State { final focalRatio = _startFocalPoint!.dx / _chartWidth; final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth; final newFocalX = oldFocalIndex * newCandleWidth; - _scrollX = (newFocalX - focalRatio * _chartWidth).clamp( - 0.0, - math.max(0.0, widget.klines.length * newCandleWidth - _chartWidth), - ); + // 缩放后重新计算maxScroll + final int halfScreenCount = (_chartWidth / 2 / newCandleWidth).ceil(); + final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * newCandleWidth); + _scrollX = (newFocalX - focalRatio * _chartWidth).clamp(0.0, maxScroll); } _candleWidth = newCandleWidth; - _userHasPanned = true; // 缩放后也标记为已操作 } else if (_startFocalPoint != null) { // 单指平移 // dx > 0 向右滑(看更早历史),dx < 0 向左滑(看更新数据) @@ -544,7 +529,6 @@ class _KlineChartWidgetState extends State { final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * _candleWidth); // 向右滑 dx>0,scrollX 减小,显示更早的数据 _scrollX = (_startScrollX - dx).clamp(0.0, maxScroll); - _userHasPanned = true; // DEBUG: 打印滚动信息 debugPrint('Pan: dx=$dx, scrollX=$_scrollX, maxScroll=$maxScroll, startIndex=${(_scrollX / _candleWidth).floor()}'); @@ -592,6 +576,8 @@ class _KlineChartWidgetState extends State { return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth); } + // 计算屏幕能显示多少根K线 + final int fullScreenCount = (chartWidth / _candleWidth).ceil() + 1; // 计算屏幕中心到左边缘能显示多少根K线 final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil(); @@ -604,26 +590,15 @@ class _KlineChartWidgetState extends State { ); } - // 数据量足够时 - 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, - ); - } + // 数据量足够时,根据 _scrollX 计算起始索引 + final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, math.max(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, + ); } int _getVisibleCount() {