Android自定义View实现双轴走势图

最终效果展示

在这里插入图片描述

代码实现
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();
    }

}

使用方法
  1. 在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" />
  1. 设置轴参数以及数据
	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);
        }
    };

}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝命三郎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值