关于自绘渐变折线图的实现

渐变风格的折线图

参考了好几个大牛的博客之后,还是上手自己写。因为现在开源的图表框架有很多是作者没有心情再维护了的,而且有的框架也太大,使得后期代码非常臃肿,在公司的计划里,这个折线图只是作为一个很小的模块,用来展示信息就可以,完全不需要框架其他的功能,所以我根据自己的需求自绘了一个view。


项目需求

项目要求封装出来的控件主要提供一个setParams方法就可以,其中三个形参分别是x轴的起始字符串、终止字符串和点的Y坐标的数组。也就是说,我不用纠结X坐标的取值,x代表的是数组的个数,只需要写出起始字符串和终止字符串就可以(其实也就是日期)。然后y轴规定是写出五个数字,以数组的最大值和最小值的差值算一个比例,然后进行平均分配。

因此,项目需求可以简化到:

  • 画出五条水平线,分别对应Y坐标的五个值
  • x坐标的起始位置和终止位置的字符串(由于实验的关系,我感觉用drawText方法比较顺手,应该也可以用TextView这个控件做,不过我没试过,只是提供给自己以后一种思路)
  • 把所有的点找出来,画线(有很多框架是把点和线分开做,而我这边的需求是不用显示点,为了达到最精简的程度,我把这个省略了)
  • 填充线与X轴区域的渐变颜色

具体代码

public class LineChartView extends View {

private static final int DEFAULT_PADDING = 10;
private final float DP = getResources().getDisplayMetrics().density;
private final int mDipPadding;
private final int mFillColor;
private final int mAxisColor;
private final float mStrokeWidth;
private final int mStrokeSpacing;
private boolean mUseDips;
private final int mBackgroundColor;
private Paint mPaint = new Paint();
private Bitmap mFullImage;
private Canvas mCanvas;
//提出来的变量
private float mTextSizeOfAxis = 15;//坐标轴字体大小
private float mXAxisUsedWidth = 400;//x轴使用宽度
private String[] mXAxisContents = {"无", "无"};//x轴的坐标内容
private float mYAxisPoints[];//数据y轴的坐标
private float mMinYAxis;//y轴起点,根据数组最大值和最小值的差值换算
private float mMaxYAxis;//y轴重点,根据数组最大值和最小值的差值换算
private String mXAxisColor;//横线的颜色,默认是#E6E6E6
private String mTextColor;//字体的颜色,默认是#999999
private String mLineStartColor;//渐变折线的起始颜色(淡),默认是#FFCC02
private String mLineEndColor;//渐变折线的终止颜色(浓),默认是#FF4E00
private String mPathStartColor;//填充色块的起始颜色(淡),默认是#11FFFFFF
private String mPathEndColor;//填充色块的终止颜色(浓),默认是#AAFF8247
private boolean bNeedClean = false;

//使用此函数设置x轴起始坐标和终结坐标,利率(浮点型)数组。
public void setParams(String xminDesc, String xmaxDesc, float values[]) {
    bNeedClean = true;
    postInvalidate();
    setmXAxisContents(new String[]{xminDesc, xmaxDesc});
    setmYAxisPoints(values);
    this.mYAxisPoints = values;
    float mMinValues = values[0];
    float mMaxValues = values[0];
    for (int i = 1; i < values.length; i++) {
        if (mMinValues > values[i]) {
            mMinValues = values[i];
        }
        if (values[i] > mMaxValues) {
            mMaxValues = values[i];
        }
    }
    float difference = mMaxValues - mMinValues;
    mMinYAxis = mMinValues - 0.5f * difference;
    mMaxYAxis = mMaxValues + 0.05f * difference;
    setmXAxisColor("#efeff4");
    setmTextColor("#9a9a9a");
    setmLineStartColor("#FFCC02");
    setmLineEndColor("#FF4E00");
    setmPathStartColor("#11FFFFFF");
    setmPathEndColor("#aaFFdaca");
    postInvalidate();
}

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

public LineChartView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mDipPadding = getPixelForDip(DEFAULT_PADDING);

    TypedArray a = context.getTheme().obtainStyledAttributes(
            attrs, R.styleable.DIYForCharts, 0, 0);
    mFillColor = a.getColor(R.styleable.DIYForCharts_lineStrokeColor, Color.BLACK);
    mAxisColor = a.getColor(R.styleable.DIYForCharts_lineAxisColor, Color.LTGRAY);
    mBackgroundColor = a.getColor(R.styleable.DIYForCharts_lineBackground, Color.TRANSPARENT);
    mStrokeWidth = a.getDimension(R.styleable.DIYForCharts_lineStrokeWidth, 2);
    mStrokeSpacing = a.getDimensionPixelSize(R.styleable.DIYForCharts_lineStrokeSpacing, 10);
    mUseDips = a.getBoolean(R.styleable.DIYForCharts_lineUseDip, false);
    a.recycle();
}

public float[] getmYAxisPoints() {
    return mYAxisPoints;
}

public void setmXAxisContents(String[] mXAxisContents) {
    this.mXAxisContents = mXAxisContents;
}

