Android自定义View——使用贝塞尔曲线实现流量进度条

今天就聊聊自定义View里面的知识点

第一次写带图片的博客,多少还是有点紧张,效果不好,请将就着看,前面的图是今天要写的控件的效果图,元素不多,分别是一个按钮和一个自定义的控件。

在此以前,我看过许多的书,比如《Android群英传》、《第一行代码》等,也看了很多大神的博客,但是即便是这样,当我看到这么多代码的时候,一直都没有真正的动手去敲过这些代码,以至于我总是觉得自定义View是一个多么高深莫测的技术,我们这些小白是难以触及的,但是当昨晚看了一篇鸡汤之后,觉得人还是要学会专注,要耐得住寂寞,要沉得住气。所以在未来的几天,我也会持续的更新自己的博客,希望能够得到大家的监督,也希望能够一起成长。

一般来说,不到迫不得已,还是不要去自定义自己的控件,毕竟现在Android API已经给我提供了功能这么强大的控件了,而且你也能够发现,就算是这么强大的谷歌,很多控件也还是有自己的bug,更何况我们自己写的控件,当然了,通过自定义控件,来加深我们对Android系统的控件的了解,这也是进阶的一个好方法。好了,废话我先说到这里,下面开始今天的主题。

贝塞尔曲线这个话题,很多大神的文章都有涉及,在郭神的微信公众号里面专门有一篇文章讲到了:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236243&idx=1&sn=00f003c809b1e3a3e5e7cb372d3ec970&scene=0#wechat_redirect ,所以我这里就不谈这个了。下面就开始进入编码阶段

新建一个Class文件,命名为MyLineView,让它继承View,实现它的三个构造函数,紧接着初始化一些画笔、Path,圆等数据。代码如下:
1.定义变量

    private Paint mPaint, mPaint2;
    private Path mPath = new Path();
    protected int mViewWidth, mViewHeight;
    protected int mWidth, mHeight;
    private float r, rArc, x;
    private float percent = 0.5f;
    private RectF rectF;
    private PointF mPointF = new PointF(0, 0);

在构造函数里面初始化数据

    public MyLineView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(100);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.CYAN);
        mPaint2.setStrokeWidth(8);
        mPaint2.setStyle(Paint.Style.FILL);

    }

准备工作已经做好了,紧接着我们复写View的一个onSizeChanged()方法,故名思议,就是当控件的大小发生改变的时候调用。我们在这里对变量进行赋值,代码如下:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        /**控件的高宽*/
        mViewWidth = w;
        mViewHeight = h;
        /**与屏幕左边的距离和距离右边的距离*/
        mWidth = mViewWidth - getPaddingLeft() - getPaddingRight();
        mHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
        /**定义半径*/
        r = Math.min(mWidth, mHeight) * 0.4f;
        rectF = new RectF(-r, -r, r, r);
    }

好了,这个时候的变量已经拥有值了,那可以开始写最重要的操作了,开始写onDraw();

    @Override
    protected void onDraw(Canvas canvas) {
        // super.onDraw(canvas);
        /**
         * 画曲线和外围的圆
         */
        canvas.translate(mViewWidth / 2, mViewHeight / 2);
        canvas.drawCircle(0, 0, r, mPaint);
        rArc = r * (1 - 2 * percent);
        double angle = Math.acos((double) rArc / r);
        x = r * (float) Math.sin(angle);
        mPath.addArc(rectF, 90 - (float) Math.toDegrees(angle),
                (float) Math.toDegrees(angle) * 2);
        mPath.moveTo(-x, rArc);
        mPath.rQuadTo(x / 2, -r / 8, x, 0);
        mPath.rQuadTo(x / 2, r / 8, x, 0);
        canvas.drawPath(mPath, mPaint2);
        mPath.rewind();
        /**
         * 画文字
         */
        NumberFormat numberFormat = NumberFormat.getPercentInstance();
        numberFormat.setMinimumFractionDigits(1);
        textCenter(new String[] { numberFormat.format(percent) }, mPaint,
                canvas, mPointF, Paint.Align.CENTER);
    }

