Android自定义View实现可拖动条形统计图

最终效果

可滚动条形统计图

代码实现
package com.example.scrollbarchat.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.List;

public class ScrollBarChart extends View {

    public ScrollBarChart(Context context) {
        this(context,null);
    }

    public ScrollBarChart(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ScrollBarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    Paint paint;
    Paint linePaint;
    Paint textPaint;
    float widthSize;
    float heightSize;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        widthSize=MeasureSpec.getSize(widthMeasureSpec);
        heightSize=MeasureSpec.getSize(heightMeasureSpec);

        int[] mColors = {Color.parseColor("#FF9800"),Color.parseColor("#FFCE85"),Color.parseColor("#FFEBCF")};
        LinearGradient linearGradient=new LinearGradient(50, 0, 50, heightSize, mColors, null, Shader.TileMode.MIRROR);
        paint=new Paint();
        paint.setShader(linearGradient);

        linePaint=new Paint();
        linePaint.setStrokeWidth(0);
        linePaint.setColor(Color.parseColor("#CACACA"));

        textPaint=new Paint();
        textPaint.setStrokeWidth(2);
        textPaint.setColor(Color.parseColor("#FF9800"));
        textPaint.setTextSize(30);
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    boolean initTag=true;
    float moveValue=0;//移动值
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘制横格条
        linePaint.setStrokeWidth(0);
        float squareStep=(heightSize-60-60)/39;
        for (int k=0;k<40;k++){
            if (k==0){
                canvas.drawLine(0,1+60,widthSize,1+60,linePaint);
            }else {
                canvas.drawLine(0,squareStep*k+60,widthSize,squareStep*k+60,linePaint);
            }
        }

        //绘制纵格条
        for (int k=0;k<(widthSize/squareStep);k++){
            if (k==0){
                canvas.drawLine(1,0+60,1,heightSize-60,linePaint);
            }else {
                canvas.drawLine(k*squareStep,0+60,k*squareStep,heightSize-60,linePaint);
            }
        }

        //约束绘制条件================================================================================
        if (dataList==null || dataList.size()==0)return;//约束:非空
        if (sectionNameList ==null || sectionNameList.size()==0)return;//约束:非空
        if (dataList.size()!= sectionNameList.size())return;//约束:数据项和名称项长度相等
        for (int k=0;k<dataList.size()-1;k++){
            if (dataList.get(k).size()!=dataList.get(k+1).size())return;//约束:所有子列表长度必须相等
        }

        //开始绘制数据================================================================================
        float maxData=0;//计算出列表最大值
        float minData=1000000000;//计算出列表最小值
        for (int a=0;a<dataList.size();a++){
            for (int k=0;k<dataList.get(a).size();k++){
                if (maxData<dataList.get(a).get(k)){
                    maxData=dataList.get(a).get(k);
                }
                if (minData>dataList.get(a).get(k)){
                    minData=dataList.get(a).get(k);
                }
            }
        }

        float dataStep=(heightSize-60-60)/maxData;//数据步进

        //倒过来绘制,从末尾开始绘制
        int dataSize=dataList.size();
        int childDataSize=dataList.get(dataSize-1).size();
        float barWidth=widthSize/(childDataSize+childDataSize+1);// 条形宽度=布局宽度/{【条形个数+(条形个数+1)】}
        float modeRightX=0;
        float modeLeftX=0;
        for (int a=dataSize;a>0;a--){
            List<Integer> childList=dataList.get(a-1);

            for (int k=0;k<childList.size();k++){
                float left=(widthSize-((-a+dataSize)*widthSize))-((2*k+2)*barWidth)+moveValue;
                float top=(heightSize-60)-(childList.get(k))*dataStep;
                float right=(widthSize-((-a+dataSize)*widthSize))-((2*k+1)*barWidth)+moveValue;
                float bottom=heightSize-60;
                //绘制方形条
                canvas.drawRect(left, top, right, bottom, paint);

                //绘制数据值
                textPaint.setColor(Color.parseColor("#FF9800"));
                textPaint.setTextAlign(Paint.Align.CENTER);
                textPaint.setTextSize(30);
                if (childList.get(k)==maxData){
                    textPaint.setTextAlign(Paint.Align.LEFT);
                    canvas.drawText(childList.get(k)+"(峰值)",right+dataStep,top+30,textPaint);
                }else if (childList.get(k)==minData){
                    canvas.drawText(childList.get(k)+"(低谷)",(left+right)/2f,top-30,textPaint);
                }else {
                    canvas.drawText(String.valueOf(childList.get(k)),(left+right)/2f,top-30,textPaint);
                }

                //绘制刻度
                linePaint.setStrokeWidth(4);
                linePaint.setColor(Color.GRAY);
                canvas.drawLine(right,heightSize-60,right,heightSize-60-10,linePaint);
                canvas.drawLine(left,heightSize-60,left,heightSize-60-10,linePaint);

                //绘制刻度文字
                textPaint.setColor(Color.GRAY);
                canvas.drawText(scaleList.get(k)+ scaleUnit,(left+right)/2f,heightSize-30,textPaint);

                //存储当前模块左右两边的X坐标值
                if (k==0)modeRightX=right;
                if (k==childList.size()-1)modeLeftX=left;
            }

            //绘制X轴
            linePaint.setStrokeWidth(4);
            linePaint.setColor(Color.GRAY);
            canvas.drawLine(0,heightSize-60,widthSize,heightSize-60,linePaint);

            //绘制区域名称
            linePaint.setStrokeWidth(2);
            linePaint.setColor(Color.parseColor("#CACACA"));
            canvas.drawLine(modeLeftX,30,modeLeftX,60,linePaint);
            canvas.drawLine(modeLeftX,30,modeLeftX+(widthSize/2f-200),30,linePaint);

            canvas.drawLine(modeRightX,30,modeRightX,60,linePaint);
            canvas.drawLine(modeRightX,30,modeRightX-(widthSize/2f-200),30,linePaint);

            textPaint.setTextSize(40);
            textPaint.setColor(Color.GRAY);
            canvas.drawText(sectionNameList.get(a-1),(modeRightX+modeLeftX)/2f,30,textPaint);
        }

    }

