背景
由于需要,最近需要在Flutter中应用股票走势线,寻找了很久Flutter相关第三方库,无果,没有一个是能达到要求的。于是脑子萌发了一个想法,那么我把原生的组件移植过来不也可以吗,只要达到组件的效果即可。 转载请注明出处,谢谢!
那么现在我需要做的是找一个合适的K线的原生代码写的第三方库,原生项目地址:点击查看,感谢KLineChartView的作者。
先看看KLineChartView的效果图:


万事具备,只欠东风,现在我们把KLineChartView装入Flutter中并使用!
第一步,新建一个Flutter项目,这里暂且就不叙述了!
第二步,打开Flutter项目中的android文件夹,并导入KLineChartViewLib!
第三步,使用原生代码把KLineChartView封装为Flutter的组件,Follow Me!
封装KLineView,要求类实现PlatformView接口,如果需要处理Dart调用控件的事件,则需要实现MethodChannel.MethodCallHandler接口
方法:getView则是需要传入的你要显示的组件对象
onMethodCall 则是需要如何处理由Flutter传回的调用
dispose 则是如何处理组件的资源释放
注意:请查看构造函数,其中有个参数叫 params,该参数则是描述该组件的初始化参数。
class KLineView(context: Context?, messenger: BinaryMessenger, id: Int, params: Map
?) : PlatformView, MethodChannel.MethodCallHandler { private var klineChartView: KLineChartView = KLineChartView(context) private val klineAdapter by lazy { KLineChartAdapter() } private var mainDrawType: Status = Status.MA private var childDrawPosition = -1 private var isShowMainDrawLine = false init { val gridRowCount: Int = when { params?.containsKey("gridRowCount") != true || params["gridRowCount"] == null -> 4 else -> params["gridRowCount"].toString().toInt() } val gridColumnCount: Int = when { params?.containsKey("gridColumnCount") != true || params["gridColumnCount"] == null -> 4 else -> params["gridColumnCount"].toString().toInt() } klineChartView.apply { adapter = klineAdapter dateTimeFormatter = DateFormatter() setGridRows(gridRowCount) setGridColumns(gridColumnCount) } MethodChannel(messenger, "${KLineViewFlutterPlugin.ViewTypeID}_$id").setMethodCallHandler(this) ///这里初始化一个MethodChanel对象,为了使Dart能够正常调用这边的方法及相关属性的更改,id则是每个原生组件创建时会由系统生成一个ID号,用于识别该组件,在Dart代码中也需要用到该ID。 } override fun getView(): View = klineChartView override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "addData" -> { val isInitData = call.argument ("isInitData") ?: false val isHistoryData = call.argument ("isHistoryData") ?: false val dataListJson = call.argument ("data") val dataList = Gson().fromJson - >(dataListJson,
object : TypeToken
- >() {}.type)
if (isInitData) klineChartView.justShowLoading()
DataHelper.calculate(dataList)
when (isHistoryData) {
true -> klineAdapter.addHeaderData(dataList)
else -> klineAdapter.addFooterData(dataList)
}
klineAdapter.notifyDataSetChanged()
klineChartView.startAnimation()
if (isInitData) klineChartView.refreshEnd()
result.success(null)
}
"changeMainDrawType" -> {
val status = when (call.argument
("type")) { 1 -> Status.MA 2 -> Status.BOLL else -> Status.NONE } if (status != mainDrawType) { mainDrawType = status klineChartView.hideSelectData() klineChartView.changeMainDrawType(status) } result.success(null) } "changeChildDraw" -> { val position = call.argument ("position") ?: -1 if (childDrawPosition != position) { childDrawPosition = position klineChartView.hideSelectData() when (position) { -1 -> klineChartView.hideChildDraw() else -> klineChartView.setChildDraw(position) } } result.success(null) } "setMainDrawLine" -> { val showMainDrawLine = call.argument ("isShowMainDrawLine") ?: false if (isShowMainDrawLine != showMainDrawLine) { isShowMainDrawLine = showMainDrawLine klineChartView.setMainDrawLine(showMainDrawLine) } result.success(null) } } } override fun dispose() { } }
封装KLineViewFactory, 该类比较简单,继承于PlatformViewFactory,需要注意PlatformViewFactory的构造方法的参数,其值是指 解析Flutter传入参数的方式,这里为一个固定实例对象StandardMessageCodec.INSTANCE。
方法:create 则是需要返回一个上面写下的一个View的对象。
class KLineViewFactory(private var messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { @Suppress("unchecked_cast") override fun create(context: Context, viewId: Int, args: Any?): PlatformView = KLineView(context, messenger, viewId, args as? Map
) }
编写KLineViewFlutterPlugin,并注入Flutter中
编写registerWith方法,检测控件是否已经注册及控件的注册
object KLineViewFlutterPlugin { const val ViewTypeID = "plugins.mrper.andrid-view/kline-view" @JvmStatic fun registerWith(registry: PluginRegistry) { val key = KLineViewFlutterPlugin::class.java.canonicalName if (registry.hasPlugin(key)) return val registrar = registry.registrarFor(key) registrar.platformViewRegistry().registerViewFactory(ViewTypeID, KLineViewFactory(registrar.messenger())) } }
MainActivity注册该组件:
class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) KLineViewFlutterPlugin.registerWith(this) //注册组件 } }
第四步,开始编写我的Flutter的KLineChartView,在此我就不再解释下面代码内容,请具体查看代码。
import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; typedef void OnKLineChartViewCreated(); /// 主图类型,默认为MA enum KLineMainDrawType { None, MA, BOLL } /// 副图类型,默认为None enum KLineChildDrawPosition { None, MACD, KDJ, RSI, WR } /// 股票走势图组件 class KLineChartView extends StatefulWidget { KLineChartView({ Key key, this.gridColumnCount = 4, this.gridRowCount = 4, double width, double height, BoxConstraints constraints, this.backgroundColor = const Color(0xff333333), this.padding, this.margin, this.alignment = Alignment.center, @required this.onViewCreated, }) : assert(constraints == null || constraints.debugAssertIsValid()), assert(onViewCreated != null), constraints = (width != null || height != null) ? (constraints ??= BoxConstraints()) ?.tighten(width: width, height: height) ?? BoxConstraints.tightFor(width: width, height: height) : (constraints ??= BoxConstraints()), super(key: key); final int gridColumnCount; final int gridRowCount; final BoxConstraints constraints; final Color backgroundColor; final EdgeInsets padding; final EdgeInsets margin; final Alignment alignment; final OnKLineChartViewCreated onViewCreated; @override KLineChartViewState createState() => KLineChartViewState(); } class KLineChartViewState extends State
{ MethodChannel _methodChannel; /// 添加数据 /// + [dataList]-数据列表 /// + [isInitData]-是否是初始数据 /// + [isHistoryData]-是否是历史数据 void addData(List 使用KlineChartView
class HomePage extends StatefulWidget { HomePage({Key key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State
{ GlobalKey _klineChartViewKey; @override void initState() { _klineChartViewKey = GlobalKey (); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('首页')), body: LoaderContainer( state: LoaderState.NoAction, onReload: () {}, emptyView: ClassicalNoDataView( spacingFromImageToText: 20, spacingFromTextToButton: 65, onRefresh: () => showDialog(context: context, child: LoadingDialog()), buttonBackgroundColor: Colors.yellow, buttonBorderRadius: BorderRadius.all(Radius.circular(3))), errorView: ClassicalErrorView( spacingFromImageToText: 20, imageWidth: 120, imageHeight: 120, onReload: () {}), contentView: Column(children: [ Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: Text('主图')), ...['MA', 'BOLL', '隐藏'] .map((item) => InkWell( child: Container( height: 35, padding: const EdgeInsets.symmetric(horizontal: 5), alignment: Alignment.center, child: Text(item)), onTap: () { KLineMainDrawType type = KLineMainDrawType.None; if (item == '隐藏') type = KLineMainDrawType.None; else if (item == 'MA') type = KLineMainDrawType.MA; else type = KLineMainDrawType.BOLL; _klineChartViewKey.currentState .changeMainDrawType(type); })) .toList() ]), Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: Text('副图')), ...['MACD', 'KDJ', 'RSI', 'WR', '隐藏'] .map((item) => InkWell( child: Container( height: 35, padding: const EdgeInsets.symmetric(horizontal: 5), alignment: Alignment.center, child: Text(item)), onTap: () { KLineChildDrawPosition position = KLineChildDrawPosition.None; if (item == '隐藏') position = KLineChildDrawPosition.None; else if (item == 'MACD') position = KLineChildDrawPosition.MACD; else if (item == 'KDJ') position = KLineChildDrawPosition.KDJ; else if (item == 'RSI') position = KLineChildDrawPosition.RSI; else position = KLineChildDrawPosition.WR; _klineChartViewKey.currentState .changeChildDraw(position); })) .toList() ]), Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ ...['分时', 'K线图'] .map((item) => InkWell( child: Container( height: 35, padding: const EdgeInsets.symmetric(horizontal: 5), alignment: Alignment.center, child: Text(item)), onTap: () { _klineChartViewKey.currentState .setMainDrawLine(item == '分时'); })) .toList() ]), Expanded( child: KLineChartView( key: _klineChartViewKey, height: MediaQuery.of(context).size.height * 2 / 3, onViewCreated: () { _klineChartViewKey.currentState .addData(TEST_DATA, isInitData: true); })) ]))); } } 效果图如下:

- 到此为止,我们所有的已经实现了,效果还是不错的!
总结
通过这次组件的嵌入化,对PlatformView的了解更加深入了解了。更重点的是,我们要学会使用MethodChannel这个重要的类,如何使用它与Dart代码沟通,如何相互调用。其次是如何创建一个具有Native性质的Flutter组件,玩的愉快!其实我最开始也只是抱着试一试的心态,没想到还可以,也支持视图缩放。