feat(kline): 支持左右平移查看历史K线
- 初始状态:最新K线在屏幕中心(保持原有逻辑不变) - 用户平移后可查看全部历史数据 - 切换周期时重置平移状态 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f149c2a06a
commit
684367941d
|
|
@ -64,6 +64,10 @@ 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();
|
||||||
|
|
@ -130,6 +134,8 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,23 +502,27 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手势处理 - 大厂标准做法:基于像素的滚动和缩放
|
// 手势处理
|
||||||
void _onScaleStart(ScaleStartDetails details) {
|
void _onScaleStart(ScaleStartDetails details) {
|
||||||
_prevCandleWidth = _candleWidth;
|
_prevCandleWidth = _candleWidth;
|
||||||
_startScrollX = _scrollX;
|
|
||||||
_startFocalPoint = details.focalPoint;
|
_startFocalPoint = details.focalPoint;
|
||||||
// 检测是否为两指缩放(pointerCount > 1表示多指触摸)
|
|
||||||
_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScaleUpdate(ScaleUpdateDetails details) {
|
void _onScaleUpdate(ScaleUpdateDetails details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// 两指缩放:调整K线宽度,围绕焦点缩放
|
// 两指缩放
|
||||||
// 只有在明确是缩放模式(多指触摸)时才进行缩放
|
|
||||||
if (_isScaling) {
|
if (_isScaling) {
|
||||||
final newCandleWidth = (_prevCandleWidth * details.scale).clamp(_minCandleWidth, _maxCandleWidth);
|
final newCandleWidth = (_prevCandleWidth * details.scale).clamp(_minCandleWidth, _maxCandleWidth);
|
||||||
|
|
||||||
// 围绕焦点缩放:调整滚动位置以保持焦点处的K线位置不变
|
|
||||||
if (_startFocalPoint != null && _chartWidth > 0) {
|
if (_startFocalPoint != null && _chartWidth > 0) {
|
||||||
final focalRatio = _startFocalPoint!.dx / _chartWidth;
|
final focalRatio = _startFocalPoint!.dx / _chartWidth;
|
||||||
final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth;
|
final oldFocalIndex = (_startScrollX + _startFocalPoint!.dx) / _prevCandleWidth;
|
||||||
|
|
@ -524,12 +534,15 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_candleWidth = newCandleWidth;
|
_candleWidth = newCandleWidth;
|
||||||
|
_userHasPanned = true; // 缩放后也标记为已操作
|
||||||
} else if (_startFocalPoint != null) {
|
} else if (_startFocalPoint != null) {
|
||||||
// 单指平移 - 直接使用像素位移
|
// 单指平移
|
||||||
final dx = details.focalPoint.dx - _startFocalPoint!.dx;
|
final dx = details.focalPoint.dx - _startFocalPoint!.dx;
|
||||||
final totalWidth = widget.klines.length * _candleWidth;
|
// 最大滚动位置:让最新K线在屏幕中心
|
||||||
final maxScroll = math.max(0.0, totalWidth - _chartWidth);
|
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);
|
_scrollX = (_startScrollX - dx).clamp(0.0, maxScroll);
|
||||||
|
_userHasPanned = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -569,7 +582,6 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据处理 - 计算要显示的K线数据
|
// 数据处理 - 计算要显示的K线数据
|
||||||
// 逻辑:最新K线在屏幕中心,只取中心到左边缘能显示的数量
|
|
||||||
_VisibleData _getVisibleData(double chartWidth) {
|
_VisibleData _getVisibleData(double chartWidth) {
|
||||||
if (widget.klines.isEmpty || chartWidth == 0) {
|
if (widget.klines.isEmpty || chartWidth == 0) {
|
||||||
return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth);
|
return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth);
|
||||||
|
|
@ -578,7 +590,7 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
||||||
// 计算屏幕中心到左边缘能显示多少根K线
|
// 计算屏幕中心到左边缘能显示多少根K线
|
||||||
final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil();
|
final int halfScreenCount = (chartWidth / 2 / _candleWidth).ceil();
|
||||||
|
|
||||||
// 数据量不够半屏时,显示全部
|
// 数据量不够半屏时,显示全部,从左开始
|
||||||
if (widget.klines.length <= halfScreenCount) {
|
if (widget.klines.length <= halfScreenCount) {
|
||||||
return _VisibleData(
|
return _VisibleData(
|
||||||
klines: widget.klines,
|
klines: widget.klines,
|
||||||
|
|
@ -587,13 +599,26 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据量足够时,只取最近的 halfScreenCount 根,让最新那根在屏幕中心
|
// 数据量足够时
|
||||||
final int startIndex = widget.klines.length - halfScreenCount;
|
if (_userHasPanned) {
|
||||||
return _VisibleData(
|
// 用户已平移,根据 _scrollX 计算起始索引
|
||||||
klines: widget.klines.sublist(startIndex),
|
final int fullScreenCount = (chartWidth / _candleWidth).ceil() + 1;
|
||||||
startIndex: startIndex,
|
final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, widget.klines.length - 1);
|
||||||
candleWidth: _candleWidth,
|
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() {
|
int _getVisibleCount() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue