The previous article talked about the related process of flutter customization.
Continue to practice flutter's custom K line today:
We can implement a simple candlestick chart interface by customizing Painter:
- Create a custom Painter for drawing candlestick charts:
import 'dart:ui';
import 'package:flutter/material.dart';
class KLinePainter extends CustomPainter {
final List<dynamic> data;
final double itemWidth;
final double scaleFactor;
KLinePainter({required this.data, required this.itemWidth, required this.scaleFactor});
@override
void paint(Canvas canvas, Size size) {
// 设置画笔
final linePaint = Paint()
..color = Colors.grey
..strokeWidth = 0.5;
final textPainter = TextPainter(textDirection: TextDirection.ltr, textAlign: TextAlign.left);
// 计算价格范围和最大成交量
num highestPrice = double.minPositive;
num lowestPrice = double.maxFinite;
num highestVolume = double.minPositive;
for (var item in data) {
if (item['high'] > highestPrice) {
highestPrice = item['high'];
}
if (item['low'] < lowestPrice) {
lowestPrice = item['low'];
}
if (item['vol'] > highestVolume) {
highestVolume = item['vol'];
}
}
// 计算价格和成交量的缩放比例
double priceScale = (size.height - 20) / (highestPrice - lowestPrice);
double volumeScale = size.height * 0.2 / highestVolume;
// 绘制K线图
for (int i = 0; i < data.length; i++) {
var item = data[i];
double open = (item['open'] - lowestPrice) * priceScale;
double close = (item['close'] - lowestPrice) * priceScale;
double high = (item['high'] - lowestPrice) * priceScale;
double low = (item['low'] - lowestPrice) * priceScale;
double vol = item['vol'] * volumeScale;
// 设置画笔颜色
linePaint.color = close >= open ? Colors.green : Colors.red;
// 绘制实体
double halfWidth = itemWidth * scaleFactor / 2;
double centerX = i * itemWidth * scaleFactor + halfWidth;
canvas.drawRect(
Rect.fromCenter(
center: Offset(centerX, size.height - (open + close) / 2 - 10),
width: itemWidth * scaleFactor,
height: (open - close).abs(),
),
linePaint,
);
// 绘制上下影线
canvas.drawLine(Offset(centerX, size.height - high - 10), Offset(centerX, size.height - low - 10), linePaint);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
- Create a StatefulWidget that handles zooming and scrolling:
class KLineChart extends StatefulWidget {
final List<dynamic> data;
KLineChart({required this.data});
@override
_KLineChartState createState() => _KLineChartState();
}
class _KLineChartState extends State<KLineChart> {
double _scaleFactor = 1.0;
double _itemWidth = 10.0;
late double _baseScaleFactor;
@override
void initState() {
super.initState();
_baseScaleFactor = _scaleFactor;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (details) {
setState(() {
// 修改这里,减小缩放速度
_scaleFactor = _baseScaleFactor * (1.0 + (details.scale - 1.0) / 2);
});
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: CustomPaint(
size: Size(widget.data.length * _itemWidth * _scaleFactor, MediaQuery.of(context).size.height),
painter: KLinePainter(data: widget.data, itemWidth: _itemWidth, scaleFactor: _scaleFactor),
),
),
);
}
}
- Where you need to use the K-line chart, call
KLineChart
the component and pass in the K-line data:
List<dynamic> kLineData = [
// 你的K线数据
];
KLineChart(data: kLineData);
In this way, you can realize a simple K-line interface that can be slid left and right, zoomed in and out.
Here we look at the effect of the realization:
I first simulated some K-line data:
List<dynamic> generateKLineData(int count) {
List<dynamic> data = [];
Random random = Random();
double open = 100.0;
double close, high, low;
num volume;
for (int i = 0; i < count; i++) {
close = open + random.nextDouble() * 20 - 10;
high = max(open, close) + random.nextDouble() * 5;
low = min(open, close) - random.nextDouble() * 5;
volume = random.nextInt(10000) + 1000;
data.add({
'open': open,
'close': close,
'high': high,
'low': low,
'vol': volume,
});
open = close;
}
return data;
}
Then quote directly:
SliverToBoxAdapter(
child: SizedBox(
height: 300.w,
child: KLineChart(data: generateKLineData(100)),
),
)
After running, the effect is as follows:
It can be slid left and right, scalable, and only includes basic functions. It really needs time-sharing charts, various indicators, and even custom indicator drawing, etc. You can further improve and optimize according to your needs.
Technologists work together every day, let's encourage each other!