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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-19 21:35:31 -08:00
parent 7ae58e98e6
commit 48ba72ce89
1 changed files with 25 additions and 50 deletions

View File

@ -64,10 +64,6 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
bool _showCrossLine = false; bool _showCrossLine = false;
int _crossLineIndex = -1; int _crossLineIndex = -1;
//
bool _userHasPanned = false;
int _panStartIndex = 0; //
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -98,25 +94,23 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
/// ///
/// ///
/// - K线从左边开始排列 /// - K线从左边开始排列
/// - <= /// - <=
/// - K线在屏幕中心 /// - K线在屏幕中心
void _scrollToCenter() { void _scrollToCenter() {
if (widget.klines.isEmpty || _chartWidth == 0) return; 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; _scrollX = 0;
} else { } else {
// K线在屏幕中心的滚动位置 // 使K线在屏幕中心
// K线的中心位置 = (K线数量 - 0.5) * K线宽度 // startIndex = klines.length - halfScreenCount
final lastKlineCenter = (widget.klines.length - 0.5) * _candleWidth; // scrollX = startIndex * candleWidth
// = K线中心 - final int startIndex = widget.klines.length - halfScreenCount;
final targetScroll = lastKlineCenter - _chartWidth / 2; _scrollX = startIndex * _candleWidth;
//
final maxScroll = totalWidth - _chartWidth;
_scrollX = targetScroll.clamp(0.0, maxScroll);
} }
} }
@ -134,7 +128,6 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
// K 线 // K 线
if (oldWidget.klines.length != widget.klines.length) { if (oldWidget.klines.length != widget.klines.length) {
_initialized = false; _initialized = false;
_userHasPanned = false; //
_scrollX = 0; _scrollX = 0;
} }
} }
@ -507,13 +500,6 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
_prevCandleWidth = _candleWidth; _prevCandleWidth = _candleWidth;
_startFocalPoint = details.focalPoint; _startFocalPoint = details.focalPoint;
_isScaling = details.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; _startScrollX = _scrollX;
} }
@ -527,14 +513,13 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
final focalRatio = _startFocalPoint!.dx / _chartWidth; final focalRatio = _startFocalPoint!.dx / _chartWidth;
final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth; final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth;
final newFocalX = oldFocalIndex * newCandleWidth; final newFocalX = oldFocalIndex * newCandleWidth;
_scrollX = (newFocalX - focalRatio * _chartWidth).clamp( // maxScroll
0.0, final int halfScreenCount = (_chartWidth / 2 / newCandleWidth).ceil();
math.max(0.0, widget.klines.length * newCandleWidth - _chartWidth), final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * newCandleWidth);
); _scrollX = (newFocalX - focalRatio * _chartWidth).clamp(0.0, maxScroll);
} }
_candleWidth = newCandleWidth; _candleWidth = newCandleWidth;
_userHasPanned = true; //
} else if (_startFocalPoint != null) { } else if (_startFocalPoint != null) {
// //
// dx > 0 dx < 0 // dx > 0 dx < 0
@ -544,7 +529,6 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * _candleWidth); final double maxScroll = math.max(0.0, (widget.klines.length - halfScreenCount) * _candleWidth);
// dx>0scrollX // dx>0scrollX
_scrollX = (_startScrollX - dx).clamp(0.0, maxScroll); _scrollX = (_startScrollX - dx).clamp(0.0, maxScroll);
_userHasPanned = true;
// DEBUG: // DEBUG:
debugPrint('Pan: dx=$dx, scrollX=$_scrollX, maxScroll=$maxScroll, startIndex=${(_scrollX / _candleWidth).floor()}'); debugPrint('Pan: dx=$dx, scrollX=$_scrollX, maxScroll=$maxScroll, startIndex=${(_scrollX / _candleWidth).floor()}');
@ -592,6 +576,8 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth); return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth);
} }
// K线
final int fullScreenCount = (chartWidth / _candleWidth).ceil() + 1;
// K线 // K线
final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil(); final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil();
@ -604,26 +590,15 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
); );
} }
// // _scrollX
if (_userHasPanned) { final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, math.max(0, widget.klines.length - 1));
// _scrollX final int endIndex = math.min(startIndex + fullScreenCount, widget.klines.length);
final int fullScreenCount = (chartWidth / _candleWidth).ceil() + 1;
final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, widget.klines.length - 1); return _VisibleData(
final int endIndex = math.min(startIndex + fullScreenCount, widget.klines.length); klines: widget.klines.sublist(startIndex, endIndex),
return _VisibleData( startIndex: startIndex,
klines: widget.klines.sublist(startIndex, endIndex), candleWidth: _candleWidth,
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() { int _getVisibleCount() {