使用MPAndroidChart实现心跳图
简介
这篇文章主要介绍如何使用MPAndroidChart实现心跳图的效果。
需求分析
之前考虑过用2个linechart上下叠起来,坐标轴上下设置了默认空格,数据需要处理坐标轴为0的情况,多个数据处理比较复杂,数据处理和UI效果不尽如意,最终考虑使用单个linechartview来实现效果,在数据方面我们主要考虑怎么求斜率,在UI层面通用的lineChartView并不能满足我们的需求,我们需要考虑自定义矩形背景,自定义网格线,自定义坐标轴,并且能动态修改坐标轴满足进行中状态的数据变更,还要做到上下2块数据颜色的区分。
三方库介绍
MPAndroidChart 是一款功能强大的 Android 图表开源库,主要功能和特点如下:
1. 支持各种图表类型:线形图、柱状图、饼图、散点图、气泡图、雷达图等。
2. 支持在同一图表内显示多种类型的数据集。
3. 支持手势操作如拖拽、缩放各轴、设置可见范围等。
4. 支持高度自定义图表样式,包括颜色、字体、描述文本等。
5. 支持实时图表,可以动态更新数据。
MPAndroidChart开发文档:https://www.wenjiangs.com/doc/b2k2uz90i
实现
引入三方库
- 添加 MPAndroidChart 库的依赖,三方库地址:https://github.com/PhilJay/MPAndroidChart
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
初始化linechart
binding.apply {
lineChart.setDescription(null)
lineChart.setDrawGridBackground(false) //是否启用网格背景
lineChart.setDrawMarkers(false)
lineChart.setDrawBorders(false) //绘制边框
lineChart.setTouchEnabled(false)
lineChart.setDragEnabled(false)
lineChart.setScaleEnabled(false)
//左边坐标轴
//隐藏左边坐标轴横网格线
lineChart.getAxisLeft().setDrawGridLines(false)
lineChart.getAxisLeft().setDrawLabels(false)
lineChart.getAxisLeft().setDrawGridLinesBehindData(false)
lineChart.getAxisLeft().setDrawTopYLabelEntry(false)
lineChart.getAxisLeft().setDrawZeroLine(false)
lineChart.axisLeft.isEnabled = true
//右边坐标轴
//隐藏右边坐标轴横网格线
lineChart.getAxisRight().setDrawGridLines(false)
lineChart.getAxisRight().setDrawLabels(false) //右边的刻度
lineChart.getAxisRight().setDrawGridLinesBehindData(false)
lineChart.getAxisRight().setDrawTopYLabelEntry(false)
lineChart.getAxisRight().setDrawZeroLine(false)
lineChart.axisRight.isEnabled = false
//x轴
//隐藏X轴竖网格线
lineChart.getXAxis().setDrawGridLines(true)
lineChart.getXAxis().setDrawLabels(true)
lineChart.getXAxis().setDrawAxisLine(false)
lineChart.getXAxis().setDrawGridLinesBehindData(false)
lineChart.xAxis.isEnabled = true
lineChart.xAxis.gridColor = ColorUtil.xAxisGridColor//垂直的线
lineChart.xAxis.gridLineWidth = 0.5f //垂直的线宽
lineChart.xAxis.setLabelCount(7, true)//标签数量 0 - 90 count = 7
//设置X轴范围
lineChart.getXAxis().setAxisMinimum(0f);
lineChart.getXAxis().setAxisMaximum(90f)//只能显示100
lineChart.setNoDataText("")
//设置Y轴范围
lineChart.getAxisLeft().setAxisMinimum(-150f) //数据的最小值
lineChart.getAxisLeft().setAxisMaximum(150f)
}
数据填充
-
首先我们需要对接口数据进行解析。接口数据大致如下:
"0,0,0,0,100,-48,70,-42,50,10,-30,66,24,100,-10,40,10,54,0,0,-100,-48,-46,26,0,62,-44,0,26,-20,-20,38,-100,-60,0,8,100,-6,100,0,38,60,-100,22,38,26"
-
定义2个list存储上下2块数据,并计算斜率,将斜率值存储到2个集合中。
private fun addData(heartRate: MutableList<Int>) { topList.clear() bottomList.clear() heartRate.add(0, 0) var previousEntry: Entry? = null //前一个点的值 for (i in 0 until heartRate.size) { val value = heartRate.get(i) val currentEntry = Entry(i.toFloat(), heartRate.get(i).toFloat()) if (previousEntry != null) { if (value > 0 && previousEntry.y < 0) { //计算斜率 X轴的坐标点 val xAxis = CustomLineChartRenderer.getXAxis(previousEntry, currentEntry) //先添加X轴的交点坐标 topList.add(Entry(xAxis, 0f)) topList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) bottomList.add(Entry(xAxis, 0f)) bottomList.add(Entry(i.toFloat(), 0f)) } else if (value < 0 && previousEntry.y > 0) { //计算斜率 X轴的坐标点 val xAxis = CustomLineChartRenderer.getXAxis(previousEntry, currentEntry) //先添加X轴的交点坐标 bottomList.add(Entry(xAxis, 0f)) bottomList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) topList.add(Entry(xAxis, 0f)) topList.add(Entry(i.toFloat(), 0f)) } else { if (value > 0) { topList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) bottomList.add(Entry(i.toFloat(), 0f)) } else if (value < 0) { bottomList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) topList.add(Entry(i.toFloat(), 0f)) } else { topList.add(Entry(i.toFloat(), 0f)) bottomList.add(Entry(i.toFloat(), 0f)) } } } else { //第一个点 if (value > 0) { topList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) bottomList.add(Entry(i.toFloat(), 0f)) } else if (value < 0) { bottomList.add(Entry(i.toFloat(), heartRate.get(i).toFloat())) topList.add(Entry(i.toFloat(), 0f)) } else { topList.add(Entry(i.toFloat(), 0f)) bottomList.add(Entry(i.toFloat(), 0f)) } } previousEntry = currentEntry } }
-
计算斜率。
/** * 如果已知两点的坐标(x1, y1)和(x2, y2),要计算经过这两点并且与横轴相交的点的坐标(x, 0),可以使用以下公式: * <p> * 求斜率:k=(y2-y1)/(x2-x1) * 直线方程 y-y1=k(x-x1) * y = 0, 求x ? */ public static float getXAxis(Entry previousEntry, Entry currentEntry) { float k = (currentEntry.getY() - previousEntry.getY()) / (currentEntry.getX() - previousEntry.getX()); float x = (0 - previousEntry.getY()) / k + previousEntry.getX(); return x; }
-
数据填充。定义 2个lineDataSet,设置上下2块数据的属性,设置填充模式。
private fun bindData(values: ArrayList<Int>) { var heartRateList = arrayListOf<Int>() heartRateList.addAll(values) addData(heartRateList) //第一部分 val set = LineDataSet(topList, null) set.setDrawCircles(false) //不显示圆点 set.setDrawValues(false) //不显示数值 set.color = Color.TRANSPARENT //线条颜色 set.setDrawFilled(true); set.setDrawCircles(false) set.setMode(LineDataSet.Mode.LINEAR);//填充模式 set.fillColor = ColorUtil.topFillColor set.fillAlpha = 225 //第2部分 val setTwo = LineDataSet(bottomList, null) setTwo.setDrawCircles(false) //不显示圆点 setTwo.setDrawValues(false) //不显示数值 setTwo.color = Color.TRANSPARENT //线条颜色 setTwo.setDrawFilled(true); setTwo.setMode(LineDataSet.Mode.LINEAR); setTwo.fillColor = ColorUtil.bottomFillColor setTwo.fillAlpha = 225 binding.lineChart.setData(LineData(set, setTwo)) //lineChart.invalidate()// 仅刷新LineChart的显示,不会改变数据和样式等设置。 //lineChart.notifyDataSetChanged()//会彻底重置并重新绘制LineChart,数据、样式以及属性设置都会生效。 }
-
刷新数据。
binding.lineChart.notifyDataSetChanged() // 通知数据集变化 binding.lineChart.invalidate()
自定义坐标轴、网格线
-
代码中设置坐标轴显示的文本。
binding.lineChart.xAxis.valueFormatter = object : DefaultAxisValueFormatter(0) { override fun getFormattedValue(value: Float): String { val formatValue = super.getFormattedValue(value) //mXAxisRenderer if (formatValue == "45") { return "HT" } return "$formatValue" + "'" }
- 自定义类CustomXAxisRenderer继承XAxisRenderer。
-
初始化画笔。
private Paint dashPaint; public CustomXAxisRenderer(Context context, ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) { super(viewPortHandler, xAxis, trans); this.context = context; bitmap = BitmapFactory.decodeResource(context.getResources(), android.R.drawable.arrow_down_float); dashPaint = new Paint(); dashPaint.setColor(ColorUtil.dashColor); dashPaint.setStyle ( Paint.Style.STROKE ) ; dashPaint.setStrokeWidth(0.5f); dashPaint.setAntiAlias(true); float[] dash = {2,7}; // 虚线段长和间隙 dashPaint.setPathEffect(new DashPathEffect(dash, 0)); //单个网格背景的高度 xAxisGradBgHeight = mViewPortHandler.contentHeight() / 2f / 3f ; bgRectBottomPaint = new Paint(); bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM); bgRectBottomPaint.setStyle(Paint.Style.FILL); }
-
重写renderGridLines方法,绘制自定义的y轴网格线。坐标轴的数量在初始化的时候已经定好了。
/** * 处理绘制X轴的 label 文本绘制 * @param c */ @Override public void renderGridLines(Canvas c) { if (!mXAxis.isDrawGridLinesEnabled() || !mXAxis.isEnabled()) return; int clipRestoreCount = c.save(); c.clipRect(getGridClippingRect()); if(mRenderGridLinesBuffer.length != mAxis.mEntryCount * 2){ mRenderGridLinesBuffer = new float[mXAxis.mEntryCount * 2]; } float[] positions = mRenderGridLinesBuffer; int htPosition = -1; for (int i = 0; i < positions.length; i += 2) { positions[i] = mXAxis.mEntries[i / 2]; //自己 定义的X轴 标签,7个 positions[i + 1] = mXAxis.mEntries[i / 2]; if (positions[i] == 45) { htPosition = i; } } mTrans.pointValuesToPixel(positions); //时间点 转 像素坐标 setupGridPaint(); Path gridLinePath = mRenderGridLinesPath; gridLinePath.reset(); for (int i = 0; i < positions.length; i += 2) { if (htPosition == i) { //绘制HT mGridPaint.setColor(ColorUtil.xAxisGridLineColor_HT); drawGridLine(c, positions[i], positions[i + 1], gridLinePath); } else { mGridPaint.setColor(ColorUtil.xAxisGridLineColor); drawGridLine(c, positions[i], positions[i + 1], gridLinePath); } } //还原 mGridPaint.setColor(ColorUtil.xAxisGridLineColor); c.restoreToCount(clipRestoreCount); }
-
坐标转换。
/** * X轴 坐标转化 * @param xAxisValue X轴的时间点, 比如 30,代表30分钟的X轴坐标点 * @return 转化后的坐标 */ private float[] pointToPixel(float xAxisValue) { float[] positions = new float[2]; positions[0] = xAxisValue; positions[1] = 0f;// 0代表离X轴的间距为0, 即代表: 文本距离X轴的高度 mTrans.pointValuesToPixel(positions); //时间点 转 像素坐标 return positions; }
-
重写drawLabel方法,修改坐标轴的颜色值。这里也可以处理不同时间点的事件,绘制点球、黄牌、换人、红牌等事件。
@Override protected void drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees) { if ("HT".equals(formattedLabel)) { mAxisLabelPaint.setColor(ColorUtil.Lable_Color_HT); mGridPaint.setColor(ColorUtil.Lable_Color_HT); super.drawLabel(c, formattedLabel, x, y, anchor, angleDegrees); } else { mAxisLabelPaint.setColor(ColorUtil.Lable_Color_other); mGridPaint.setColor(ColorUtil.Lable_Color_other); super.drawLabel(c, formattedLabel, x, y, anchor, angleDegrees); } if( onCustomXAxisRendererListener != null){ List<EventData> eventDataList = onCustomXAxisRendererListener.getDrawEventList(); if (eventDataList != null) { //绘制事件、黄牌、换人、红牌 float lineWidth = dp2px(0.5f); float spacing = 0f; //间距 for (EventData eventDatum : eventDataList) { float[] positions = pointToPixel(eventDatum.getTime() / 1f); // position 0中立 1 主队 2 客队 var res = getEventIcon(eventDatum); bitmap = BitmapFactory.decodeResource(context.getResources(), res); newBitmap = createScaledBitmap(bitmap); if (eventDatum.getPosition() == 1) { //正数 // c.drawLine(positions[0] - lineWidth / 2f, positions[1] / 2, positions[0] + lineWidth / 2f, positions[1], dashPaint); c.drawBitmap(newBitmap, positions[0] - newBitmap.getWidth() / 2f, positions[1] / 2 - newBitmap.getHeight() - spacing, null); } else if (eventDatum.getPosition() == 2) { //负数 // c.drawLine(positions[0] - lineWidth / 2f, positions[1] + positions[1] / 2, positions[0] + lineWidth / 2f, positions[1], dashPaint); //绘制球 c.drawBitmap(newBitmap, positions[0] - newBitmap.getWidth() / 2f, positions[1] + positions[1] / 2 + spacing, null); } if(bitmap != null) { bitmap.recycle(); } if(newBitmap != null) { newBitmap.recycle(); } } } } }
自定义背景
- 自定义CustomLineChartRenderer 实现LineChartRenderer。
-
初始化CustomLineChartRenderer。
public CustomLineChartRenderer(Context context, LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { super(chart, animator, viewPortHandler); this.context = context; mChart = chart; paintProgress = new Paint(); paintProgress.setColor(ColorUtil.xAxisGridLineColor_HT); paintProgress.setStyle(Paint.Style.FILL); textPaint = new Paint(); textPaint.setColor(ColorUtil.xAxisGridLineColor_HT); textPaint.setTextSize(dp2px(10f)); textPaint.setAntiAlias(true); paintGrey = new Paint(); paintGrey.setColor(ColorUtil.penaltyKickColor); paintGrey.setStyle(Paint.Style.FILL); bgRectTopPaint = new Paint(); bgRectTopPaint.setColor(ColorUtil.BG_Rect_TOP); bgRectTopPaint.setStyle(Paint.Style.FILL); bgRectBottomPaint = new Paint(); bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM); bgRectBottomPaint.setStyle(Paint.Style.FILL); dashPaint = new Paint(); dashPaint.setColor(ColorUtil.dashColor); dashPaint.setStyle(Paint.Style.STROKE); dashPaint.setStrokeWidth(1f); dashPaint.setAntiAlias(true); float[] dash = {2, 7}; // 虚线段长和间隙 dashPaint.setPathEffect(new DashPathEffect(dash, 0)); }
-
设置单个网格背景的高度。
@Override protected void drawDataSet(Canvas c, ILineDataSet dataSet) { //四个角的空间 偏移量 + 内容区域,就是整个LineChart的区域 // Log.e("tag", "path offsetLeft = " + mViewPortHandler.offsetLeft()); // Log.e("tag", "path offsetTop = " + mViewPortHandler.offsetTop()); // Log.e("tag", "path offsetRight = " + mViewPortHandler.offsetRight()); // Log.e("tag", "path offsetBottom = " + mViewPortHandler.offsetBottom()); // Log.e("tag", "path contentTop = " + mViewPortHandler.contentTop()); xAxisGradBgHeight = mViewPortHandler.contentHeight() / 2f / 3f; super.drawDataSet(c, dataSet); }
-
重写drawFillPath方法绘制背景。
@Override protected void drawFilledPath(Canvas c, Path filledPath, int fillColor, int fillAlpha) { bounds = new RectF(); //获取填充颜色的path的矩形范围 filledPath.computeBounds(bounds, false); if (onCustomLineChartRendererListener != null) { penaltyData = onCustomLineChartRendererListener.drawPenaltyData(); } //数据为0,绘制底部灰色区块 if (penaltyData != null && penaltyData.getTotal() == 0) { bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM); c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 2, getRightDrawRegion(penaltyData), mViewPortHandler.offsetTop() + xAxisGradBgHeight * 4), bgRectBottomPaint); return; } //x轴的下方 if (floatEqual(bounds.top,mViewPortHandler.contentHeight() / 2 + mViewPortHandler.offsetTop())) { //绘制底部灰色区块(所有区域) bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM); c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3, getRightDrawRegion(penaltyData), mViewPortHandler.offsetTop() + xAxisGradBgHeight * 4), bgRectBottomPaint); //绘制底部灰色区域(内容部分) bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM_2); c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3, bounds.right, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 5), bgRectBottomPaint); //绘制底部灰色区域(第二内容部分) bgRectBottomPaint.setColor(ColorUtil.BG_Rect_BOTTOM); c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3, bounds.right, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 4), bgRectBottomPaint); // } else { //x轴的上方 bgRectTopPaint.setColor(ColorUtil.BG_Rect_TOP); //绘制第一部分绿色矩形(仅仅内容区域) c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 1, bounds.right, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 2), bgRectTopPaint); bgRectTopPaint.setColor(ColorUtil.BG_Rect_BOTTOM); //绘制第二部分所有底部灰色区块(所有区域) c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 2, getRightDrawRegion(penaltyData), mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3), bgRectTopPaint); //绘制第二部分底部内容区域绿色区块(内容区域) bgRectTopPaint.setColor(ColorUtil.BG_Rect_TOP_2); c.drawRect(new RectF(bounds.left, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 2, bounds.right, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3), bgRectTopPaint); } super.drawFilledPath(c, filledPath, fillColor, fillAlpha); } public boolean floatEqual(float a, float b){ return Math.abs(Double.parseDouble(df.format(a))-Double.parseDouble(df.format(b))) < 0.5f; };
drawFilledPath这步是绘制贝塞尔曲线,所以我们自定义背景需要在这步之前。
呼吸线实现
LineChartRenderer的父类onDraw方法中有个drawExtras()方法,这步在填充完数据后,这样我们可以添加我们额外的功能,如呼吸线、点球,自定义坐标轴等。
@Override
public void drawExtras(Canvas c) {
super.drawExtras(c);
//绘制最右边的深色线 压在填充色上面
c.drawRect(new RectF(bounds.right - gridLineWidth / 2f, xAxisGradBgHeight * 1 + mViewPortHandler.offsetTop(), bounds.right + gridLineWidth / 2f, xAxisGradBgHeight * 5 + mViewPortHandler.offsetTop()), paintProgress);
if (onCustomLineChartRendererListener != null) {
penaltyData = onCustomLineChartRendererListener.drawPenaltyData();
}
//绘制坐标轴
textPaint.setColor(ColorUtil.axisColor);
c.drawRect(new RectF(bounds.right, mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3, mViewPortHandler.contentRight(), mViewPortHandler.offsetTop() + xAxisGradBgHeight * 3 + axisHeight), paintGrey);
}
如何让我们的线实现呼吸效果呢,这里我使用了渐变动画,并且通过view的postInvalidata实现了局部刷新。
ValueAnimator anim;
Handler handler = new Handler() ;
Runnable runnable;
public void startAnimation() {
if(anim == null) {
anim = ValueAnimator.ofFloat(1, 0);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAlpha = (float) animation.getAnimatedValue();
}
});
anim.setDuration(800);
anim.setRepeatCount(ValueAnimator.INFINITE);
anim.setRepeatMode(ValueAnimator.REVERSE);
}
anim.start();
if(runnable == null) {
runnable = new Runnable() {
@Override
public void run() {
if (mChart instanceof LineChart && bounds != null) {
((LineChart) mChart).postInvalidate((int)(bounds.right - gridLineWidth / 2f), (int)(xAxisGradBgHeight * 1 + mViewPortHandler.offsetTop()), (int)(bounds.right + gridLineWidth / 2f), (int)(xAxisGradBgHeight * 5 + mViewPortHandler.offsetTop()));
}
handler.postDelayed(this, 400);
}
};
}
handler.postDelayed(runnable,0);
}
动态变更paint。
@Override
public void drawData(Canvas c) {
paintProgress.setAlpha((int) (255 * mAlpha)); // 设置透明度.setAlpha((int) (255 * mAlpha)); // 设置透明度
super.drawData(c);
}
注意在适当的时候进行动画的取消。
public void cancelAnim(){
if (anim != null && anim.isRunning()) anim.cancel();
handler.removeCallbacksAndMessages(null);
}
除此之外我们还可以动态变更坐标轴。
binding.lineChart.xAxis.axisMaximum = 120f
binding.lineChart.notifyDataSetChanged() // 通知数据集变化
binding.lineChart.invalidate()
优化
由于项目中有视频直播,视频播放的时候会产生卡顿,分析发现,心跳图的绘制会发生掉帧的情况,因此考虑在linechatView同级布局中增加一个view,通过linechatview数据位置来动态更新view的位置。
增加长度为1dp的呼吸线view
<FrameLayout
android:id="@+id/flContainer"
android:layout_width="0dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/llLayout"
app:layout_constraintTop_toTopOf="parent">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/lineChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<View
android:id="@+id/vLine"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:visibility="gone"
/>
</FrameLayout>
根据比赛状态来开启或者取消动画。这里创建了一个定时器,1s通知外部view更新位置。TimerTask每次取消后都需要新建,否则会报错。
开启、取消定时器
ValueAnimator anim;
Timer timer = new Timer();
TimerTask timerTask;
public void startAnimation() {
cancelAnim();
timerTask= new TimerTask() {
@Override
public void run() {
// 执行动画
if (mChart instanceof LineChart && bounds != null) {
// LogUtils.e("linechart----(int)bounds.right }-"+(int)bounds.right+"---"+(int) (xAxisGradBgHeight * 1 + mViewPortHandler.offsetTop()));
onCustomLineChartRendererListener.drawLine((int)bounds.right,(int) (xAxisGradBgHeight * 1 + mViewPortHandler.offsetTop()),(int)(xAxisGradBgHeight * 4));
}
}
}; //首次0毫秒后执行,之后每1秒更新一次位置
timer.schedule(timerTask, 0, 1000);
}
public void cancelAnim() {
if (anim != null && anim.isRunning()) anim.cancel();
if(timerTask != null) {
timerTask.cancel();
}
}
通知view执行呼吸动画
override fun drawLine(marginStart: Int, marginTop: Int,height:Int) {
job?.cancel()
job = lifecycleScope.launch {
withContext(Dispatchers.Main) {
val layoutParams = FrameLayout.LayoutParams(1.dp, height)
layoutParams.marginStart = marginStart
layoutParams.topMargin = marginTop
binding.vLine.layoutParams = layoutParams
if (alphaAnimation == null) {
alphaAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_breathe)
}
if(binding.vLine.isGone) binding.vLine.isShow(true)
if (binding.vLine.animation == null) {
binding.vLine.startAnimation(alphaAnimation)
}
}
}
}
anim_breathe.xml文件
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="500"
android:fromAlpha="1"
android:interpolator="@android:anim/accelerate_interpolator"
android:repeatCount="infinite"
android:fillAfter="true"
android:repeatMode="reverse"
android:toAlpha="0" />
</set>
问题及解决
问题:
当2个球队只有一个球队有数据,会出现坐标轴从左下角开始的情况。
解决:
DefaultFillFormatter 是一个用于配置图表填充区域样式的类。我们可以发现当max或者min为正数和负数的时候,最小填充位置为0,其他时候位置则为最小值或者最大值。
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
float fillMin = 0f;
float chartMaxY = dataProvider.getYChartMax();
float chartMinY = dataProvider.getYChartMin();
LineData data = dataProvider.getLineData();
if (dataSet.getYMax() > 0 && dataSet.getYMin() < 0) {
fillMin = 0f;
} else {
float max, min;
if (data.getYMax() > 0)
max = 0f;
else
max = chartMaxY;
if (data.getYMin() < 0)
min = 0f;
else
min = chartMinY;
fillMin = dataSet.getYMin() >= 0 ? min : max;
}
return fillMin;
}
根据实际情况修改:
//下面的数据
set.fillFormatter =
IFillFormatter { dataSet, dataProvider ->
var fillMin = 0f
if (dataSet.yMax > 0 && dataSet.yMin < 0) {
fillMin = 0.0f
} else {
fillMin = dataSet.yMax
}
fillMin }
//上面的数据
setTwo.fillFormatter =
IFillFormatter { dataSet, dataProvider -> 0.0f }
实现效果