欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义View-水波纹loading

程序员文章站 2022-05-30 19:56:23
...

利用Xfermode实现水波纹动画

文末有详细的代码+注释

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

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

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

第一步

先贴一张大家经常看到的图
自定义View-水波纹loading
这里我们将会用到的模式是SrcIn
第一步实现后的效果图如下:
自定义View-水波纹loading
首先我们绘制水波纹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);

第二步

将白色文字绘制上去
自定义View-水波纹loading
此时圆形水波纹已经实现,我们只需要通过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);

此时效果基本已经实现了:
自定义View-水波纹loading

第四步

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

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;
    }
}
相关标签: 自定义view