自定义控件之折线图

分析:折线的实现可以用canvas对象调用drawLine方法绘制点与点之间的直线,或使用Path对象,调用canvas的drawPath方法来一次绘制所有的直线。在折线下面实现了颜色渐变,我们可以使用线性LinearGradient类来实现,最后处理触摸事件,重新绘绘制界面,效果如下图所示:

                                                                                 

1.数据准备

public class Dot {
    private int x;
    private int y;
    private int value;
    private int transformValue;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public int getTransformValue() {
        return transformValue;
    }

    public void setTransformValue(int transformValue) {
        this.transformValue = transformValue;
    }
}

 

2.具体代码实现

package com.cmj.linechart;

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.Rect;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class LineChart extends View {
    private Paint mAxisPaint;
    private Paint mDotPaint;
    private Paint mLinePaint;
    private Paint mGradientPaint;
    public static final int[] DEFAULT_GRADIENT_COLORS = {Color.BLUE, Color.GREEN};
    private int[] mDataList;
    private int mMax;
    private String[] mHorizontalAxis;
    private int mRadius;
    private int mClickRadius;
    private List<Dot> mDots = new ArrayList<>();
    private Rect mTextRect;
    private int mGap;
    private Path mPath;
    private Path mGradientPath;
    private int mStep;
    private int mSelectDotIndex = -1;
    private int mSelectDotColor;
    private int mNormalDotColor;
    private int mLineColor;

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

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

    public LineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChart);
        mLineColor = typedArray.getColor(R.styleable.LineChart_line_color, Color.BLACK);
        mNormalDotColor = typedArray.getColor(R.styleable.LineChart_dot_normal_color, Color.BLACK);
        mSelectDotColor = typedArray.getColor(R.styleable.LineChart_dot_selected_color, Color.RED);
        typedArray.recycle();
        initView();
        mPath = new Path();
        mGradientPath=new Path();
        mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
        mClickRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
        mGap = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
        mTextRect = new Rect();
    }

    private void initView() {
        mAxisPaint = new Paint();
        mAxisPaint.setAntiAlias(true);
        mAxisPaint.setTextSize(20);
        mAxisPaint.setTextAlign(Paint.Align.CENTER);
        mDotPaint = new Paint();
        mDotPaint.setAntiAlias(true);
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeWidth(3);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setColor(mLineColor);
        mGradientPaint = new Paint();
        mGradientPaint.setAntiAlias(true);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        //清空点的集合
        mDots.clear();
        //去除padding,计算绘制区域的宽高
        int width = w - getPaddingLeft() - getPaddingRight();
        int height = h - getPaddingBottom() - getPaddingTop();
        //根据点的个数平分宽度
        mStep = width / (mDataList.length - 1);
        //根据文本画笔计算绘制x轴第一个个坐标文本占据的矩形边界,这里主要获取其高度
        mAxisPaint.getTextBounds(mHorizontalAxis[0], 0, mHorizontalAxis[0].length(), mTextRect);
        //折线图的最大高度
        int maxBarHeight = height - mTextRect.height() - mGap;
        //计算折线图最大高度和最大数据的比值
        float heightRatio = maxBarHeight / mMax;
        for (int i = 0; i < mDataList.length; i++) {
            Dot dot = new Dot();
            dot.setValue(mDataList[i]);
            dot.setTransformValue((int) (dot.getValue() * heightRatio));
            dot.setX(mStep * i + getPaddingLeft());
            dot.setY(getPaddingTop() + maxBarHeight - dot.getTransformValue());
            //当时第一个点时,将路径移动到该点
            if (i == 0) {
                mPath.moveTo(dot.getX(), dot.getY());
                mGradientPath.moveTo(dot.getX(), dot.getY());
            } else {
                mPath.lineTo(dot.getX(), dot.getY());
                mGradientPath.lineTo(dot.getX(), dot.getY());
            }
            //如果到了最后一个点
            if (i == mDataList.length - 1) {
                int bottom = getPaddingTop() + maxBarHeight;
                //将渐变路径连接到最后一个点在竖直方向的最低点
                mGradientPath.lineTo(dot.getX(), bottom);
                Dot firstDot = mDots.get(0);
                //连接到第一个点在竖直方向的最低点
                mGradientPath.lineTo(firstDot.getX(), bottom);
                //连接到第一个点,形成闭合区域
                mGradientPath.lineTo(firstDot.getX(), firstDot.getY());
            }
            mDots.add(dot);
        }
        Shader linearGradient = new LinearGradient(0, 0, 0, getHeight(), DEFAULT_GRADIENT_COLORS, null, Shader.TileMode.CLAMP);
        mGradientPaint.setShader(linearGradient);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mPath, mLinePaint);
        canvas.drawPath(mGradientPath, mGradientPaint);
        for (int i = 0; i < mDots.size(); i++) {
            //绘制文本
            String text = mHorizontalAxis[i];
            int x = getPaddingLeft() + i * mStep;
            int y = getHeight() - getPaddingBottom();
            //绘制x轴
            canvas.drawText(text, x, y, mAxisPaint);
            Dot dot = mDots.get(i);
            if (i == mSelectDotIndex) {
                mDotPaint.setColor(mSelectDotColor);
                canvas.drawText(String.valueOf(mDataList[i]), dot.getX(), dot.getY() - mRadius - mGap, mAxisPaint);
            } else {
                mDotPaint.setColor(mNormalDotColor);
            }
            canvas.drawCircle(dot.getX(), dot.getY(), mRadius, mDotPaint);
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mSelectDotIndex = getClicDotIndex(event.getX(), event.getY());
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                mSelectDotIndex=-1;
                invalidate();
                break;
        }
        return true;
    }

    /**
     * 判断选中的点
     * @param x
     * @param y
     * @return
     */
    private int getClicDotIndex(float x, float y) {
        int index = -1;
        for (int i = 0; i < mDots.size(); i++) {
            Dot dot = mDots.get(i);
            int left = dot.getX() - mClickRadius;
            int top = dot.getY() - mClickRadius;
            int right = dot.getX() + mClickRadius;
            int bottom = dot.getY() + mClickRadius;
            if (x > left && x < right && y > top && y < bottom) {
                index = i;
                break;
            }

        }
        return index;
    }

    public void setHorizontalAxis(String[] horizontalAxis) {
        mHorizontalAxis = horizontalAxis;
    }
    /**
     * 设置柱状图数据
     *
     * @param dataList
     * @param max
     */
    public void setDataList(int[] dataList, int max) {
        mDataList = dataList;
        mMax = max;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值