文字的处理:

    /**
     * 多行文本居中、居右、居左
     * 
     * @param strings
     *            文本字符串列表
     * @param paint
     *            画笔
     * @param canvas
     *            画布
     * @param point
     *            点的坐标
     * @param align
     *            居中、居右、居左
     */
    protected void textCenter(String[] strings, Paint paint, Canvas canvas,
            PointF point, Paint.Align align) {
        paint.setTextAlign(align);
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;
        int length = strings.length;
        float total = (length - 1) * (-top + bottom)
                + (-fontMetrics.ascent + fontMetrics.descent);
        float offset = total / 2 - bottom;
        for (int i = 0; i < length; i++) {
            float yAxis = -(length - i - 1) * (-top + bottom) + offset;
            canvas.drawText(strings[i], point.x, point.y + yAxis, paint);
        }
    }

好了,截止目前已经把控件写好了,但是作为一个控件,数据不可能固定不变,所以向外暴露一个方法,用于改变进度的大小;

    public void setProgress(float percent) {
        if (percent != 0) {
            this.percent = percent;
        } else {
            this.percent = 0;
        }
        /**重绘*/
        invalidate();
    }

好了全部流程都在这里了,最后再贴上全部的代码;

package com.example.view.weight;

import java.text.NumberFormat;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

/**
 * 贝塞尔曲线
 * 
 * @author cheng
 * 
 */
public class MyLineView extends View {

    private Paint mPaint, mPaint2;
    private Path mPath = new Path();
    protected int mViewWidth, mViewHeight;
    protected int mWidth, mHeight;
    private float r, rArc, x;
    private float percent = 0.5f;
    private RectF rectF;
    private PointF mPointF = new PointF(0, 0);

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

    }

    public MyLineView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(100);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.CYAN);
        mPaint2.setStrokeWidth(8);
        mPaint2.setStyle(Paint.Style.FILL);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mViewWidth = w;
        mViewHeight = h;

        mWidth = mViewWidth - getPaddingLeft() - getPaddingRight();
        mHeight = mViewHeight - getPaddingTop() - getPaddingBottom();

        r = Math.min(mWidth, mHeight) * 0.4f;
        rectF = new RectF(-r, -r, r, r);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // super.onDraw(canvas);
        /**
         * 画曲线和外围的圆
         */
        canvas.translate(mViewWidth / 2, mViewHeight / 2);
        canvas.drawCircle(0, 0, r, mPaint);
        rArc = r * (1 - 2 * percent);
        double angle = Math.acos((double) rArc / r);
        x = r * (float) Math.sin(angle);
        mPath.addArc(rectF, 90 - (float) Math.toDegrees(angle),
                (float) Math.toDegrees(angle) * 2);
        mPath.moveTo(-x, rArc);
        mPath.rQuadTo(x / 2, -r / 8, x, 0);
        mPath.rQuadTo(x / 2, r / 8, x, 0);
        canvas.drawPath(mPath, mPaint2);
        mPath.rewind();
        /**
         * 画文字
         */
        NumberFormat numberFormat = NumberFormat.getPercentInstance();
        numberFormat.setMinimumFractionDigits(1);
        textCenter(new String[] { numberFormat.format(percent) }, mPaint,
                canvas, mPointF, Paint.Align.CENTER);
    }

    /**
     * 多行文本居中、居右、居左
     * 
     * @param strings
     *            文本字符串列表
     * @param paint
     *            画笔
     * @param canvas
     *            画布
     * @param point
     *            点的坐标
     * @param align
     *            居中、居右、居左
     */
    protected void textCenter(String[] strings, Paint paint, Canvas canvas,
            PointF point, Paint.Align align) {
        paint.setTextAlign(align);
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;
        int length = strings.length;
        float total = (length - 1) * (-top + bottom)
                + (-fontMetrics.ascent + fontMetrics.descent);
        float offset = total / 2 - bottom;
        for (int i = 0; i < length; i++) {
            float yAxis = -(length - i - 1) * (-top + bottom) + offset;
            canvas.drawText(strings[i], point.x, point.y + yAxis, paint);
        }
    }

    public void setProgress(float percent) {
        if (percent != 0) {
            this.percent = percent;
        } else {
            this.percent = 0;
        }
        invalidate();
    }
}

布局里面的代码:

    <com.example.view.weight.MyLineView
        android:id="@+id/view_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center" />

完成了,在最后感谢
https://github.com/Idtk/Blog/blob/master/Blog/6%E3%80%81Bezier.md

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值