    float downX;//按下的X坐标
    float oldMoveValue;//之前的移动值
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (event.getAction()==MotionEvent.ACTION_DOWN){
            downX=event.getX();
            oldMoveValue=moveValue;
            return true;
        }

        if (event.getAction()==MotionEvent.ACTION_MOVE){
            moveValue=(event.getX()-downX)+oldMoveValue;//叠加偏移量
            postInvalidate();
            return true;
        }

        if (event.getAction()==MotionEvent.ACTION_UP){
            return true;
        }

        return super.onTouchEvent(event);
    }

    //设置数据
    private List<List<Integer>> dataList;
    private String dataUnit ="";
    public void setData(List<List<Integer>> dataList,String unit){
        this.dataList=dataList;
        dataUnit=unit;
        postInvalidate();
    }

    //设置区间名称
    private List<String> sectionNameList;
    private String sectionUnit ="";
    public void setSectionName(List<String> sectionNameList){
        this.sectionNameList =sectionNameList;
        postInvalidate();
    }

    private List<Integer> scaleList;
    private String scaleUnit ="";
    public void setScaleName(List<Integer> scaleList,String unit){
        this.scaleList=scaleList;
        scaleUnit =unit;
        postInvalidate();
    }

}
使用方法
  1. 带xml中引用
