最终效果展示
代码实现
package com.example.chartdemo.util;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.List;
/**
* 双轴趋势图
* 说明:一个X轴对应左Y轴和右Y轴
*/
public class BiaxialBrokenLineChartView extends View {
public BiaxialBrokenLineChartView(Context context) {
this(context,null);
}
public BiaxialBrokenLineChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public BiaxialBrokenLineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private int widthMode;
private int heightMode;
private int widthSize;
private int heightSize;
private List<Integer> xItems;//X轴 轴项
private String xUnit;//X轴 刻度单位
private List<Integer> leftY_Items;//Y轴 轴项
private String leftY_Unit;//Y轴 刻度单位
private List<Integer> rightY_Items;//Y轴 轴项
private String rightY_Unit;//Y轴 刻度单位
private List<Integer> dataList1;//数据
private List<Integer> dataList2;//数据2
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthMode = MeasureSpec.getMode(widthMeasureSpec);
heightMode = MeasureSpec.getMode(heightMeasureSpec);
widthSize = MeasureSpec.getSize(widthMeasureSpec);
heightSize = MeasureSpec.getSize(heightMeasureSpec);
initPaint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (xItems==null || xItems.size()==0)return;
if (leftY_Items==null || leftY_Items.size()==0)return;
if (rightY_Items==null || rightY_Items.size()==0)return;
if (leftY_Items.size()!=rightY_Items.size())return;//要求左右刻度项数一致
if (dataList1==null || dataList1.size()==0)return;
if (dataList2==null || dataList2.size()==0)return;
float geSize=leftY_Items.size();
float geStep=(heightSize-140)/geSize;
for (int k=0;k<leftY_Items.size();k++){
//绘制阶层格线
canvas.drawLine(0+100,k*geStep+100,widthSize-100,k*geStep+100,gePaint);
//绘制左侧刻度
textPaint.setColor(Color.parseColor("#FF5722"));
canvas.drawText(leftY_Items.get(k)+"",0+80,k*geStep+100,textPaint);
//绘制右侧刻度
textPaint.setColor(Color.parseColor("#03A9F4"));
canvas.drawText(rightY_Items.get(k)+"",widthSize-100,k*geStep+100,textPaint);
}
float xZhouSize=xItems.size();
float xStep=(widthSize-200)/(xZhouSize+1);
for (int k=0;k<xItems.size();k++){
//绘制底部刻度
textPaint.setColor(Color.parseColor("#686868"));
if (k==xItems.size()-1){
canvas.drawText(xItems.get(k)+"("+xUnit+")",k*xStep+80+xStep,heightSize-60,textPaint);
}else {
canvas.drawText(xItems.get(k)+"",k*xStep+80+xStep,heightSize-60,textPaint);
}
}
//绘制左侧单位
textPaint.setColor(Color.parseColor("#686868"));
canvas.drawText(leftY_Unit,0+40,0+40,textPaint);
//绘制右侧单位
textPaint.setColor(Color.parseColor("#686868"));
canvas.drawText(rightY_Unit,widthSize-200,0+40,textPaint);
//========================下面是绘制数据=======================================================
float baseY=heightSize-100;//绘制数据原点坐标 Y 即:最后一根线的左端Y
float baseX=0+100;//绘制数据原点坐标 X 即:最后一根线的左端X
//绘制曲线1==================
Path path = new Path();
float leftX_Length=leftY_Items.get(0)-leftY_Items.get(leftY_Items.size()-1);//轴最大值 - 轴最小值
float dataStep1=(heightSize-200)/leftX_Length;//除去边缘长度后的像素点 与 轴数据总长度 的 对应关系值
for (int k=0;k<dataList1.size();k++){
if (k>=xItems.size())break;//超轴数据不用继续绘制
if (k<dataList1.size()-1){
float startX=baseX+(xStep*k)+xStep;
float startY=baseY-((dataList1.get(k)-leftY_Items.get(leftY_Items.size()-1))*dataStep1);
float endX=baseX+(xStep*(k+1))+xStep;
float endY=baseY-((dataList1.get(k+1)-leftY_Items.get(leftY_Items.size()-1))*dataStep1);
path.moveTo(startX,startY);//起点
path.quadTo((startX+endX)/2f,(startY+endY)/2f+40 //控制点偏移20个像素点
,endX,endY);//终点
canvas.drawPath(path,linePaint1);
}
}
//绘制曲线2==================
Path path2 = new Path();
float rightX_Length=rightY_Items.get(0)-rightY_Items.get(rightY_Items.size()-1);//轴最大值 - 轴最小值
float dataStep2=(heightSize-200)/rightX_Length;//除去边缘长度后的像素点 与 轴数据总长度 的 对应关系值
for (int k=0;k<dataList2.size();k++){
if (k>=xItems.size())break;//超轴数据不用继续绘制
if (k<dataList2.size()-1){
float startX=baseX+(xStep*k)+xStep;
float startY=baseY-((dataList2.get(k)-rightY_Items.get(rightY_Items.size()-1))*dataStep2);
float endX=baseX+(xStep*(k+1))+xStep;
float endY=baseY-((dataList2.get(k+1)-rightY_Items.get(rightY_Items.size()-1))*dataStep2);
path2.moveTo(startX,startY);//起点
path2.quadTo((startX+endX)/2f,(startY+endY)/2f+40 //控制点偏移20个像素点
,endX,endY);//终点
canvas.drawPath(path2,linePaint2);
}
}
}
private Paint linePaint1;
private Paint linePaint2;
private Paint zhouPaint;
private Paint gePaint;
private Paint textPaint;
private void initPaint(){
linePaint1 =new Paint();
linePaint1.setColor(Color.parseColor("#FF5722"));
linePaint1.setStyle(Paint.Style.STROKE);//不加这个不显示
linePaint1.setStrokeWidth(2);
linePaint1.setAntiAlias(true);//抗锯齿功能
linePaint2=new Paint();
linePaint2.setColor(Color.parseColor("#03A9F4"));
linePaint2.setStyle(Paint.Style.STROKE);//不加这个不显示
linePaint2.setStrokeWidth(2);
linePaint2.setAntiAlias(true);//抗锯齿功能
gePaint=new Paint();
gePaint.setColor(Color.parseColor("#A3AFB8"));
gePaint.setStyle(Paint.Style.STROKE);//不加这个不显示
gePaint.setStrokeWidth(0);
gePaint.setAntiAlias(true);//抗锯齿功能
zhouPaint=new Paint();
zhouPaint.setColor(Color.BLACK);
zhouPaint.setStrokeWidth(4);
zhouPaint.setAntiAlias(true);//抗锯齿功能
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(30);
}
/**
* 设置曲线1 数据
* @param dataList
*/
public void setData1(List<Integer> dataList){
this.dataList1 =dataList;
postInvalidate();
}
/**
* 设置曲线2 数据
* @param dataList
*/
public void setData2(List<Integer> dataList){
this.dataList2=dataList;
postInvalidate();
}
/**
* 设置X轴参数
* @param xList
* @param unit 刻度单位
*/
public void setDataScaleX(List<Integer> xList,String unit){
xItems=xList;
xUnit=unit;
postInvalidate();
}
/**
* 设置左侧Y轴参数
* @param yList
* @param unit 刻度单位
*/
public void setDataScaleLeftY(List<Integer> yList,String unit){
leftY_Items =yList;
leftY_Unit =unit;
postInvalidate();
}
/**
* 设置左侧Y轴参数
* @param yList
* @param unit 刻度单位
*/
public void setDataScaleRightY(List<Integer> yList,String unit){
rightY_Items =yList;
rightY_Unit =unit;
postInvalidate();
}
}
使用方法
- 在XML中引用
<com.example.chartdemo.util.BiaxialBrokenLineChartView
android:id="@+id/biaxial"
android:background="#EFEFEF"
android:layout_margin="20px"
android:layout_width="match_parent"
android:layout_height="600px" />
- 设置轴参数以及数据
private BiaxialBrokenLineChartView biaxial;
private List<Integer> biaxialData1;
private List<Integer> biaxialData2;
int biaxialForTag =0;
private void initBiaChart() {
//=====================================双轴趋势图=============================================
List<Integer> leftYs=new ArrayList<>();
leftYs.add(70);
leftYs.add(60);
leftYs.add(50);
leftYs.add(40);
leftYs.add(30);
leftYs.add(20);
leftYs.add(10);
List<Integer> rightYs=new ArrayList<>();
rightYs.add(35);
rightYs.add(30);
rightYs.add(25);
rightYs.add(20);
rightYs.add(15);
rightYs.add(10);
rightYs.add(5);
List<Integer> Xs=new ArrayList<>();
Xs.add(20);
Xs.add(25);
Xs.add(30);
Xs.add(35);
Xs.add(40);
Xs.add(45);
Xs.add(50);
Xs.add(55);
Xs.add(60);
Xs.add(65);
Xs.add(70);
biaxialData1=new ArrayList<>();
biaxialData1.add(20);
biaxialData1.add(46);
biaxialData1.add(54);
biaxialData1.add(34);
biaxialData1.add(54);
biaxialData1.add(70);
biaxialData1.add(50);
biaxialData1.add(55);
biaxialData1.add(60);
biaxialData1.add(45);
biaxialData1.add(55);
biaxialData2=new ArrayList<>();
biaxialData2.add(23);
biaxialData2.add(16);
biaxialData2.add(23);
biaxialData2.add(14);
biaxialData2.add(17);
biaxialData2.add(15);
biaxialData2.add(21);
biaxialData2.add(26);
biaxialData2.add(25);
biaxialData2.add(18);
biaxialData2.add(10);
biaxial=(BiaxialBrokenLineChartView) findViewById(R.id.biaxial);
biaxial.setDataScaleX(Xs,"岁");
biaxial.setDataScaleLeftY(leftYs,"幸福指数(AiX)");
biaxial.setDataScaleRightY(rightYs,"负债(万元)");
//biaxial.setData1(biaxialData1);
//biaxial.setData2(biaxialData2);
//========================模拟数据实现动效========================================
pieForTag =0;
doData1BiaChart();
}
private void doData1BiaChart() {
for (int a=0;a<biaxialData1.size();a++){
new BarHeartbeat().start(30, a, biaxialData1.get(a), new ActionCallback() {
@Override
public void toDo(Object o) {
List<Integer> list= (List<Integer>) o;
biaxialData1.set(list.get(0),list.get(1));
biaxial.setData1(biaxialData1);
}
});
}
for (int a=0;a<biaxialData2.size();a++){
new BarHeartbeat().start(30, a, biaxialData2.get(a), new ActionCallback() {
@Override
public void toDo(Object o) {
List<Integer> list= (List<Integer>) o;
biaxialData2.set(list.get(0),list.get(1));
biaxial.setData2(biaxialData2);
}
});
}
}
动效实现辅助类
package com.soface.chartdemo.util;
import android.os.Looper;
import android.os.Message;
import com.soface.chartdemo.Interface.ActionCallback;
import java.util.ArrayList;
import java.util.List;
/**
* 心跳计时器
*/
public class BarHeartbeat {
//事件标签
public final int ACTION_TAG =0xFF;
//要回调的接口
private ActionCallback thisActionDo;
//Handler事件监听器
private android.os.Handler mHandler = new android.os.Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case ACTION_TAG:
List<Integer> list= (List<Integer>) msg.obj;
thisActionDo.toDo(list);
break;
}
}
};
//启动
public void start(int countDownTime,int index,int maxNumber,ActionCallback actionDo){
thisActionDo=actionDo;
for (int a=0;a<=maxNumber;a++){
Message msg = mHandler.obtainMessage();
msg.what = ACTION_TAG;
List<Integer> list=new ArrayList<>();
list.add(index);
list.add(a);
msg.obj=list;
mHandler.sendMessageDelayed(msg,countDownTime*a);
}
};
}