自定义View之颜色渐变折线图

本文已发表在掘金,转载请注明出处。

首先看下要实现的效果图。
这里写图片描述
折线图的绘制主要有一下几个步骤。
一、定义LineChartView类并继承View。
二、添加自定义属性。以在value目录下创建attrs.xml文件,文件中我们可以定义一些用到的属性,比如折线颜色、字体大小等属性。文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="LineChartView">
    <attr name="axesColor" format="color"/> <!--坐标轴颜色-->
    <attr name="axesWidth" format="dimension"/><!--坐标轴宽度-->
    <attr name="textColor" format="color"/> <!--字体颜色-->
    <attr name="textSize" format="dimension"/> <!--字体大小-->
    <attr name="lineColor" format="color"/> <!--折线颜色-->
  </declare-styleable>
</resources>

接下来在LineChartView的构造方法中解析自定义属性的值并做相应的处理。在构造方法里还初始化了渐变颜色、折线数据的List集合以及初始化画笔等操作代码如下:

 public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
        mAxesColor = typedArray.getColor(R.styleable.LineChartView_axesColor, Color.parseColor("#CCCCCC"));
        mAxesWidth = typedArray.getDimension(R.styleable.LineChartView_axesWidth, 1);
        mTextColor = typedArray.getColor(R.styleable.LineChartView_textColor, Color.parseColor("#ABABAB"));
        mTextSize = typedArray.getDimension(R.styleable.LineChartView_textSize, 32);
        mLineColor = typedArray.getColor(R.styleable.LineChartView_lineColor, Color.RED);
        typedArray.recycle();

        //  初始化渐变色
        shadeColors = new int[]{
                Color.argb(100, 255, 86, 86), Color.argb(15, 255, 86, 86),
                Color.argb(0, 255, 86, 86)};
      //  初始化折线数据
        mValues = new ArrayList<>();
        mMargin10 = DensityUtils.dp2px(context, 10);
        init();
    }

三、初始化画笔和路径。代码如下:

private void init() {
        //  初始化坐标轴画笔
        mPaintAxes = new Paint();
        mPaintAxes.setColor(mAxesColor);
        mPaintAxes.setStrokeWidth(mAxesWidth);

        //  初始化文字画笔
        mPaintText = new Paint();
        mPaintText.setStyle(Paint.Style.FILL);
        mPaintText.setAntiAlias(true); //抗锯齿
        mPaintText.setTextSize(mTextSize);
        mPaintText.setColor(mTextColor);
        mPaintText.setTextAlign(Paint.Align.LEFT);

        //  初始化折线画笔
        mPaintLine = new Paint();
        mPaintLine.setStyle(Paint.Style.STROKE);
        mPaintLine.setAntiAlias(true);
        mPaintLine.setStrokeWidth(mAxesWidth / 2);
        mPaintLine.setColor(mLineColor);

        //  初始化折线路径
        mPath = new Path();
        mPathShader = new Path();

        //  阴影画笔
        mPaintShader = new Paint();
        mPaintShader.setAntiAlias(true);
        mPaintShader.setStrokeWidth(2f);
    }

四、重写onLayout方法。在onLayout方法中获取控件的宽高、初始化原点坐标以及设置控件的背景。代码如下:

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {

            mWidth = getWidth();
            mHeight = getHeight();
            timeWidth = (int) mPaintText.measureText(startTime);
            //  初始化原点坐标
            xOrigin = 0 + mMargin10;
            yOrigin = (mHeight - mTextSize - mMargin10); 
        }
    }

五、重写onDraw方法。在onDraw方法中完成折线图的绘制。代码如下:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //  Y轴坐标间距
        yInterval = (max - min) / (yOrigin - mMargin10);
		//  X轴坐标间距
        xInterval = (mWidth - xOrigin) / (mItems.size() - 1);
        //  画坐标轴
        drawAxes(canvas);
        //  画文字
        drawText(canvas);
        //  画折线
        drawLine(canvas);
		//  绘制路径
        drawPath(canvas);
    }

折线图的绘制可以分三部分:1.绘制坐标轴。2.绘制View上的文字。3.绘制折线。

1.坐标轴绘制的是第一象限,即左下角的点为原点。绘制坐标轴代码如下:

//  画坐标轴
    private void drawAxes(Canvas canvas) {
        //  绘制X轴
        canvas.drawLine(xOrigin, yOrigin, mWidth - mMargin10, yOrigin, mPaintAxes);
        //  绘制X中轴线
        canvas.drawLine(xOrigin, yOrigin / 2, mWidth - mMargin10, yOrigin / 2, mPaintAxes);
        //  绘制X上边线
        canvas.drawLine(xOrigin, mMargin10, mWidth - mMargin10, mMargin10, mPaintAxes);
        //  绘制画Y轴
        canvas.drawLine(xOrigin, yOrigin, xOrigin, mMargin10, mPaintAxes);
        //  绘制Y右边线
        canvas.drawLine(mWidth - mMargin10, mMargin10, mWidth - mMargin10, yOrigin, mPaintAxes);
    }

2.绘制文字,代码如下:

