From 928d6c8df284b556bcc59483020c300c97d921dc Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 19 Jan 2026 06:31:24 -0800 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20=E4=BC=98=E5=8C=96K?= =?UTF-8?q?=E7=BA=BF=E5=9B=BE=E6=98=BE=E7=A4=BA=E4=B8=8E=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: 1. 隐藏技术指标 - 暂时隐藏MA/EMA/BOLL主图指标和MACD/KDJ/RSI副图指标 - 保留全部代码,便于未来恢复(取消注释即可) - 调整高度分配:主图75%、成交量25% 2. 修复单指滑动(pan)问题 - 移除错误的scale阈值检测 `(details.scale - 1.0).abs() > 0.01` - 改用 `pointerCount > 1` 区分单指滑动和双指缩放 - 单指滑动现可正常左右拖动K线图 3. 优化首次加载显示 - 新增 `_initialized` 标志控制初始化时机 - 新增 `_initializeCandleWidth()` 方法动态计算K线宽度 - K线首次加载时自动填满可视区域宽度 - 数据量变化时自动重新初始化 技术细节: - 使用 LayoutBuilder 获取实际图表宽度后再初始化 - 通过 postFrameCallback 确保在布局完成后执行初始化 - K线宽度限制在 3.0-30.0 像素范围内 Co-Authored-By: Claude Opus 4.5 --- .../kline_chart/kline_chart_widget.dart | 129 ++++++++++-------- 1 file changed, 75 insertions(+), 54 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 796b594a..edc52c45 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 @@ -50,6 +50,7 @@ class _KlineChartWidgetState extends State { Offset? _startFocalPoint; bool _isScaling = false; double _chartWidth = 0.0; // 缓存图表宽度 + bool _initialized = false; // 是否已初始化K线宽度 // K线宽度范围 static const double _minCandleWidth = 3.0; @@ -66,10 +67,24 @@ class _KlineChartWidgetState extends State { @override void initState() { super.initState(); - // 初始化偏移量到最右边(显示最新数据) - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToEnd(); - }); + // 初始化会在 LayoutBuilder 中完成 + } + + /// 初始化 K 线宽度,让所有 K 线占满屏幕 + void _initializeCandleWidth(double chartWidth) { + if (_initialized || widget.klines.isEmpty || chartWidth == 0) return; + + _initialized = true; + _chartWidth = chartWidth; + + // 计算让所有 K 线占满屏幕的宽度 + final idealWidth = chartWidth / widget.klines.length; + // 限制在合理范围内 + _candleWidth = idealWidth.clamp(_minCandleWidth, _maxCandleWidth); + _prevCandleWidth = _candleWidth; + + // 滚动到最右边(显示最新数据) + _scrollToEnd(); } void _scrollToEnd() { @@ -77,16 +92,15 @@ class _KlineChartWidgetState extends State { // 滚动到最右边(显示最新数据) final totalWidth = widget.klines.length * _candleWidth; final maxScroll = math.max(0.0, totalWidth - _chartWidth); - setState(() { - _scrollX = maxScroll; - }); + _scrollX = maxScroll; } @override void didUpdateWidget(KlineChartWidget oldWidget) { super.didUpdateWidget(oldWidget); + // 当 K 线数据变化时,重新初始化 if (oldWidget.klines.length != widget.klines.length) { - _scrollToEnd(); + _initialized = false; } } @@ -151,8 +165,8 @@ class _KlineChartWidgetState extends State { body: SafeArea( child: Column( children: [ - // 指标选择器 - _buildIndicatorSelector(), + // 指标选择器(暂时隐藏,需要时取消注释) + // _buildIndicatorSelector(), // K线图区域(占据大部分空间) Expanded(child: _buildChartArea()), // 时间周期选择 @@ -170,22 +184,23 @@ class _KlineChartWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // 指标切换(简化版) - Row( - children: [ - _buildIndicatorChip('MA', _selectedMainIndicator == 0, () { - setState(() => _selectedMainIndicator = 0); - }), - const SizedBox(width: 8), - _buildIndicatorChip('MACD', _selectedSubIndicator == 0, () { - setState(() => _selectedSubIndicator = 0); - }), - const SizedBox(width: 8), - _buildIndicatorChip('KDJ', _selectedSubIndicator == 1, () { - setState(() => _selectedSubIndicator = 1); - }), - ], - ), + // 指标切换(暂时隐藏,需要时取消注释) + // Row( + // children: [ + // _buildIndicatorChip('MA', _selectedMainIndicator == 0, () { + // setState(() => _selectedMainIndicator = 0); + // }), + // const SizedBox(width: 8), + // _buildIndicatorChip('MACD', _selectedSubIndicator == 0, () { + // setState(() => _selectedSubIndicator = 0); + // }), + // const SizedBox(width: 8), + // _buildIndicatorChip('KDJ', _selectedSubIndicator == 1, () { + // setState(() => _selectedSubIndicator = 1); + // }), + // ], + // ), + const SizedBox(), // 占位 // 全屏按钮 IconButton( icon: Icon( @@ -288,6 +303,14 @@ class _KlineChartWidgetState extends State { // 缓存图表宽度用于滚动计算 _chartWidth = chartWidth; + // 初始化 K 线宽度(首次加载时让 K 线占满屏幕) + if (!_initialized && widget.klines.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _initializeCandleWidth(chartWidth); + if (mounted) setState(() {}); + }); + } + if (widget.klines.isEmpty) { return Center( child: Column( @@ -313,16 +336,16 @@ class _KlineChartWidgetState extends State { final bollData = processor.calculateBOLL(); final emaData = processor.calculateEMA([5, 10, 20]); - // 计算各区域高度 - 全屏模式给主图更多空间 + // 计算各区域高度 - 隐藏副图指标后重新分配空间 + // 主图:75%,成交量:25%(隐藏副图指标) final mainChartHeight = widget.showVolume - ? (widget.isFullScreen ? chartHeight * 0.65 : chartHeight * 0.6) - : (widget.isFullScreen ? chartHeight * 0.75 : chartHeight * 0.7); + ? chartHeight * 0.75 + : chartHeight * 1.0; final volumeHeight = widget.showVolume - ? (widget.isFullScreen ? chartHeight * 0.12 : chartHeight * 0.2) + ? chartHeight * 0.25 : 0.0; - final indicatorHeight = widget.isFullScreen - ? (widget.showVolume ? chartHeight * 0.23 : chartHeight * 0.25) - : (widget.showVolume ? chartHeight * 0.2 : chartHeight * 0.3); + // 副图指标高度(暂时隐藏,需要时取消注释并恢复下方副图代码) + // final indicatorHeight = chartHeight * 0.20; return Column( children: [ @@ -382,21 +405,21 @@ class _KlineChartWidgetState extends State { ), ), ), - // 副图指标(MACD/KDJ/RSI) - SizedBox( - height: indicatorHeight, - child: CustomPaint( - size: Size(chartWidth, indicatorHeight), - painter: KlineIndicatorPainter( - indicatorType: _selectedSubIndicator, - macdData: _selectedSubIndicator == 0 ? _getVisibleMacdData(macdData, visibleData.startIndex) : null, - kdjData: _selectedSubIndicator == 1 ? _getVisibleKdjData(kdjData, visibleData.startIndex) : null, - rsiData: _selectedSubIndicator == 2 ? _getVisibleRsiData(rsiData, visibleData.startIndex) : null, - crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, - candleWidth: visibleData.candleWidth, - ), - ), - ), + // 副图指标(MACD/KDJ/RSI)- 暂时隐藏,需要时取消注释 + // SizedBox( + // height: indicatorHeight, + // child: CustomPaint( + // size: Size(chartWidth, indicatorHeight), + // painter: KlineIndicatorPainter( + // indicatorType: _selectedSubIndicator, + // macdData: _selectedSubIndicator == 0 ? _getVisibleMacdData(macdData, visibleData.startIndex) : null, + // kdjData: _selectedSubIndicator == 1 ? _getVisibleKdjData(kdjData, visibleData.startIndex) : null, + // rsiData: _selectedSubIndicator == 2 ? _getVisibleRsiData(rsiData, visibleData.startIndex) : null, + // crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, + // candleWidth: visibleData.candleWidth, + // ), + // ), + // ), ], ); }, @@ -453,8 +476,8 @@ class _KlineChartWidgetState extends State { void _onScaleUpdate(ScaleUpdateDetails details) { setState(() { // 两指缩放:调整K线宽度,围绕焦点缩放 - if (_isScaling || (details.scale - 1.0).abs() > 0.01) { - _isScaling = true; + // 只有在明确是缩放模式(多指触摸)时才进行缩放 + if (_isScaling) { final newCandleWidth = (_prevCandleWidth * details.scale).clamp(_minCandleWidth, _maxCandleWidth); // 围绕焦点缩放:调整滚动位置以保持焦点处的K线位置不变 @@ -469,10 +492,8 @@ class _KlineChartWidgetState extends State { } _candleWidth = newCandleWidth; - } - - // 单指平移(非缩放模式时)- 直接使用像素位移 - if (!_isScaling && _startFocalPoint != null) { + } 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);