使用插件:k_chart,
k_chart: ^0.7.1
首页进入详情需要传对应币种的symbol:BTCUSDT
.onTap((){
Get.toNamed('/k_chart_page', arguments: {
'symbol': item.symbol});
}),
下面主要记录下K线的实现过程
1、定义model
模型,前端需要传给后台的
{
symbol: BTCUSDT, // 币种
interval: 1m, // 时间
limit: 500, // 条数
}
import 'package:xiaoshukeji/common/index.dart';
class HomeKlineReq {
String? symbol;
String? interval;
int? limit;
HomeKlineReq({
this.symbol, this.interval, this.limit});
factory HomeKlineReq.fromJson(Map<String, dynamic> json) => HomeKlineReq(
symbol: DataUtils.toStr(json['symbol']),
interval: DataUtils.toStr(json['interval']),
limit: DataUtils.toInt(json['limit']),
);
Map<String, dynamic> toJson() => {
'symbol': symbol,
'interval': interval,
'limit': limit,
};
}
2、后台返回数据格式处理,数据来源于火币网
[
[
1499040000000, // 开盘时间
"0.01634790", // 开盘价
"0.80000000", // 最高价
"0.01575800", // 最低价
"0.01577100", // 收盘价(当前K线未结束的即为最新价)
"148976.11427815", // 成交量
1499644799999, // 收盘时间
"2434.19055334", // 成交额
308, // 成交笔数
"1756.87402397", // 主动买入成交量
"28.46694368", // 主动买入成交额
"17928899.62484339" // 请忽略该参数
]
]
这个格式并不能直接在k_chart中使用,需要自己处理下数据
在flutter_k_chart演示demo中,找到了官方的数据默认格式
// kline_1M.json id=时间戳
[{
"amount": 46197.237447628898264056,
"open": 7121.890000000000000000,
"close": 7102.900000000000000000,
"high": 7380.000000000000000000,
"id": 1574438400,
"count": 296348,
"low": 7094.690000000000000000,
"vol": 333578883.234124950380397343870000000000000000
},
{
"id": 1574352000,
"open": 7628.590000000000000000,
"close": 7121.440000000000000000,
"high": 7725.090000000000000000,
"low": 6790.000000000000000000,
"vol": 664521165.622325248991851131,
"amount": 91327.402878725150140416,
"count": 627969
}
]
按照这个格式制作model
import 'package:xiaoshukeji/common/index.dart';
class KlineData {
final double amount;
final double open;
final double close;
final double high;
final int time;
final int count;
final double low;
final double vol;
KlineData({
required this.amount,
required this.open,
required this.close,
required this.high,
required this.time,
required this.count,
required this.low,
required this.vol,
});
factory KlineData.fromJson(List<dynamic> json) {
return KlineData(
amount: DataUtils.toDouble(json[5]) ?? 0, // 成交量
open: DataUtils.toDouble(json[1]) ?? 0, // 开盘价
close: DataUtils.toDouble(json[4]) ?? 0, // 收盘价
high: DataUtils.toDouble(json[2]) ?? 0, // 最高价
time: DataUtils.toInt(json[0]) ?? 0, // 时间
count: DataUtils.toInt(json[8]) ?? 0, // 笔数
low: DataUtils.toDouble(json[3]) ?? 0, // 最低价
vol: DataUtils.toDouble(json[7]) ?? 0, // 24小时成交量
);
}
Map<String, dynamic> toJson() => {
'amount': amount,
'open': open,
'close': close,
'high': high,
'time': time,
'count': count,
'low': low,
'vol': vol,
};
}
// 接口请求,获取K线数据
static Future<List<KlineData>> klineData(HomeKlineReq data) async {
var res = await WPHttpService.to.get(
'/api/markets/api/v3/klines',
params: data.toJson(),
);
var klineData = res.data;
if (klineData != null && klineData is List && klineData.isNotEmpty) {
return klineData.map((e) => KlineData.fromJson(e)).toList();
}
return [];
}
3、controller
控制器获取数据
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:k_chart/flutter_k_chart.dart';
import 'dart:async';
import 'package:xiaoshukeji/common/index.dart';
class KChartController extends GetxController {
KChartController();
// 币种
String symbol = '';
// 头部24小时数据
HomeKlineHeadPriceModel headPrice = HomeKlineHeadPriceModel();
// K线数据
List<KLineEntity> datas = []; // 初始化为空列表
// 主图指标
MainState mainState = MainState.MA;
// 副图指标
SecondaryState secondaryState = SecondaryState.MACD;
// 当前选中的时间周期
String period = '1m'; // 默认1分钟
// 定时器
Timer? _timer;
//
// 初始化数据
@override
void onInit() {
super.onInit();
_initData();
_startTimer();
}
@override
void onClose() {
_timer?.cancel();
super.onClose();
}
//
// 初始化数据
void _initData() async {
symbol = Get.arguments?['symbol'] ?? '';
fetchKLineHeadPrice();
fetchKLineData();
}
// 获取K线头部24小时数据
Future<void> fetchKLineHeadPrice() async {
headPrice = await HomeApi.klineHeadPrice(symbol);
update(["k_chart"]);
}
// 获取K线数据
Future<void> fetchKLineData() async {
try {
// 根据不同周期读取不同的json文件
List<KlineData> klineData = await HomeApi.klineData(HomeKlineReq(symbol: symbol, interval: period, limit: 1000));
datas = klineData.map((item) {
double open = item.open;
double close = item.close;
double change = close - open; // change: 价格变化,通常是收盘价与开盘价的差值
double ratio = (change / open) * 100; // ratio: 价格变化百分比
return KLineEntity.fromCustom(
time: item.time,
open: open,
high: item.high,
low: item.low,
close: close,
vol: item.vol,
amount: item.amount,
change: change,
ratio: ratio,
);
}).toList();
DataUtil.calculate(datas);
update(["k_chart"]);
} catch (e) {
print('获取K线数据失败: $e');
}
}
// 启动定时器
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 5), (timer) {
if (datas.isEmpty) return;
fetchKLineData();
});
}
// 切换时间周期
void changePeriod(String newPeriod) {
period = newPeriod;
print('切换周期: $period');
fetchKLineData(); // 重新获取对应周期的数据
update(["k_chart"]);
}
// 切换主图指标
void changeMainState(MainState state) {
mainState = state;
update(["k_chart"]);
}
// 切换副图指标
void changeSecondaryState(SecondaryState state) {
secondaryState = state;
update(["k_chart"]);
}
// 获取价格变化颜色
Color getPriceChangeColor(String? percent) {
if (percent == null) return AppTheme.textColorlv;
// 转换为 double 进行比较
try {
double value = double.parse(percent);
return value < 0 ? const Color(0xffcd3746) : const Color(0xff01b56b);
} catch (e) {
return AppTheme.textColorlv; // 转换失败返回默认颜色
}
}
}
4、view
视图
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:k_chart/k_chart_widget.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import 'package:xiaoshukeji/common/index.dart';
import 'package:k_chart/flutter_k_chart.dart';
import 'index.dart';
class KChartPage extends GetView<KChartController> {
const KChartPage({
super.key});
// 头部数据
Widget _buildHeader() {
return <Widget>[
<Widget>[
TextWidget.body('${controller.headPrice.lastPrice}',size:30.sp,color: AppTheme.textColorfff,weight: FontWeight.w600,),
SizedBox(height: 15.w),
<Widget>[
TextWidget.body('${controller.headPrice.priceChangePercent}%',size:26.sp,color: AppTheme.textColorfff,weight: FontWeight.w600,),
].toRow().paddingHorizontal(30.w).card(
color: controller.getPriceChangeColor(controller.headPrice.priceChangePercent),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.w)),
).height(40.w)
].toColumn(mainAxisAlignment: MainAxisAlignment.center),
<Widget>[
TextWidget.body('24h量 ${controller.headPrice.volume}',size:24.sp,color: AppTheme.textColorfff,),
SizedBox(height: 10.w),
TextWidget.body('24h高 ${controller.headPrice.highPrice}',size:24.sp,color: AppTheme.textColorfff,),
SizedBox(height: 10.w),
TextWidget.body('24h低 ${controller.headPrice.lowPrice}',size:24.sp,color: AppTheme.textColorfff,),
].toColumn(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.start),
].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween).paddingHorizontal(30.w).card(color: AppTheme.blockBgColor).tight(width: 690.w, height: 160.w);
}
// 时间选择
Widget _buildPeriodButtons() {
return <Widget>[
_buildPeriodButton('1m', '1分钟'),
_buildPeriodButton('5m', '5分钟'),
_buildPeriodButton('15m', '15分钟'),
_buildPeriodButton('30m', '30分钟'),
_buildPeriodButton('1h', '1小时'),
_buildPeriodButton('1d', '日线'),
].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween).paddingHorizontal(30.w).height(90.w);
}
Widget _buildPeriodButton(String period, String label) {
bool isSelected = controller.period == period;
return TextWidget.body(label, color: isSelected ? AppTheme.primaryYellow : AppTheme.textColor646).onTap(() {
controller.changePeriod(period);
});
}
// 指标切换按钮
Widget _buildIndicatorButtons() {
return <Widget>[
TextWidget.body('MA', color: controller.mainState == MainState.MA ? AppTheme.primaryYellow : AppTheme.textColor646).onTap(() {
controller.changeMainState(MainState.MA);
}),
SizedBox(width: 20.w),
TextWidget.body('BOLL',color: controller.mainState == MainState.BOLL ? AppTheme.primaryYellow : AppTheme.textColor646).onTap(() {
controller.changeMainState(MainState.BOLL);
}),
SizedBox(width: 100.w),
TextWidget.body('MACD',color: controller.secondaryState == SecondaryState.MACD ? AppTheme.primaryYellow : AppTheme.textColor646).onTap(() {
controller.changeSecondaryState(SecondaryState.MACD);
}),
SizedBox(width: 20.w),
TextWidget.body('KDJ',color: controller.secondaryState == SecondaryState.KDJ ? AppTheme.primaryYellow : AppTheme.textColor646 ).onTap(() {
controller.changeSecondaryState(SecondaryState.KDJ);
}),
SizedBox(width: 20.w),
TextWidget.body('RSI',color: controller.secondaryState == SecondaryState.RSI ? AppTheme.primaryYellow : AppTheme.textColor646).onTap(() {
controller.changeSecondaryState(SecondaryState.RSI);
}),
].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween).paddingHorizontal(60.w).card(color: AppTheme.blockBgColor).height(80.w);
}
@override
Widget build(BuildContext context) {
return GetBuilder<KChartController>(
init: KChartController(),
id: "k_chart",
builder: (_) {
return Scaffold(
backgroundColor: AppTheme.pageBgColor,
appBar: TDNavBar(
height: 45,
title: controller.symbol,
titleColor: AppTheme.textColorfff,
titleFontWeight: FontWeight.w600,
backgroundColor: AppTheme.navBarBgColor,
screenAdaptation: true,
useDefaultBack: true,
),
body: Column(
children: [
SizedBox(height: 30.w),
_buildHeader(),
_buildPeriodButtons(),
SizedBox(
width: 750.w,
child: KChartWidget(
controller.datas,
ChartStyle(),
ChartColors(),
isLine: false,
isTapShowInfoDialog:true, // 单击显示详细信息
mainState: controller.mainState,
secondaryState: controller.secondaryState,
volHidden: true, // 隐藏量
isTrendLine: false, // 显示趋势线
),
).expanded(),
_buildIndicatorButtons(),
],
),
);
},
);
}
}