效果图
按照往常惯例先看下效果图
设计原理
要实现图中的水波纹效果其实也很简单,首先想到的使用ValueAnimator动画来实现。计算最里面的水波纹和最外面的水波纹的距离,然后通过onAnimationUpdate回调获取当前的值画圆,这里我们需要用一个List来保存从动画开始到动画结束所以的值。用来在onDraw()来计算画多少个水波纹。注意这里还有颜色的透明度的变化还有每个水波纹之间的距离也是要考虑的。透明度可以通过距离差值动态的从FF~00设置,每个水波纹之间的距离这就要考虑用何种Interpolator还有就是onAnimationUpdate每回调几次才进行绘制一次来计算了。总体逻辑就是这样。接下来上源代码了
源码
java代码
public class RippleView extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
//水波纹开始的半径
private int mStartRadius;
//控件的半径
private int mRadius;
//水波纹颜色
private int mColor;
//线的宽度
private int mStrokeWidth;
//一次水波纹的动画时间
private int mDuration;
//画笔
private Paint mPaint;
private static final int DEFAULT_START_RADIUS = 30;
private static final int DEFAULT_COLOR = 0xFF5BE5E5;
private static final int DEFAULT_STROKE = 3;
private static final int DEFAULT_DURAION = 1000;//1秒
//没有透明度的颜色值
private int mNoAlphaColor;
//默认动画差值
private TimeInterpolator mInterpolator = new AccelerateInterpolator();
private ValueAnimator mValueAnimator;
private ArrayList<Integer> mRadiusList = new ArrayList<Integer>(50);
//统计动画回调的次数
private int mCount=0;
//取模次数才进行绘制
private int mMode;
private static final int DEFAULT_MODE=8;
public RippleView(Context context) {
super(context);
init(context, null);
}
public RippleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RippleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attr) {
TypedArray typedArray = context.obtainStyledAttributes(attr, R.styleable.RippleView, 0, 0);
mStartRadius = typedArray.getDimensionPixelSize(R.styleable.RippleView_ripple_start_radius, DEFAULT_START_RADIUS);
mColor = typedArray.getColor(R.styleable.RippleView_ripple_color, DEFAULT_COLOR);
mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.RippleView_ripple_stroke, DEFAULT_STROKE);
mDuration = typedArray.getInteger(R.styleable.RippleView_ripple_duration, DEFAULT_DURAION);
mMode=typedArray.getInteger(R.styleable.RippleView_ripple_mode,DEFAULT_MODE);
typedArray.recycle();
//如果最高位有值得话就说明设置了透明度
//无符号右移 高位补0
if ((mColor>>>24) > 0) {
mNoAlphaColor = mColor & 0x00FFFFFF;
} else {
mNoAlphaColor = mColor;
}
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureHanlder(widthMeasureSpec);
int height = measureHanlder(heightMeasureSpec);
mRadius = ((width < height) ? width : height) / 2;
setMeasuredDimension(width, height);
}
private int measureHanlder(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.max(result, specSize);
}
return result;
}
/**
* 开始动画
*/
public void startAnimation() {
if (mValueAnimator == null) {
mValueAnimator = new ValueAnimator();
mValueAnimator.setInterpolator(mInterpolator);
mValueAnimator.setIntValues(mStartRadius,
mRadius);
mValueAnimator.setDuration(mDuration);
mValueAnimator.setRepeatCount(-1);
mValueAnimator.addUpdateListener(this);
mValueAnimator.addListener(this);
mValueAnimator.start();
} else {
if (!mValueAnimator.isRunning()) {
mValueAnimator.start();
}
}
}
/**
* 获取透明度
*
* @param mChangeRadius
* @return
*/
private int getColor(int mChangeRadius) {
int currentRT = (mRadius - mChangeRadius);
int sumR = mRadius - mStartRadius;
int alpha = 255 * currentRT / sumR;
//把透明度左移24位移动到最高位再进行与运算就得到正在的值啦
return (alpha << 24) | mNoAlphaColor;
}
@Override
protected void onDetachedFromWindow() {
stopAnimation();
super.onDetachedFromWindow();
}
/**
* 结束动画
*/
public void stopAnimation() {
if (mValueAnimator != null && mValueAnimator.isRunning()) {
mValueAnimator.cancel();
}
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < mRadiusList.size(); i++) {
int changeRadius = mRadiusList.get(i);
mPaint.setColor(getColor(changeRadius));
canvas.drawCircle(mRadius, mRadius, changeRadius, mPaint);
}
}
@Override
public void onAnimationStart(Animator animation) {
mRadiusList.clear();
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
mCount=0;
mRadiusList.clear();
invalidate();
}
@Override
public void onAnimationRepeat(Animator animation) {
mCount=0;
mRadiusList.clear();
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCount++;
if(mCount%mMode==0){
int mRadius = (int) animation.getAnimatedValue();
Log.e("lx","now Radius=>"+mRadius+"startRadius=>"+mStartRadius+"mRadius=>"+this.mRadius);
mRadiusList.add(mRadius);
invalidate();
}
}
}
xml中的属性
<declare-styleable name="RippleView">
<!--水波纹从什么时候开始,开始的半径 -->
<attr name="ripple_start_radius" format="dimension|reference"/>
<!--水波纹颜色 -->
<attr name="ripple_color" format="color|reference"/>
<!--水波纹粗度 -->
<attr name="ripple_stroke" format="dimension|reference"/>
<!--动画的时间 建议1000 毫秒单位 -->
<attr name="ripple_duration" format="integer"/>
<!--在动画更新值方法中多少次才取一次数据刷新绘制 建议值8 -->
<attr name="ripple_mode" format="integer"/>
</declare-styleable>
总结
代码比较简单,就这样吧