public void setmYAxisPoints(float[] mYAxisPoints) {
    this.mYAxisPoints = mYAxisPoints;
}

public void setmXAxisColor(String mXAxisColor) {
    this.mXAxisColor = mXAxisColor;
}

public void setmTextColor(String mTextColor) {
    this.mTextColor = mTextColor;
}

public void setmLineStartColor(String mLineStartColor) {
    this.mLineStartColor = mLineStartColor;
}

public void setmLineEndColor(String mLineEndColor) {
    this.mLineEndColor = mLineEndColor;
}

public void setmPathStartColor(String mPathStartColor) {
    this.mPathStartColor = mPathStartColor;
}

public void setmPathEndColor(String mPathEndColor) {
    this.mPathEndColor = mPathEndColor;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = measureWidth(widthMeasureSpec);
    int height = measureHeight(heightMeasureSpec);
    //设置宽高
    setMeasuredDimension(width, height);
}

//根据xml的设定获取宽度
private int measureWidth(int measureSpec) {
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    //wrap_content
    if (specMode == MeasureSpec.AT_MOST) {
        specSize = Utils.px2dip(getContext(), 280);
    }
    //fill_parent或者精确值
    else if (specMode == MeasureSpec.EXACTLY) {
        if (specSize < 280) {
            specSize = Utils.px2dip(getContext(), 280);
        }
    }
    this.mXAxisUsedWidth = specSize * 9 / 10;
    return specSize;
}

//根据xml的设定获取高度
private int measureHeight(int measureSpec) {
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    //wrap_content
    if (specMode == MeasureSpec.AT_MOST) {
        specSize = Utils.px2dip(getContext(), 150);
    }
    //fill_parent或者精确值
    else if (specMode == MeasureSpec.EXACTLY) {
        if (specSize < 150) {
            specSize = Utils.px2dip(getContext(), 150);
        }
    }
    this.mTextSizeOfAxis = Utils.px2dip(getContext(), specSize / 60);
    return specSize;
}

//重置画笔和抗锯齿
private void resetPaintWithAntiAlias(Paint paint, boolean antiAlias) {
    paint.reset();
    paint.setAntiAlias(antiAlias);
}

public void onDraw(Canvas canvas) {
    if (null == mFullImage) {
        mFullImage = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mFullImage);
    }

    if (bNeedClean) {
        mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        bNeedClean = false;
    }

    mCanvas.drawColor(mBackgroundColor);

    //没有数据时
    if (mYAxisPoints == null || mYAxisPoints.length == 0) {
        resetPaintWithAntiAlias(mPaint, true);
        mPaint.setColor(Color.parseColor("#000000"));
        mPaint.setStrokeWidth(DP);
        mPaint.setTextAlign(Paint.Align.LEFT);

// mCanvas.drawText(“暂无数据,请稍后重试”, getWidth() / 3, getHeight() / 8, mPaint);
return;
}

