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:
parent
7ae58e98e6
commit
48ba72ce89
|
|
@ -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>0,scrollX 减小,显示更早的数据
|
// 向右滑 dx>0,scrollX 减小,显示更早的数据
|
||||||
_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() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue