自定义View-水波纹loading

利用Xfermode实现水波纹动画

文末有详细的代码+注释

该view用了两种方法来实现,一种是利用Xfermode来实现,还有一种利用canvas的clipPath()方法来实现,本次先用Xfermode来分析实现,:
先上效果图,gif图看到有锯齿,不知道是录屏的原因还是什么别的,在真机上是看不到锯齿的
loading动画效果图

首先是第一种利用Xfermode方法的实现:
主要步骤分为如下几步:

  1. 首先绘制圆形水波纹动画
  2. 将文字与圆形水波纹相交的白色区域绘制出来
  3. 将文字顶部不相交的地方绘制出来
  4. 动起来

第一步

先贴一张大家经常看到的图
PorterDuff
这里我们将会用到的模式是SrcIn
第一步实现后的效果图如下:
这里写图片描述
首先我们绘制水波纹path

//动画移动距离
private int mTranslateX;
//水波高度
private int mWaveHeight;
//view宽度
private int mWidth;
//view一半宽度
private int mHalfWidth;
//view四分之一宽度
private int mQuarterWidt
//view高度
private int mHeight;
//view一半高度
private int mHalfHeight;

mWavePath.reset();
mWavePath.moveTo(-mWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(-mHalfWidth - mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, -mHalfWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(-mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mTranslateX, mHalfHeight);
mWavePath.quadTo(mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, mHalfWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(mHalfWidth + mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mWidth + mTranslateX, mHalfHeight);
mWavePath.lineTo(mWidth + mTranslateX, mHeight);
mWavePath.lineTo(-mWidth + mTranslateX, mHeight);
mWavePath.close();

水波纹的绘制原理就是在控件外侧再绘制一上一下两个波纹,然后通过改变mTranslateX来实现移动
此时再绘制一个圆:

private Bitmap circleBitmap(int width,int height){
    Paint circlePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
    circlePaint.setColor(mWaveColor);
    Bitmap bitmap=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
    Canvas canvas=new Canvas(bitmap);
    canvas.drawCircle(width/2,height/2,width/2,circlePaint);
    return bitmap;
}

通过Xfemode的方式将圆与水波纹组合在一起,就可以实现圆形水波纹,整个代码如下:

int layer=canvas.saveLayerAlpha(mRectF,255,Canvas.ALL_SAVE_FLAG);
mWavePath.reset();
mWavePath.moveTo(-mWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(-mHalfWidth - mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, -mHalfWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(-mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mTranslateX, mHalfHeight);
mWavePath.quadTo(mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, mHalfWidth + mTranslateX, mHalfHeight);
mWavePath.quadTo(mHalfWidth + mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mWidth + mTranslateX, mHalfHeight);
mWavePath.lineTo(mWidth + mTranslateX, mHeight);
mWavePath.lineTo(-mWidth + mTranslateX, mHeight);
mWavePath.close();
canvas.drawPath(mWavePath, mCirclePaint);
mCirclePaint.setXfermode(mXfermode);
if (mCircleBitmap==null) {
    mCircleBitmap=circleBitmap(getWidth(),getHeight());
}
canvas.drawBitmap(mCircleBitmap,0,0,mCirclePaint);
mCirclePaint.setXfermode(null);
canvas.restoreToCount(layer);

第二步

将白色文字绘制上去
这里写图片描述
此时圆形水波纹已经实现,我们只需要通过Xfermode将文字绘制上去,并通过SRC_ATOP来只绘制与圆形水波纹相交的部分:

mTextPaint.setTextSize(mWidth/2/mContent.length());
int txtLayer=canvas.saveLayerAlpha(mRectF,255,Canvas.ALL_SAVE_FLAG);
..上述圆形水波纹代码..
mTextPaint.setXfermode(mTxtXfermode);
mTextPaint.setColor(Color.WHITE);
canvas.drawText(mContent, getWidth() / 2, Math.abs(mTextPaint.getFontMetrics().ascent +
        mTextPaint.getFontMetrics().descent) / 2 + getHeight() / 2, mTextPaint);
mTextPaint.setXfermode(null);
canvas.restoreToCount(txtLayer);

第三步

绘制顶部不相交的地方,只要在onDraw中首先绘制上方文字既可

mTextPaint.setColor(mTextColor);
canvas.drawText(mContent, getWidth() / 2, Math.abs(mTextPaint.getFontMetrics().ascent +
        mTextPaint.getFontMetrics().descent) / 2 + getHeight() / 2, mTextPaint);

此时效果基本已经实现了:
这里写图片描述

第四步

动画这边自然使用属性动画来实现:

private void initAnim(int width) {
    mAnimator = ValueAnimator.ofInt(0, width);
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mTranslateX = (int) animation.getAnimatedValue();
            invalidate();
        }
    });
    mAnimator.setDuration(2000);
    mAnimator.setRepeatCount(ValueAnimator.INFINITE);
    mAnimator.setRepeatMode(ValueAnimator.RESTART);
    mAnimator.setInterpolator(new LinearInterpolator());
}

此时效果基本已经实现。

详细注释+代码

public class BaiduLoadingView2 extends View {
    //绘制文字的画笔
    private Paint mTextPaint;
    //水波纹路径
    private Path mWavePath;
    //动画移动距离
    private int mTranslateX;
    //水波高度
    private int mWaveHeight;
    //view宽度
    private int mWidth;
    //view一半宽度
    private int mHalfWidth;
    //view四分之一宽度
    private int mQuarterWidth;
    //view高度
    private int mHeight;
    //view一半高度
    private int mHalfHeight;
    //动画
    private ValueAnimator mAnimator;
    //显示文字
    private String mContent = "穷";
    //水波颜色
    private int mWaveColor = Color.BLUE;
    //默认文字颜色
    private int mTextColor = Color.BLUE;
    //水波path和圆形的Xfermode
    private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    //圆形水波和文字的Xfermode
    private Xfermode mTxtXfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP);
    //圆画笔,最终决定水波颜色的画笔
    private Paint mCirclePaint;
    //圆
    private Bitmap mCircleBitmap;
    private RectF mRectF;


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

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

    public BaiduLoadingView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    //设置水波颜色
    public void setWaveColor(int waveColor) {
        mWaveColor = waveColor;
        mCircleBitmap=null;
        invalidate();
    }

    //设置顶部不相交文字的颜色
    public void setTextColor(int textColor) {
        mTextColor = textColor;
        mTextPaint.setColor(mTextColor);
        invalidate();
    }

    private void init() {
        mWavePath = new Path();
        mRectF=new RectF();

        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setStyle(Paint.Style.FILL);
        mCirclePaint.setColor(Color.RED);



        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(Color.BLACK);


        mCirclePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setColor(Color.RED);
        mCirclePaint.setStyle(Paint.Style.FILL);

        post(new Runnable() {
            @Override
            public void run() {
                startAnim();
            }
        });
    }


    @Override
    protected void onDraw(Canvas canvas) {
        //设置文字大小
        mTextPaint.setTextSize(mWidth/2/mContent.length());
        mTextPaint.setColor(mTextColor);
        canvas.drawText(mContent, getWidth() / 2, Math.abs(mTextPaint.getFontMetrics().ascent +
                mTextPaint.getFontMetrics().descent) / 2 + getHeight() / 2, mTextPaint);
        int txtLayer=canvas.saveLayerAlpha(mRectF,255,Canvas.ALL_SAVE_FLAG);


        /*
        start  水波绘制
         */
        int layer=canvas.saveLayerAlpha(mRectF,255,Canvas.ALL_SAVE_FLAG);
        mWavePath.reset();
        mWavePath.moveTo(-mWidth + mTranslateX, mHalfHeight);
        mWavePath.quadTo(-mHalfWidth - mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, -mHalfWidth + mTranslateX, mHalfHeight);
        mWavePath.quadTo(-mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mTranslateX, mHalfHeight);
        mWavePath.quadTo(mQuarterWidth + mTranslateX, mHalfHeight + mWaveHeight, mHalfWidth + mTranslateX, mHalfHeight);
        mWavePath.quadTo(mHalfWidth + mQuarterWidth + mTranslateX, mHalfHeight - mWaveHeight, mWidth + mTranslateX, mHalfHeight);
        mWavePath.lineTo(mWidth + mTranslateX, mHeight);
        mWavePath.lineTo(-mWidth + mTranslateX, mHeight);
        mWavePath.close();
        canvas.drawPath(mWavePath, mCirclePaint);
        mCirclePaint.setXfermode(mXfermode);
        if (mCircleBitmap==null) {
            mCircleBitmap=circleBitmap(getWidth(),getHeight());
        }
        canvas.drawBitmap(mCircleBitmap,0,0,mCirclePaint);
        mCirclePaint.setXfermode(null);
        canvas.restoreToCount(layer);
        /*
        end  水波绘制结束
         */



        mTextPaint.setXfermode(mTxtXfermode);
        mTextPaint.setColor(Color.WHITE);
        canvas.drawText(mContent, getWidth() / 2, Math.abs(mTextPaint.getFontMetrics().ascent +
                mTextPaint.getFontMetrics().descent) / 2 + getHeight() / 2, mTextPaint);
        mTextPaint.setXfermode(null);
        canvas.restoreToCount(txtLayer);
    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = 0, height = 0;
        switch (MeasureSpec.getMode(widthMeasureSpec)) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                width=height=getResources().getDisplayMetrics().widthPixels/5;
                break;
            case MeasureSpec.EXACTLY:
                width=height=Math.max(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
                break;
        }
        setMeasuredDimension(width, height);
        mRectF.set(0,0,width,height);
        mWidth = getMeasuredWidth();
        mHalfWidth = mWidth / 2;
        mQuarterWidth = mWidth / 4;
        mWaveHeight = mWidth / 6;

        mHeight = getMeasuredHeight();
        mHalfHeight = mHeight / 2;
        mHalfHeight = mHeight / 2;
        initAnim(mWidth);
    }

    private void initAnim(int width) {
        mAnimator = ValueAnimator.ofInt(0, width);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mTranslateX = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimator.setDuration(2000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.setRepeatMode(ValueAnimator.RESTART);
        mAnimator.setInterpolator(new LinearInterpolator());
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility== View.VISIBLE) {
            startAnim();
        }else {
            stopAnim();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    public void startAnim() {
        if (mAnimator != null && (!mAnimator.isStarted() && !mAnimator.isRunning())) {
            mAnimator.start();
        }
    }

    public void stopAnim() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
    }

    public void setContent(String content) {
        mContent = content;
        invalidate();
    }


    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (mCircleBitmap != null) {
            mCircleBitmap.recycle();
            mCircleBitmap=null;
        }
    }


    private Bitmap circleBitmap(int width,int height){
        Paint circlePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setColor(mWaveColor);
        Bitmap bitmap=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
        Canvas canvas=new Canvas(bitmap);
        canvas.drawCircle(width/2,height/2,width/2,circlePaint);
        return bitmap;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_34240569/article/details/81302117