<com.example.scrollbarchat.view.ScrollBarChart
        android:id="@+id/chart"
        android:layout_width="match_parent"
        android:layout_height="600px"/>
  1. 导入数据
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initChart();

        Button reset=(Button) findViewById(R.id.reset);
        reset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doReset();
            }
        });

    }

    private List<Integer> firstYear;
    private List<Integer> secondYear;
    private List<Integer> thirdYear;
    private ScrollBarChart chart;
    private void initChart() {

        List<Integer> monthList=new ArrayList<>();
        monthList.add(12);//12月
        monthList.add(11);//11月
        monthList.add(10);//10月
        monthList.add(9);//9月
        monthList.add(8);//8月
        monthList.add(7);//7月
        monthList.add(6);//6月
        monthList.add(5);//5月
        monthList.add(4);//4月
        monthList.add(3);//3月
        monthList.add(2);//2月
        monthList.add(1);//1月

        firstYear=new ArrayList<>();
        firstYear.add(5);//12月
        firstYear.add(8);//11月
        firstYear.add(34);//10月
        firstYear.add(56);//9月
        firstYear.add(90);//8月
        firstYear.add(145);//7月
        firstYear.add(78);//6月
        firstYear.add(45);//5月
        firstYear.add(37);//4月
        firstYear.add(87);//3月
        firstYear.add(66);//2月
        firstYear.add(108);//1月

        secondYear=new ArrayList<>();
        secondYear.add(77);//12月
        secondYear.add(87);//11月
        secondYear.add(90);//10月
        secondYear.add(78);//9月
        secondYear.add(46);//8月
        secondYear.add(145);//7月
        secondYear.add(321);//6月
        secondYear.add(105);//5月
        secondYear.add(98);//4月
        secondYear.add(78);//3月
        secondYear.add(53);//2月
        secondYear.add(56);//1月

        thirdYear=new ArrayList<>();
        thirdYear.add(108);//12月
        thirdYear.add(99);//11月
        thirdYear.add(78);//10月
        thirdYear.add(56);//9月
        thirdYear.add(22);//8月
        thirdYear.add(33);//7月
        thirdYear.add(44);//6月
        thirdYear.add(55);//5月
        thirdYear.add(66);//4月
        thirdYear.add(77);//3月
        thirdYear.add(88);//2月
        thirdYear.add(76);//1月

        List<List<Integer>> list=new ArrayList<>();
        list.add(firstYear);
        list.add(secondYear);
        list.add(thirdYear);

        List<String> modeNameList=new ArrayList<>();
        modeNameList.add("2020年");
        modeNameList.add("2021年");
        modeNameList.add("2022年");

        chart=(ScrollBarChart) findViewById(R.id.chart);
        chart.setData(list,"件");
        chart.setSectionName(modeNameList);
        chart.setScaleName(monthList,"月");

        doReset();

    }

    private void doReset() {
        //模拟数据实现动态效果
        for (int a=0;a<firstYear.size();a++){
            new BarHeartbeat().start(30, a, firstYear.get(a), new ActionCallback() {
                @Override
                public void toDo(Object o) {
                    List<Integer> list2= (List<Integer>) o;
                    firstYear.set(list2.get(0),list2.get(1));

                    List<List<Integer>> list=new ArrayList<>();
                    list.add(firstYear);
                    list.add(secondYear);
                    list.add(thirdYear);

                    chart.setData(list,"件");
                }
            });
        }

        for (int a=0;a<secondYear.size();a++){
            new BarHeartbeat().start(30, a, secondYear.get(a), new ActionCallback() {
                @Override
                public void toDo(Object o) {
                    List<Integer> list2= (List<Integer>) o;
                    secondYear.set(list2.get(0),list2.get(1));

                    List<List<Integer>> list=new ArrayList<>();
                    list.add(firstYear);
                    list.add(secondYear);
                    list.add(thirdYear);

                    chart.setData(list,"件");
                }
            });
        }

        for (int a=0;a<thirdYear.size();a++){
            new BarHeartbeat().start(30, a, thirdYear.get(a), new ActionCallback() {
                @Override
                public void toDo(Object o) {
                    List<Integer> list2= (List<Integer>) o;
                    thirdYear.set(list2.get(0),list2.get(1));

                    List<List<Integer>> list=new ArrayList<>();
                    list.add(firstYear);
                    list.add(secondYear);
                    list.add(thirdYear);

                    chart.setData(list,"件");
                }
            });
        }
    }

}

实现动态效果的辅助类和接口

package com.example.scrollbarchat.unit;

import android.os.Looper;
import android.os.Message;

import com.example.scrollbarchat.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);
        }
    };

}
package com.example.scrollbarchat.Interface;

public interface ActionCallback {

    void toDo(Object o);

}

到此结束,复制使用即可,不需要关注细节实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绝命三郎

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

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

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

打赏作者

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

抵扣说明:

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

余额充值