尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android自选股K线图实战:MPAndroidChart组合图实现与性能优化

Android自选股K线图实战:MPAndroidChart组合图实现与性能优化 简介模拟自选股小程序.zip 提供一套基于 Android 的完整工程源码适合正在学习移动端开发、数据可视化及股票信息展示的初学者或中级开发者。项目以 CombinedChartDemo 为主体展示如何借助 Java 与 XML 搭建自选股界面并通过图表库呈现股票趋势帮助理解从数据获取到页面刷新的整体流程涵盖界面搭建、数据渲染和交互处理等常见场景。压缩包共53个文件约114KB主要包括16个Java源码文件、19个XML布局/配置资源、3个Gradle构建脚本、5张图片另含1个jar依赖包和1份README说明文档工程结构清晰便于按模块阅读和二次开发。目前已有183人浏览学习适合作为课程设计或自学练手项目。通过研读源码能够掌握 Android 项目构建、界面布局、图表组件集成等关键知识点也可据此扩展自选股管理、行情刷新和提醒等功能。对于希望了解行情类 App 开发流程的读者这是一份轻量而完整的参考资料。1. 模拟自选股小程序zip包里装了什么拿到这个zip第一反应别被“小程序”三个字带偏。解压后看到的CombinedChartDemo-master、gradlew、settings.gradle、app/src这不是微信小程序也不是H5页面而是一个标准到不能再标准的Android Studio工程Gradle Wrapper都给你配好了。这个工程要解决的自选股场景核心问题就两个一是K线图怎么画二是K线和均线怎么叠在一张图里还能丝滑缩放。目录名里的CombinedChart已经说明它选用的技术路线是MPAndroidChart这套老牌图表库用CombinedChart把CandleStick图和Line图组合到同一个坐标系。适合两类人刚接触Android图表开发、想找一套完整可跑代码的人以及已经在写股票类App、想快速抄一个自选股联动交互的工程师。2. Gradle 构建与工程结构解开 CombineChartDemo 的骨架2.1 Gradle Wrapper 不是装饰是构建基线gradlew和gradlew.bat这两个文件很多人觉得是IDE自动生成的没用的东西实际恰恰相反。Wrapper锁定了Gradle版本团队里不管谁clone下来执行./gradlew assembleDebug都会用同一个Gradle版本去构建不会出现“我这边能编译你那边报错”的经典灾难。项目根目录的gradle/wrapper里如果没有gradle-wrapper.properties会默认走系统Gradle那这个工程挪到CI上第一件事就是要对版本。建议拿到压缩包后第一件事就是看一眼gradle-wrapper.properties里的distributionUrl确认Gradle版本和Android Gradle Plugin兼容。常见配法是这样的gradle-wrapper.propertiesdistributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists这段配置里的distributionUrl是构建的源头这里写7.5AGP版本就要选7.1以上但不高于8.0的组合。如果打开工程后IDE提示Gradle版本不匹配优先改的是这个文件而不是动系统环境变量。2.2 settings.gradle 与 app/build.gradle 怎么读工程的模块入口在settings.gradle里声明展开能看到include :app。真正有用的是在app/build.gradle的dependencies块这是整个自选股Demo能画图的底层支撑。需要引入MPAndroidChart同时还要确保支持库版本与编译SDK匹配。一个自选股项目里常见的app/build.gradle依赖区是这样dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.9.0 implementation androidx.constraintlayout:constraintlayout:2.1.4 // MPAndroidChartK线图、均线、分时图都要靠它 implementation com.github.PhilJay:MPAndroidChart:v3.1.0 }implementation关键字表示依赖只在模块内部可见。注意v3.1.0是MPAndroidChart在JitPack上的最后一个稳定版本2018年之后作者没有大的更新但这并不代表不能用Compose兴起之前的老牌图表库稳定性反而比很多新库可靠。如果后续需要K线图做指标计算比如加一个MACD副图还是要靠这个库的底层绘制能力来扩展。2.3 目录结构里的几个关键文件app/src下通常分main/java、main/res、main/assets三块。组合图Demo的核心文件一般会拆成一个主Activity承载布局、一个Fragment或者自定义View承载Chart、一个工具类生成假的股票K线数据。proguard-rules.pro这个文件不是摆设如果App要上线上混淆MPAndroidChart里有大量反射调用的地方需要保留图表库的类名。路径作用app/src/main/javaKotlin或Java源码Demo的入口和图表逻辑在这app/src/main/res/layoutXML布局定义Activity里Chart的摆放位置gradlew/gradlew.batUnix / Windows下的构建脚本.idea目录IDE的本地配置可以删除不影响编译提交Git时建议忽略README.md工程说明看三分钟能知道这个Demo的feature清单.idea目录里躺着的misc.xml、compiler.xml是IDE缓存不是业务代码。真实场景里通常会把它加进.gitignore这属于工程规范不影响跑起来。3. CombinedChart 组合图表核心K线、均线、成交量一图叠放3.1 为什么不自绘而是用 CombinedChart股票图表的难点不是画一根K线而是K线、均线、成交量三套坐标系要叠在一起并且触摸移动时三套数据要同步缩放。自绘需要用onDraw里自己算坐标映射还要处理触摸事件分发工程量很大。CombinedChart的核心价值是内置了坐标轴同步机制多个DataSet之间共享同一个X轴Y轴上左轴画价格、右轴画成交量用CombinedData把CandleData和LineData打包到一个图里。3.2 构建 K 线数据集的代码实现K线需要四个价格开盘价、最高价、最低价、收盘价。MPAndroidChart用CandleStickEntry来承载初始化参数是x索引、开盘、最高、最低、收盘。一段完整的K线数据构建逻辑是这样ArrayListCandleStickEntry candleEntries new ArrayList(); Random random new Random(); float open 100f; for (int i 0; i 120; i) { float close open (random.nextFloat() - 0.45f) * 4f; float high Math.max(open, close) random.nextFloat() * 2f; float low Math.min(open, close) - random.nextFloat() * 2f; candleEntries.add(new CandleStickEntry(i, open, low, high, close)); open close; } CandleDataSet candleDataSet new CandleDataSet(candleEntries, 日K); candleDataSet.setColor(getResources().getColor(R.color.quote_line)); candleDataSet.setDecreasingColor(getResources().getColor(R.color.quote_down)); candleDataSet.setIncreasingColor(getResources().getColor(R.color.quote_up)); candleDataSet.setDecreasingPaintStyle(Paint.Style.FILL); candleDataSet.setIncreasingPaintStyle(Paint.Style.FILL); candleDataSet.setShadowColor(getResources().getColor(R.color.quote_shadow)); candleDataSet.setShadowWidth(1f); candleDataSet.setNeutralColor(getResources().getColor(R.color.quote_flat));这段代码里有个容易踩坑的细节setDecreasingColor和setIncreasingColor控制的是阴线和阳线的颜色而setShadowColor控制的是影线的颜色。默认情况下shadowColor是灰色如果不显式设置K线涨跌颜色和影线颜色不一致视觉上会非常奇怪。setShadowWidth(1f)是影线宽度太宽会盖住相邻的K线实体一般0.5到1之间比较合适。这里用Paint.Style.FILL而不是STROKE是为了让实体柱跟国内股票软件的实心K线表现一致用STROKE画出来的是空心K线两者没有对错看产品要求。3.3 均线叠加与坐标轴同步配置K线画完后第二层是MA5、MA10、MA20三条均线。均线本质是收盘价的滑动平均LineDataSet接受Entry作为数据点注意Entry的x索引必须和CandleStickEntry的x索引一一对应否则图表会错位。叠加代码在拿到CombinedData后执行CombinedData combinedData new CombinedData(); combinedData.setData(candleDataSet); // 均线MA5 ArrayListEntry ma5Entries new ArrayList(); for (int i 4; i candleEntries.size(); i) { float avg calculateMA(candleEntries, i, 5); ma5Entries.add(new Entry(i, avg)); } LineDataSet ma5 new LineDataSet(ma5Entries, MA5); ma5.setColor(getResources().getColor(R.color.ma5)); ma5.setLineWidth(1f); ma5.setDrawCircles(false); ma5.setDrawValues(false); combinedData.setData(ma5); mCombinedChart.setData(combinedData); mCombinedChart.invalidate();setData的顺序有讲究先CandleData再LineData因为CombinedChart默认后set的DataSet层级覆盖先set的均线要画在K线之上。setDrawValues(false)是必须的均线上标数字会严重干扰K线阅读除非产品明确要求显示收盘价标签否则建议一律关掉。setDrawCircles(false)同理均线不是散点图不需要在每个点画圆点。calculateMA是自己写的一个滑动平均函数核心逻辑就是取当前索引往前数N天对收盘价求和再取平均边界从N-1开始因为前面凑不齐N天。X轴和Y轴的配置决定图表看起来是否专业。一套可行的坐标轴参数是XAxis xAxis mCombinedChart.getXAxis(); xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); xAxis.setDrawGridLines(false); xAxis.setGranularity(1f); xAxis.setLabelCount(6, true); YAxis leftAxis mCombinedChart.getAxisLeft(); leftAxis.setDrawGridLines(true); leftAxis.setGridColor(Color.parseColor(#1AFFFFFF)); leftAxis.setAxisMinimum(0f); YAxis rightAxis mCombinedChart.getAxisRight(); rightAxis.setEnabled(false);setGranularity(1f)解决的是X轴索引跳动的问题默认情况下X轴会自动插值导致标记的日期中间出现空白刻度粒度设为1表示每个数据点对应一个刻度自选股K线这种天级别数据必须开启。setLabelCount(6, true)里的true是强制绘制6个标签如果设成false图表的label会智能隐藏国内股票软件的横轴习惯显示4到6个日期所以这里用6。右侧Y轴直接关掉因为自选股Demo里只有价格维度没有成交量维度留着右轴会出现双Y轴刻度不一致的认知负担。表盘手势交互上还有一个关键配置setPinchZoom(false)。CombinedChart默认允许双指缩放时同时缩放两个轴但股票场景通常只缩放X轴时间轴Y轴跟随数据范围自适应变化。关掉pinchZoom后双指手势会只缩放X轴接近行情软件的横向缩放体验。setDoubleTapToZoomEnabled(false)也是股票App里常用的关闭项双击放大在图表和列表联动时会打断滚动操作。4. 自选股列表与实时刷新从 K 线到 RecyclerView 的联动链路4.1 用随机游走生成模拟数据自选股Demo没有真实行情接口图里要“能动”就得让数据自己变。K线生成的常见做法不是完全随机而是使用随机游走模型让价格在上一根K线的收盘价基础上小幅波动这样画出来的走势才有趋近真实的连续感。之前那段代码里close open (random.nextFloat() - 0.45f) * 4f偏移值减0.45f而不是0.5f是故意让涨的概率略高于跌的概率模拟牛市倾向。(0.45f)这个值如果浮动调大比如0.3K线会整体单边上涨走势图完全没有回调一眼假。真实的股票走势在统计学上更接近带漂移项的几何布朗运动此处用简化版够Demo展示用。4.2 RecyclerView 与点击联动回调自选股列表用RecyclerView是最常规的方案。列表的每一个item展示股票名称、最新价、涨跌幅点击一条后右边的图表区要切换到对应股票的K线。这里的关键是列表回调跟Chart更新的线程一致性问题列表点击发生在UI线程直接更新mCombinedChart的数据是安全的不需要额外Handler切换。核心代码大概是这样public class StockAdapter extends RecyclerView.AdapterStockAdapter.ViewHolder { private ListStockData mData; private OnStockClickListener mListener; interface OnStockClickListener { void onStockClick(StockData data); } public void setOnStockClickListener(OnStockClickListener listener) { this.mListener listener; } Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { StockData item mData.get(position); holder.tvName.setText(item.getName()); float change item.getPrice() - item.getPrevClose(); holder.tvChange.setText(String.format(%.2f %.2f%%, change, change / item.getPrevClose() * 100)); holder.tvChange.setTextColor(change 0 ? upColor : downColor); holder.itemView.setOnClickListener(v - { if (mListener ! null) { mListener.onStockClick(item); } }); } }String.format(%.2f %.2f%%, ...)这里不能直接用String.valueOf拼接因为涨跌浮点数会有0.100000001这种精度尾巴用format能稳定保留两位小数。change / prevClose * 100是涨跌幅的计算公式prevClose就是昨收价。注意tvChange.setTextColor必须在setText之后否则颜色设置会被文本刷新覆盖掉。4.3 刷新链路只动数据不重建图轮询刷新是自选股App的核心体验。模拟场景没有WebSocket或HTTP接口数据只能自己生成。常见做法是做一条模拟的行情推送通道一个ScheduledExecutorService每隔500毫毫秒生成一个新价然后通过postOnUiThread把价格变化喂给图表引擎。这个过程最容易犯的错是每次都调用mCombinedChart.invalidate()而正确姿势是只更新最后一个CandleStickEntry的close再调用mCombinedChart.invalidate()。invalidate本身只是标记View重绘不会触发布局测量性能上成本可控。完整刷新链路是ScheduledExecutorService executor Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() - { runOnUiThread(() - updateLastPrice()); }, 0, 500, TimeUnit.MILLISECONDS); private void updateLastPrice() { if (candleEntries.isEmpty()) { return; } int lastIndex candleEntries.size() - 1; CandleStickEntry lastEntry candleEntries.get(lastIndex); float lastClose lastEntry.getClose(); float newClose lastClose (float)(Math.random() - 0.4) * 0.8f; float newHigh Math.max(lastEntry.getHigh(), newClose); float newLow Math.min(lastEntry.getLow(), newClose); candleEntries.set(lastIndex, new CandleStickEntry( lastIndex, lastEntry.getOpen(), newLow, newHigh, newClose)); combinedData.notifyDataChanged(); mCombinedChart.notifyDataSetChanged(); mCombinedChart.invalidate(); }scheduleAtFixedRate的间距设为500毫秒因为用户在手机上连续盯盘时刷新频率超过200毫秒会变成快速闪烁反而不利于阅读分时变化。(Math.random() - 0.4) * 0.8f同样是制造向上漂移的随机偏移。notifyDataChanged()通知CombinedData内部结构有变动notifyDataSetChanged()通知图表数据源不是同一个对象。这两个方法缺一个都会导致图表不刷新或数据错乱是个坑。5. 数据量大时的性能抖动与踩坑修复CombinedChart在200个数据点以内时性能压力不大但自选股列表切到周K、月K或者一次性加载两年的日K数据量上到500点以上掉帧和触摸卡顿就来了。常见做法是限制可见数据点范围mCombinedChart.setVisibleXRangeMaximum(120)。这一行限制X轴最多显示120个刻度配合双指缩放手势用户放大再看细节缩小也只看最近一段不会触发全量渲染。setVisibleXRangeMaximum的值是数据点个数不是像素120针对手机屏幕宽度大概能显示K线实体间距的合理密度数值太小K线被拉伸数值太大K线被压缩成细线。关闭高亮和选择反馈是第二个有效优化点。K线图上默认开启触摸高亮手指滑动时会画出一根十字线这在分时图里有用在K线图中却是性能杀手。高亮绘制需要的重绘区域会扩大好几倍。业务非必需时用:mCombinedChart.setHighlightPerDragEnabled(false); mCombinedChart.setHighlightPerTapEnabled(false); mCombinedChart.setScaleYEnabled(false);setHighlightPerDragEnabled(false)是关闭拖拽过程中的高亮重绘setHighlightPerTapEnabled(false)是关闭点击十字线。setScaleYEnabled(false)关闭Y轴缩放。如果不关Y轴缩放用户在自选股详情页快速左右滑动时很容易误触纵向缩放图表整体的比例被拖乱体验会很差。注意这三行代码必须在setData之前调用才会对后续的所有绘制生效写在invalidate之后是无效的这是一个顺序坑。K线的CandleStickEntry对象不可复用每次更新数据都要新建对象不能修改close后再set回去API内部会缓存阴影值和绘制状态修改已存在的Entry后UI经常不刷新。最后一个实际项目中常见的问题是轮询更新和用户触摸手势冲突。手指按住图表拖动时如果后台500毫秒轮询线程在同时改数据图表会低频跳变表现为拖动时K线自己往回缩。解决方式是在手势开始触摸时暂停外部数据更新触摸结束后恢复。做法是重写ChartTouchListener或者更简单的在onTouchEvent里判断action ACTION_DOWN时挂起定时任务ACTION_UP时恢复。自选股场景里行情刷新错过几百毫秒无感但拖动图表时数据跳变是肉眼可见的体验劣化。数据量进一步增大时考虑对candleEntries做分页加载scrollToPosition到最左时触发加载上一页这套逻辑配合CombinedChart的setVisibleXRangeMaximum能稳定支撑两年日K级别的数据量。本文还有配套的精品资源点击获取
返回列表