// mCanvas.drawText(“”, getWidth() / 3, getHeight() / 8, mPaint);

    //再画5条一样的线
    resetPaintWithAntiAlias(mPaint, true);
    mPaint.setColor(Color.parseColor(mXAxisColor));
    mPaint.setStrokeWidth(DP);


    for (int i = 1; i <= 5; i++) {
        mCanvas.drawLine(getWidth() - mXAxisUsedWidth, getHeight() / 8 + getHeight() * 3 / 16 * (i - 1), getWidth(), getHeight() / 8 + getHeight() * 3 / 16 * (i - 1), mPaint);
    }


    //写y轴的5个数字
    resetPaintWithAntiAlias(mPaint, true);
    mPaint.setColor(Color.parseColor(mTextColor));
    mPaint.setTextSize(mTextSizeOfAxis);
    mPaint.setStrokeWidth(DP);
    mPaint.setTextAlign(Paint.Align.RIGHT);
    float f = mMaxYAxis - mMinYAxis;

    mCanvas.drawText(Utils.omit4Float(mMaxYAxis) + " ", getWidth() - mXAxisUsedWidth, getHeight() / 8 + mTextSizeOfAxis / 3, mPaint);
    mCanvas.drawText(Utils.omit4Float(mMaxYAxis - f / 4f) + " ", getWidth() - mXAxisUsedWidth, getHeight() / 8 + getHeight() * 3 / 16 + mTextSizeOfAxis / 3, mPaint);
    mCanvas.drawText(Utils.omit4Float(mMaxYAxis - f / 2f) + " ", getWidth() - mXAxisUsedWidth, getHeight() / 8 + getHeight() * 3 / 16 * 2 + mTextSizeOfAxis / 3, mPaint);
    mCanvas.drawText(Utils.omit4Float(mMaxYAxis - 3f * f / 4f) + " ", getWidth() - mXAxisUsedWidth, getHeight() / 8 + getHeight() * 3 / 16 * 3 + mTextSizeOfAxis / 3, mPaint);
    mCanvas.drawText(Utils.omit4Float(mMaxYAxis - f) + " ", getWidth() - mXAxisUsedWidth, getHeight() / 8 + getHeight() * 3 / 16 * 4 + mTextSizeOfAxis / 3, mPaint);


    //x轴数字
    resetPaintWithAntiAlias(mPaint, true);
    mPaint.setColor(Color.parseColor(mTextColor));
    mPaint.setTextSize(mTextSizeOfAxis);
    mPaint.setStrokeWidth(DP);
    mPaint.setTextAlign(Paint.Align.LEFT);
    mCanvas.drawText(mXAxisContents[0], getWidth() - mXAxisUsedWidth, getHeight() * 7 / 8 + mTextSizeOfAxis, mPaint);
    resetPaintWithAntiAlias(mPaint, true);
    mPaint.setColor(Color.parseColor(mTextColor));
    mPaint.setTextSize(mTextSizeOfAxis);
    mPaint.setStrokeWidth(DP);
    mPaint.setTextAlign(Paint.Align.RIGHT);
    mCanvas.drawText(mXAxisContents[1], getWidth(), getHeight() * 7 / 8 + mTextSizeOfAxis, mPaint);


    int colors[] = new int[]{Color.parseColor(mLineEndColor), Color.parseColor(mLineStartColor)};
    float positions[] = new float[]{0f, 0.5f};
    LinearGradient linearGradient = new LinearGradient(0, 0, 0, getHeight(), colors, positions, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient);
    mPaint.setStrokeWidth(2 * DP);
    Path path = new Path();
    float lastXPoint = 0;
    float lastYPoint = 0;
    float currentXPoint = 0;
    float currentYPoint = 0;
    path.moveTo(getWidth() - mXAxisUsedWidth, getHeight() * 7 / 8);
    for (int i = 0; i < mYAxisPoints.length; i++) {
        float xPercent = (float) i / (mYAxisPoints.length - 1);
        float yPercent = (mYAxisPoints[i] - mMinYAxis) / (mMaxYAxis - mMinYAxis);
        if (i == 0) {
            lastXPoint = xPercent * mXAxisUsedWidth + getWidth() - mXAxisUsedWidth;
            lastYPoint = getHeight() * 7 / 8 - yPercent * getHeight() * 3 / 4;
            path.lineTo(lastXPoint, lastYPoint);
        } else {
            currentXPoint = xPercent * mXAxisUsedWidth + getWidth() - mXAxisUsedWidth;
            currentYPoint = getHeight() * 7 / 8 - yPercent * getHeight() * 3 / 4;
            mCanvas.drawLine(lastXPoint, lastYPoint, currentXPoint, currentYPoint, mPaint);
            path.lineTo(currentXPoint, currentYPoint);
            lastXPoint = currentXPoint;
            lastYPoint = currentYPoint;
        }
        if (i == mYAxisPoints.length - 1) {

            path.lineTo(lastXPoint, getHeight() * 7 / 8);
            //Log.v("test", "close");
            path.close();
            resetPaintWithAntiAlias(mPaint, true);
            LinearGradient linearGradient1 = new LinearGradient(0, 0, 0, getHeight(), Color.parseColor(mPathEndColor), Color.parseColor(mPathStartColor), Shader.TileMode.CLAMP);
            mPaint.setShader(linearGradient1);
            mCanvas.drawPath(path, mPaint);
        }
    }

    canvas.drawBitmap(mFullImage, 0, 0, null);
}

private int getPixelForDip(int dipValue) {
    return (int) TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            dipValue,
            getResources().getDisplayMetrics());
}

}


代码解读

其实代码部分已经没什么好说的了,我的注释和命名基本上大家对自定义view有了解的话都能看出我这是很简单的一个自绘图形,自定义view牵扯到最大的两个方法就是onMeasure和onDraw,我在onMeasure的自我测量里把不合比例的宽高写死的原因是,我的字体大小是根据给定的高度来的,如果比例特别不协调会造成最后出来的图形特别难看,字也有可能显示不完全,这么写的另外一个原因就是为我同事使用控件服务的,因为是定制款的所以不需要考虑特别多的适配问题。

折线渐变的实现思路起始就是设置两个颜色,然后控制它们的相对位置,区域填色也类似,反正起点到终点是代表颜色渐变的方向,而不是颜色渐变的线。

Utils工具类里面只有两个方法,这里就不贴了,事实上我是一个安卓新人,所以没有太多markdown的使用经验,好像代码块显示也有一点问题,不过不影响观看就不管了。


写在后面

写这些在博客上,一是给自己提个醒,把一些工作中遇到的难题解决思路记录下来,二是看看自己有多勤奋可以保持更新,三是希望在成为大牛的路上开始奔跑,四是好多大神的博客都是互相转来转去,对解决问题帮助不大,就好像我一开始被一个大牛的博客推荐用HelloCharts这个框架,用过的人自然会知道有多坑,反正不改源码是不可能正常使用的。MPAndroidCharts没用过,不做评价。

源码下载地址:
https://github.com/CecilKwei/DIYForCharts.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值