建设网站都需要下载那些软件武汉搜索推广
使用插件: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;//// 初始化数据@overridevoid onInit() {super.onInit();_initData();_startTimer();}@overridevoid 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);}@overrideWidget 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(),],),);},);}
}