Android自定义view可拖动的柱状图折线图组合可拖动的折线图

公司最近一个需求就是需要每年的每个月的消费概况的统计图 ,因为涉及到一些乱七八糟的需求 。先看图

其中要求本年未到的年份不一样的颜色,且数据不用画出来当然也没有数据。折线下面有个阴影。所以决定自己撸个图。看布局:

再看attr这个就不用解释了吧,不知道请到宏洋大神的博客里搜zidingyiview


        
        
        
        
        
    

然后看三部曲了,onMeasure,onLayout,onDraw

第一步当然是测量长宽了

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }

        setMeasuredDimension(width, height);
    }

然后onlayout

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (ischangeFirst) {
            ischangeFirst =false;
            mHeight = getHeight();
            mSize = getWidth() /valueSize * 2;
            mChartWidth = getWidth() / valueSize * 2;
            textStartplace = getWidth() / valueSize * 2;
            textWidth = getWidth() / valueSize * 2;
            linearStartX = mSize / 2;
            xOri = 0;
            yOri = getHeight()/3*2;
            minXInit = getWidth() - mSize * (valueSize - 1) - mSize / 2;//计算最小的长度
            minZxInit = getWidth() - mSize * (valueSize);
            maxXInit = linearStartX;
            zhuStartX = 0;
            maxZxInit = mSize;
            initData();
        }
        super.onLayout(changed, left, top, right, bottom);
    }

最后ondraw

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        mPaint.setColor(xyColor);
        //渐变
        gradient = new LinearGradient(0,0,getWidth(),mHeight/3*2,context.getResources().getColor(R.color.color_7c98ff),context.getResources().getColor(R.color.color_77b0ff),Shader.TileMode.REPEAT);
        mbackgroundPaint.setShader(gradient);
        canvas.drawRect(0, 0, getMeasuredWidth(), mHeight/3*2, mbackgroundPaint);
        //画柱状图
        drawRect(canvas);
        //划地下月份
       drawMonth(canvas);
       //画折线图和圆点
        drawLine(canvas);
        Log.e("234","linearStartX===="+linearStartX);
    }

标注已经写的很清楚了,还不知道的欢迎评论。

最后加上全部的代码

package com.py.ysl.view.myview;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

import com.py.ysl.R;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lizhijun 2018.8.1
 * 自定义柱状图
 */
public class BarGraphView extends View{

    private int bargrapColor;//柱状图的颜色
    private int selectRightColor;
    private int xyColor;//
    private int textSize;//月份字体大小
    private int textColor;//月份字体颜色
    private Paint mPaint, mChartPaint,mbackgroundPaint;//横轴画笔、柱状图画笔、背景图画笔
    private Paint lineapaint;//折线的画笔
    private Paint dotPain;//圆点的画笔
    private Paint zxbPaint;//折线下面的背景色
    private Rect mBound;
    private float mHeight, mChartWidth, mSize;//屏幕宽度高度、柱状图起始位置、柱状图宽度
    private float textStartplace,textWidth;//月份起始位置、月份的间隔宽度
    //x轴的原点坐标
    private int xOri;
    //y轴的原点坐标
    private int yOri;
    //第一个点对应的最大Y坐标
    private float maxXInit;
    //第一个点对应的最小X坐标
    private float minXInit;

    //第一个柱形的最大Y坐标
    private float maxZxInit;
    //第一个柱形对应的最小X坐标
    private float minZxInit;
    private  LinearGradient gradient;//背景渐变色

    private float linearStartX;//折线第一个点
    private float zhuStartX;//柱形第一个位置
    private Context context;
    //是否在ACTION_UP时,根据速度进行自滑动,没有要求,建议关闭,过于占用GPU
    private boolean isScroll = false;
    //是否正在滑动
    private boolean isScrolling = false;
    //点击的点对应的X轴的第几个点,默认1
    private int selectIndex = 1;
    //数据源
    //x轴坐标对应的数据
    private List xValue = new ArrayList<>();
    //y轴坐标对应的数据
    private int yValue =0;
    //折线对应的数据
    private Map value = new HashMap<>();

    private int posMonth = 12;//当前月份,今年若超过当前月份的则不画
    private int posPlace = 0;//一开始的位移 用于显示到最新的月份