private void drawText(Canvas canvas) {
        //  绘制最大值
        canvas.drawText(String.format("%.2f", max * 100 / 100.0) + "%", xOrigin + 6, 2 * mMargin10, mPaintText);
        //  绘制最小值
        canvas.drawText(String.format("%.2f", min * 100 / 100.0) + "%", xOrigin + 6, yOrigin - 6, mPaintText);
        //  绘制中间值
        canvas.drawText((String.format("%.2f", (min + max) * 100 / 200.0) + "%"), xOrigin + 6, (yOrigin + mMargin10) / 2, mPaintText);

        //  绘制开始日期
        canvas.drawText(startTime, xOrigin, mHeight - mMargin10, mPaintText);
        //  绘制结束日期
        canvas.drawText(endTime, mWidth - timeWidth - mMargin10, mHeight - mMargin10, mPaintText);
    }

3.绘制折线及渐变填充

private void drawLine(Canvas canvas) {
        //  画坐标点
        for (int i = 0; i < mValues.size(); i++) {
            float x = i * xInterval + xOrigin + mAxesWidth;
            if (i == 0) {
                mPathShader.moveTo(x, yOrigin - (mValues.get(i) - min) / yInterval);
                mPath.moveTo(x, yOrigin - (mValues.get(i) - min) / yInterval);
            } else {
                mPath.lineTo(x - mMargin10 - mAxesWidth, yOrigin - (mValues.get(i) - min) / yInterval);
                mPathShader.lineTo(x - mMargin10 - mAxesWidth, yOrigin - (mValues.get(i) - min) / yInterval);
                if (i == mValues.size() - 1) {
                    mPathShader.lineTo(x - mMargin10 - mAxesWidth, yOrigin);
                    mPathShader.lineTo(xOrigin, yOrigin);
                    mPathShader.close();
                }
            }
        }
        if (null == mShadeColors) {
            mPaintShader.setColor(Color.argb(0, 0, 0, 0));
        } else {
            Shader mShader = new LinearGradient(0, 0, 0, getHeight(), mShadeColors, null, Shader.TileMode.CLAMP);
            mPaintShader.setShader(mShader);
        }
        canvas.drawPath(mPathShader, mPaintShader);
    }

六、折线图添加动画。

1.折线图的动画使用属性动画,首先需要计算动画的进度,因此需要先添加setPercentage方法,当动画开始时,我们可以在该方法中拿到percentage的值

    /**
     * Animate this property. It is the percentage of the path that is drawn.
     *
     * @param percentage float the percentage of the path.
     */
    public void setPercentage(float percentage) {
        if (percentage < 0.0f || percentage > 1.0f) {
            throw new IllegalArgumentException(
                    "setPercentage not between 0.0f and 1.0f");
        }
        mProgress = percentage;
        invalidate();
    }

2.通过Path来绘制折线路径,代码如下:

 private void drawPath(Canvas canvas) {

        PathMeasure measure = new PathMeasure(mPath, false);
        float pathLength = measure.getLength();
        PathEffect effect = new DashPathEffect(new float[]{pathLength,
                pathLength}, pathLength - pathLength * mProgress);
        mPaintLine.setPathEffect(effect);
        canvas.drawPath(mPath, mPaintLine);
    }

3.通过ObjectAnimator 开启动画,注意ObjectAnimator.ofFloat(lineChartView, “percentage”, 0.0f, 1.0f)中第二个参数必须为“percentage”,对应前那边的setPercentage方法,属性动画会根据“percentage”参数通过反射调用setPercentage:

  /**
     * @param lineChartView
     * @param duration      动画持续时间
     */
    public void startAnim(LineChartView lineChartView, long duration) {
        ObjectAnimator anim = ObjectAnimator.ofFloat(lineChartView, "percentage", 0.0f, 1.0f);
        anim.setDuration(duration);
        anim.setInterpolator(new LinearInterpolator());
        anim.start();
    }

至此,折线图的绘制已经全部完成。最后还可以添加get() set()方法,暴露出属性接口,以供外部调用。代码就不再贴出来了。

七、使用LineChartView
1.在布局文件中添加LineChartView,可设置折线颜色、字体颜色、等属性,如下:

<com.example.zhpan.linechartview.LineChartView
        android:id="@+id/lcv"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="20dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="15dp"
        app:lineColor="#FF0000"
        app:textColor="#ABABAB"
        app:textSize="12dp"/>

2.在Activity中为LineChartView设置数据,也可以通过代码为其设置属性。

private void initData() {
        //  初始化折线数据
        List<Float> listValues = new ArrayList<>();
        Random random = new Random();
        float startValue = random.nextFloat() * 10;
        listValues.add(startValue);
        for (int i = 0; i < 30; i++) {
            startValue += random.nextFloat() * 10 - 5;
            listValues.add(startValue);
        }
        List<Integer> listShadeColors = new ArrayList<>();
        listShadeColors.add(Color.argb(100, 255, 86, 86));
        listShadeColors.add(Color.argb(15, 255, 86, 86));
        listShadeColors.add(Color.argb(0, 255, 86, 86));
        //  设置折线数据
        mLineChartView.setValues(listValues);
        //  设置渐变颜色
        mLineChartView.setShadeColors(listShadeColors);
        //  设置动画插值器
        mLineChartView.setInterpolator(new DecelerateInterpolator());
        mLineChartView.setAxisMinValue(-30);
        mLineChartView.setAxisMaxValue(30);
        mLineChartView.setStartTime("2017-03-15");
        mLineChartView.setEndTime("2017-04-14");
        //  开启动画
        mLineChartView.startAnim(2500);
    }

源码下载

好库推荐

给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。通过BannerViewPager可以实现腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式。

欢迎大家到github关注BannerViewPager

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值