自定义View-水波纹loading
程序员文章站
2022-05-30 19:56:23
...
利用Xfermode实现水波纹动画
文末有详细的代码+注释
该view用了两种方法来实现,一种是利用Xfermode来实现,还有一种利用canvas的clipPath()方法来实现,本次先用Xfermode来分析实现,:
先上效果图,gif图看到有锯齿,不知道是录屏的原因还是什么别的,在真机上是看不到锯齿的
首先是第一种利用Xfermode方法的实现:
主要步骤分为如下几步:
- 首先绘制圆形水波纹动画
- 将文字与圆形水波纹相交的白色区域绘制出来
- 将文字顶部不相交的地方绘制出来
- 动起来
第一步
先贴一张大家经常看到的图
这里我们将会用到的模式是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;
}
}