    private boolean isMonthFirst = false;//用于位移时是否第一次改变数据
    private boolean isRectFirst = false;
    private boolean ischangeFirst = true;//防止onlayout两次改变数据
    private int valueSize = 12;//正常分割为12份

    //速度检测器
    private VelocityTracker velocityTracker;

    public BarGraphView(Context context){
        this(context,null);

    }
    public BarGraphView(Context context,AttributeSet attrs){
        this(context,attrs,0);
    }
    public BarGraphView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        this.context = context;
        initView(context,attrs,defStyleAttr);
    }

    private void initView(Context context,AttributeSet attrs,int defStyleAttr){
        TypedArray array =context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView,defStyleAttr,0);
        int count = array.getIndexCount();
        for (int i=0;i maxXInit) {
                    linearStartX = maxXInit;
                    zhuStartX = 0;
                } else {
                    linearStartX = linearStartX + offX;
                    zhuStartX += offX;
                }
               invalidate();
                break;
            case MotionEvent.ACTION_UP:
                this.getParent().requestDisallowInterceptTouchEvent(false);//当该view获得点击事件,就请求父控件不拦截事件
                clickAction(event);
                scrollAfterActionUp();
                recycleVelocityTracker();
                break;

        }
        return true;
    }
    /**
     * 绘制显示Y值的浮动框
     *
     * @param canvas
     * @param x
     * @param y
     * @param text
     * @param pos 第几个
     */
    private void drawFloatTextBox(Canvas canvas, float x, float y, String text,int pos) {
        int dp6 = dpToPx(6);
        int dp7 = dpToPx(7);
        int dp35 = dpToPx(35);
        int dp40 = dpToPx(40);
        //p1
        Path path = new Path();
        lineapaint.setColor(Color.parseColor("#ffffff"));
        lineapaint.setStrokeWidth(dpToPx(1));
        lineapaint.setAntiAlias(true);
        lineapaint.setStyle(Paint.Style.FILL);
        float left = x-dp40;
        float top = y-dp35;
        float right =x+dp40;
        float bottom =y-dp6;
        if (pos==0){
            left=x-mSize/3;
            right=dp40*2+x-mSize/3;
        }
        if (pos==value.size()-1){
            left=x+mSize/3-dp40*2;
            right=x+mSize/3;
        }
        RectF oval3 = new RectF(left, top, right,bottom);// 设置个新的长方形
        canvas.drawRoundRect(oval3, dpToPx(7), dpToPx(7), lineapaint);//第二个参数是x半径,第三个参数是y半径
        path.moveTo(x,y);
        path.lineTo(x-dp7,y-dp7);
        path.lineTo(x+dp7,y-dp7);
        path.close();
        canvas.drawPath(path, lineapaint);



        mPaint.setTextSize(textSize);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setColor(textColor);//要写在draw里面不然画不出来
        mPaint.getTextBounds(text,0,text.length(),mBound);
        if (pos==value.size()-1){
            canvas.drawText(text, x-mSize/3, y  - mBound.height()/3*2-dp7, mPaint);
        }else if (pos==0){
            canvas.drawText(text, x+mSize/3, y  - mBound.height()/3*2-dp7, mPaint);
        }else {
            canvas.drawText(text, x , y  - mBound.height()/3*2-dp7, mPaint);
        }
    }
    /**
     * 获取丈量文本的矩形
     *
     * @param text
     * @param paint
     * @return
     */
    private Rect getTextBounds(String text, Paint paint) {
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);
        return rect;
    }
    /**
     * 点击X轴坐标或者折线节点
     *
     * @param event
     */
    private void clickAction(MotionEvent event) {
        if (value==null||xValue==null||value.size()==0||xValue.size()==0)
            return;
        int dp8 = dpToPx(18);
        float eventX = event.getX();
        float eventY = event.getY();
        for (int i = 0; i < xValue.size(); i++) {
            //节点
            float x = linearStartX + mSize * i;
            float y = yOri - yOri * value.get(xValue.get(i)) / yValue;
            if (eventX >= x - dp8 && eventX <= x + dp8 &&
                    eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//每个节点周围8dp都是可点击区域
                if (i+1<=posMonth) {
                    selectIndex = i + 1;
                    invalidate();
                }
                return;
            }
        }
    }
    /**
     * dp转化成为px
     *
     * @param dp
     * @return
     */
    private int dpToPx(int dp) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1));
    }
    /**
     * 获取速度跟踪器
     *
     * @param event
     */
    private void obtainVelocityTracker(MotionEvent event) {
        if (!isScroll)
            return;
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        }
        velocityTracker.addMovement(event);
    }
    /**
     * 回收速度跟踪器
     */
    private void recycleVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }
    /**
     * 获取速度
     *
     * @return
     */
    private float getVelocity() {
        if (velocityTracker != null) {
            velocityTracker.computeCurrentVelocity(1000);
            return velocityTracker.getXVelocity();
        }
        return 0;
    }
    /**
     * 手指抬起后的滑动处理
     */
    private void scrollAfterActionUp() {
        if (!isScroll)
            return;
        final float velocity = getVelocity();
        float scrollLength = maxXInit - minXInit;
        if (Math.abs(velocity) < 10000)//10000是一个速度临界值,如果速度达到10000,最大可以滑动(maxXInit - minXInit)
            scrollLength = (maxXInit - minXInit) * Math.abs(velocity) / 10000;
        ValueAnimator animator = ValueAnimator.ofFloat(0, scrollLength);
        animator.setDuration((long) (scrollLength / (maxXInit - minXInit) * 1000));//时间最大为1000毫秒,此处使用比例进行换算
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                if (velocity < 0 && linearStartX > minXInit) {//向左滑动
                    if (linearStartX - value <= minXInit)
                        linearStartX = minXInit;
                    else
                        linearStartX = linearStartX - value;
                } else if (velocity > 0 && linearStartX < maxXInit) {//向右滑动
                    if (linearStartX + value >= maxXInit)
                        linearStartX = maxXInit;
                    else
                        linearStartX = linearStartX + value;
                }
                invalidate();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                isScrolling = true;
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                isScrolling = false;
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                isScrolling = false;
            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        animator.start();

    }

    /**
     * 设置初始值
     * @param value
     * @param xValue
     * @param yValue 纵坐标的最大值
     */
    public void setValue(Map value, List xValue,int yValue) {
        this.value = value;
        this.xValue = xValue;
        this.yValue = yValue;
        invalidate();
    }

    /**
     * 设置当前月份
     * @param posMonth
     */
    public void setCurrentMonth(int posMonth){
        this.posMonth = posMonth;
        selectIndex = posMonth;
        getDisPlace(posMonth);
        invalidate();

    }
    /**
     * 设置一开始位移
     * @param posPlace
     */
    public void setCurrentDisplace(int posPlace){
        this.posPlace = posPlace;
        isRectFirst = true;
        isMonthFirst = true;
    }
    private void getDisPlace(int posMonth){
        if (posMonth<=6){
            setCurrentDisplace(0);
        }else {
            setCurrentDisplace(posMonth-6);
        }

    }
}

最后就是在activity里面调用了

 

 ListStrList = new ArrayList<>();
        ListintList = new ArrayList<>();
        //折线对应的数据
         Map value = new HashMap<>();
        for (int i = 0; i < 12; i++) {
            StrList.add((i + 1) + "月");
        }
        value.put( "1月", 600);//60--240
        value.put( "2月", 500);//60--240
        value.put( "3月", 800);//60--240
        value.put( "4月", 900);//60--240
        value.put( "5月", 400);//60--240
        value.put( "6月", 900);//60--240
        value.put( "7月", 1100);//60--240
        value.put( "8月", 1300);//60--240
        value.put( "9月", 1800);//60--240
        value.put( "10月", 1400);//60--240
        value.put( "11月", 1600);//60--240
        value.put( "12月", 1000);//60--240 value.put( "1月", 200);//60--240

       
        bar_view.setValue(value,StrList,1800*3/2);
        bar_view.setCurrentMonth(9);//当没有满一年的时候需要用到

就此一个可滑动的就完成了。最后感谢网上的一些大神,看了一些别人的例子才最后画出来的大神连接

https://blog.csdn.net/u014544193/article/details/54313257

你可能感兴趣的:(安卓)