自定义波浪加载小球

来源:互联网 发布:c语言取反运算 编辑:程序博客网 时间:2024/05/16 06:58

功能来源于需求 ,给我一盒画笔,我将画出整个世界。

本篇记录一次自定义类似加速球的自定义实现:

  • 效果图
    这里写图片描述

  • 具体实现
    为了实现这样的一个效果,就必须自定义View,进行自定义布局初始 化以及定义变量等:

  private Paint mPaint; //基本画笔    private Paint textPaint; //文字画笔    private Path path; //路径    private int mWidth = DimentionUtils.px2Dp(getContext(), 50); //默认的view的宽度    private int mHeight = DimentionUtils.px2Dp(getContext(), 50);//默认view的高度    private int textSize = DimentionUtils.px2Sp(getContext(), 10);//默认文字的大小    private String content = "卢";//文字内容    private float curPercent; //波浪线水平移动的速率    private int color; //文字颜色(默认颜色为红色)    private float ratio = 0.5f;// 波浪的高度与view的比值,默认0.5    public WaveLoadCircle(Context context) {        this(context, null);    }    public WaveLoadCircle(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public WaveLoadCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(attrs);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int width = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        if (widthMode == MeasureSpec.EXACTLY) {// 确定值或者match_parent            mWidth = width;        }        if (heightMode == MeasureSpec.EXACTLY) {            mHeight = height;        }        setMeasuredDimension(mWidth, mHeight);        textSize = mWidth / 4;//文字大小为宽度的四分之一        textPaint.setTextSize(textSize);    }  /**     * 初始化画笔和路径     *     * @param attr     */    private void init(AttributeSet attr) {        TypedArray arr = getContext().obtainStyledAttributes(attr, R.styleable.WaveLoadCircle);        //自定义颜色和文字,默认蓝色        int c = arr.getColor(R.styleable.WaveLoadCircle_color, Color.BLUE);        String text = arr.getString(R.styleable.WaveLoadCircle_text);        if (c != 0) {            Log.i("tag", "init:color " + c);            color = c;        }        if (text != null) {            content = text;            Log.i("tag", "init:text " + text);        }        arr.recycle();//回收资源        //初始化画笔        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//去除锯齿        mPaint.setDither(true);        mPaint.setStyle(Paint.Style.FILL);//填充        mPaint.setColor(color);        //初始化文字画笔        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//反锯齿标志        textPaint.setColor(color);        textPaint.setStyle(Paint.Style.FILL);        textPaint.setDither(true);        //闭合的波浪路径        path = new Path();    }

自定义属性:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="WaveLoadCircle">        <attr name="color" format="color"/>        <attr name="text" format="string"/>    </declare-styleable></resources>

实现这样的View是通过Canvas、Paint、Path来画了四层效果图将其叠加在一起:

这里写图片描述

那么首先就是画最底下那个文字,这里有个问题就是如何将文字画到画布的中间呢,下面的这句代码其实只是对齐了x轴,也就是水平居中了,所以就是如何才能让其垂直居中呢,

Rect rect = new Rect(0, 0, mWidth, mHeight);paint.setTextAlign(Paint.Align.CENTER);

先来看一个文字绘制时如何的定位:

这里写图片描述

所以,文字的y轴居中坐标:centerY = (画布高度 - 字体.assent - 字体.descent)/2,这样画到画布的文字才会居中显示:

/**     * 为了将文字画在画布的中央,centerY = (画布高度 - 字体.assent - 字体.descent)/2     *     * @param canvas     * @param paint     */    private void drawCentertext(Canvas canvas, Paint paint) {        Rect rect = new Rect(0, 0, mWidth, mHeight);        paint.setTextAlign(Paint.Align.CENTER);        Paint.FontMetrics pf = paint.getFontMetrics();        int centerY = (int) ((mHeight - pf.ascent - pf.descent) / 2);        canvas.drawText(content, rect.centerX(), centerY, paint);    }

最底下的文字画好以后,就是第二层画波浪线,利用Path来完成这个使命,示意图如下:

这里写图片描述

图中的灰色就是我们的画布,为了能够实现波浪的波动,因为Path中没有画sin 或者 cos的曲线,所以就选择用贝塞尔曲线来画一段曲线。要想实现波浪的波动,图中左边的起点会不断向右滑动,所以绘制一个上边是图中所示的闭合矩形:

    /**     * 绘制一个上边是由4段二阶贝塞尔曲线的矩形,区间位置-mWidth ~ mWidth     *     * @param percent     * @return     */    public Path getWavePath(float percent) {        Path path = new Path();        float x = -mWidth * percent;        path.moveTo(x, mHeight * (1 - ratio));        //控制点的相对宽度        int qWidth = mWidth / 4;        //控制点的相对高度        int qHeight = qWidth / 2;        //第一个波浪        path.rQuadTo(qWidth, qHeight, qWidth * 2, 0);        path.rQuadTo(qWidth, -qHeight, qWidth * 2, 0);        //第二个波浪        path.rQuadTo(qWidth, qHeight, qWidth * 2, 0);        path.rQuadTo(qWidth, -qHeight, qWidth * 2, 0);        //右侧的直线        path.lineTo(x + mWidth * 2, mHeight);        path.lineTo(x, mHeight);        //自动闭合补出左边的直线        path.close();        return path;    }

接着裁剪该path的闭合路径:

// 获取path路径为一个上边为贝塞尔曲线的矩形        path = getWavePath(curPercent);        Log.i("tag", "onDraw:percent " + curPercent);        // 在画布上面裁剪该path        canvas.clipPath(path);

在裁剪后的画布上面画圆(画圆的画笔颜色是波浪的颜色):

  // 画圆        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);

最后一层就是再画一个文字和最底层文字一样,但是颜色为白色的文字:

 textPaint.setColor(Color.WHITE);        // 再画一个颜色与上面字体颜色不一样的字        drawCentertext(canvas, textPaint);

通过ValueAnimator来设置波浪的移动距离:

  // 波浪水平移动的速率        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);        anim.setDuration(1000);        anim.setRepeatCount(ValueAnimator.INFINITE); // 无限重复        anim.setRepeatMode(ValueAnimator.RESTART);//重头再来        anim.setInterpolator(new LinearInterpolator());        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                curPercent = animation.getAnimatedFraction();// 将0~1 的动画值不断得赋值当前的进度                Log.i("tag", "onAnimationUpdate: " + curPercent);                invalidate();            }        });        anim.start();

添加一个设置波浪高度的方法:

/**     * 设置波浪的高度与view高度的比值(0f ~ 1f)     *     * @param ratio     */    public void setWaveHeightRatio(float ratio) {        this.ratio = ratio;        invalidate();    }

好了,基本流程就是这样。github源码链接