波纹效果需要同时绘制多个同心圆,而且这些圆 不是页面内的元素,未触发之前不需要显示。如果用属性动画实现,至少需要在 xml 布局文件中添加多个 ImageView 画圆,效率低还不易复用。
WaveView分析
使用自定义View绘制动画的主要思路是这样的:
分解动画成帧,考虑如何在 onDraw 中绘制每一帧提取出绘制所需参数,分为随时间变化和不可变两种,不可变参数可以暴露去(setter方法/attribute设置),总结随时间变化的参数变化规律,实现时间轴按需求提供出播放,暂停,停止,重置等等方法
按照这个思路,一步步实现一下 WaveView吧。
第一步:
在onDraw中画圆需要用到 canvas.drawCircle 方法,四个参数:圆心x、y坐标,半径和Paint。WaveView 绘制的每一帧都是圆,区别是圆的半径,数量,透明度。还需要设置圆的最小半径和最大半径,以及扩散的时候两个圆的半径差。
每次绘制只需要把所有的圆画出来。
第二步:
扩散过程中随时间变化的参数只有半径和透明度。圆的数量,最小最大半径和半径差则是不可变参数。
第三步:
计算出圆心和中心图片的距离
以上是画波纹的全部思路,最后附上全部代码
public class HankView extends View {
/**
* 扩散圆圈颜色
*/
private int mColor = getResources().getColor(R.color.colorAccent);
/**
* 圆圈中心颜色
*/
private int mCoreColor = getResources().getColor(R.color.colorPrimary);
/**
* 圆圈中心图片
*/
private Bitmap mBitmap;
/**
* 中心圆半径
*/
private float mCoreRadius = 50;
/**
* 扩散圆宽度
*/
private int mDiffuseWidth = 3;
/**
* 最大宽度
*/
private Integer mMaxWidth = 255;
/**
* 是否正在扩散中
*/
private boolean mIsDiffuse = false;
// 透明度集合
private List<Integer> mAlphas = new ArrayList<>();
// 扩散圆半径集合
private List<Integer> mWidths = new ArrayList<>();
private Paint mPaint;
private OnClickListener mOnClickListener;
private String TAG = "DiffuseView";
private int mMarginBottom; // 麦克风图片距离底边距
public DiffuseView(Context context) {
this(context, null);
}
public DiffuseView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public DiffuseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DiffuseView);
mColor = a.getColor(R.styleable.DiffuseView_diffuse_color, mColor);
mCoreColor = a.getColor(R.styleable.DiffuseView_diffuse_coreColor, mCoreColor);
mCoreRadius = a.getFloat(R.styleable.DiffuseView_diffuse_coreRadius, mCoreRadius);
mDiffuseWidth = a.getInt(R.styleable.DiffuseView_diffuse_width, mDiffuseWidth);
mMaxWidth = a.getInt(R.styleable.DiffuseView_diffuse_maxWidth, mMaxWidth);
int imageId = a.getResourceId(R.styleable.DiffuseView_diffuse_coreImage, -1);
if (imageId != -1) {
mBitmap = BitmapFactory.decodeResource(getResources(), imageId);
}
mMarginBottom = a.getDimensionPixelOffset(R.styleable.DiffuseView_margin_bottom, 12);
a.recycle();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mAlphas.add(255);
mWidths.add(0);
}
/**
* 开始扩散
*/
public void start() {
mIsDiffuse = true;
invalidate();
}
/**
* 停止扩散
*/
public void stop() {
mIsDiffuse = false;
mWidths.clear();
mAlphas.clear();
mAlphas.add(255);
mWidths.add(0);
invalidate();
}
/**
* 是否扩散中
*/
public boolean isDiffuse() {
return mIsDiffuse;
}
/**
* 设置扩散圆颜色
*/
public void setColor(int colorId) {
mColor = colorId;
}
/**
* 设置中心圆颜色
*/
public void setCoreColor(int colorId) {
mCoreColor = colorId;
}
/**
* 设置中心圆图片
*/
public void setCoreImage(int imageId) {
mBitmap = BitmapFactory.decodeResource(getResources(), imageId);
}
/**
* 设置中心圆半径
*/
public void setCoreRadius(int radius) {
mCoreRadius = radius;
}
/**
* 设置扩散圆宽度(值越小宽度越大)
*/
public void setDiffuseWidth(int width) {
mDiffuseWidth = width;
}
/**
* 设置最大宽度
*/
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
@Override
public void setOnClickListener(@Nullable OnClickListener onClickListener) {
mOnClickListener = onClickListener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float downX = event.getX();
float downY = event.getY();
if (downX >= (getWidth() / 2 - mBitmap.getWidth() / 2) && // 左
downX <= (getWidth() / 2 + mBitmap.getWidth() / 2) && // 右
downY >= (getHeight() - mBitmap.getHeight() - mMarginBottom) && // 上
downY <= (getHeight() - mMarginBottom)) { // 下
// 只有在图片范围内才具备点击响应
mOnClickListener.onClick(this);
}
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public void invalidate() {
if (hasWindowFocus()) {
super.invalidate();
}
}
@Override
public void onDraw(Canvas canvas) {
// 绘制扩散圆
mPaint.setColor(mColor);
for (int i = 0; i < mAlphas.size(); i++) {
// 设置透明度
Integer alpha = mAlphas.get(i);
mPaint.setAlpha(alpha);
// 绘制扩散圆
Integer width = mWidths.get(i);
canvas.drawCircle(getWidth() / 2, getHeight() - mMarginBottom - mBitmap.getHeight() / 2, mCoreRadius + width, mPaint);
if (alpha > 0 && width < mMaxWidth) {
mAlphas.set(i, alpha - 1);
mWidths.set(i, width + 1);
}
}
// 判断当扩散圆扩散到指定宽度时添加新扩散圆
if (mWidths.get(mWidths.size() - 1) == mMaxWidth / mDiffuseWidth) {
mAlphas.add(255);
mWidths.add(0);
}
// 超过10个扩散圆,删除最外层
if (mWidths.size() >= 10) {
mWidths.remove(0);
mAlphas.remove(0);
}
// 绘制中心圆及图片
mPaint.setAlpha(255);
mPaint.setColor(mCoreColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mCoreRadius, mPaint);
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, getWidth() / 2 - mBitmap.getWidth() / 2
, getHeight() - mBitmap.getHeight() - mMarginBottom, mPaint);
}
if (mIsDiffuse) {
invalidate();
}
}
}