蓝牙项目,考虑到后面可能会用到这个扫描的效果,所以参照大神写好的控件,增加了自己需要使用的接口。也顺便巩固一下自定义view中各种零碎的知识点。
先放一个效果图,点击中心图片开始动画,再次点击结束动画:
先来思路:
可以看到,这个动画是由圆和图片构成,中心图片画出来,然后根据中心图片的大小确定创建波纹时的半径,波纹的最大半径为当前view的宽高较小的。
动画部分,主要使用runable延时 + ValueAnimator 实现波纹效果; 然后整体旋转。
代码来说话。
- attrs中定义好需要在xml中设置的变量
- 部分成员变量注释,方便代码阅读
//波纹生成时的半径
private float mWaveRadiusMin;
//波纹消失前的半径
private float mWaveRadiusMax;
//每条波纹持续时间
private long mWaveDuration;
//波纹生成速度
private long mWaveCreatedSpeed;
private Paint mPaint;
//画笔是否为stroke模式(即线条)
private boolean stroke = false;
//中间图标画笔
private Paint mCenterBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//中间图标区域
private Rect mCenterBitmapArea = new Rect();
//波纹颜色
private int mWaveColor;
//波纹动画效果
private Interpolator mInterpolator = new AccelerateInterpolator();
//所有的水波纹
private List mAnimatorList = new ArrayList<>();
//是否开启水波纹
private boolean mIsRuning = false;
//是否点击了中间图标
private boolean mIsCenterClick = false;
//中间的图标
private Bitmap mCenterBitmap;
//中间的圆形图标
private Bitmap mCenterCircleBitmap;
//旋转动画
private Animation operatingAnim;
- 构造方法
public WaveCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.WaveCircleView, 0, defStyleAttr);
for (int i = 0; i < typedArray.length(); i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.WaveCircleView_color:
mWaveColor = typedArray.getColor(attr, Color.BLUE);
break;
case R.styleable.WaveCircleView_image:
mCenterBitmap = BitmapFactory.decodeResource(getResources(),
typedArray.getResourceId(attr, R.mipmap.translate));
break;
case R.styleable.WaveCircleView_duration:
mWaveDuration = typedArray.getInteger(attr, 3000);
break;
case R.styleable.WaveCircleView_waveCreateSpeed:
mWaveCreatedSpeed = typedArray.getInteger(attr, 1000);
break;
case R.styleable.WaveCircleView_stroke:
stroke = typedArray.getBoolean(attr, false);
break;
}
}
typedArray.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(3);
mPaint.setColor(mWaveColor);
mPaint.setDither(true);
if (stroke)//如果xml中设置为false,就把画笔属性设置为Stroke,最后的效果是线条
mPaint.setStyle(Paint.Style.STROKE);
else//填充效果
mPaint.setStyle(Paint.Style.FILL);
if (mCenterBitmap == null) {
mCenterBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.translate);
mWaveRadiusMin = Math.min(mCenterBitmap.getWidth(), mCenterBitmap.getHeight()) / 2;
}
mWaveRadiusMin = Math.min(mCenterBitmap.getWidth(), mCenterBitmap.getHeight()) / 2;
}
- onDraw()中 ,先把中心图片渲染到画布
if (mCenterCircleBitmap == null) {
mCenterCircleBitmap = createCircleImage(mCenterBitmap, mCenterBitmap.getWidth());
}
canvas.drawBitmap(mCenterCircleBitmap, null, mCenterBitmapArea, mCenterBitmapPaint);
private Bitmap createCircleImage(Bitmap source, int min) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap target = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888);
//产生一个同样大小的画布
Canvas canvas = new Canvas(target);
//首先绘制圆形
canvas.drawCircle(min / 2, min / 2, min / 2, paint);
//使用SRC_IN
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//绘制图片
canvas.drawBitmap(source, 0, 0, paint);
return target;
}
- 波纹效果
使用runable实现循环更新UI
private Runnable mWaveRunable = new Runnable() {
@Override
public void run() {
if (mIsRuning) {
createWaveAnimator();
invalidate();
//延时循环
postDelayed(mWaveRunable, mWaveCreatedSpeed);
}
}
};
private ValueAnimator createWaveAnimator() {
final ValueAnimator mWaveAnimator = new ValueAnimator();
mWaveAnimator.setFloatValues(mWaveRadiusMin, mWaveRadiusMax);
mWaveAnimator.setDuration(mWaveDuration);
mWaveAnimator.setRepeatCount(0);
mWaveAnimator.setInterpolator(mInterpolator);
mWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
//将生成的ValueAnimator存储在list里
mAnimatorList.add(mWaveAnimator);
//开始动画
mWaveAnimator.start();
return mWaveAnimator;
}
onDraw()中绘制波纹,根据动画集合中存储的AnimatedValue 改变画笔透明度 和 canvas 时圆的radius,完成扩散渐隐效果
Iterator iterator = mAnimatorList.iterator();
while (iterator.hasNext()) {
ValueAnimator valueAnimator = iterator.next();
// Log.e("AnimatedValue",(float)valueAnimator.getAnimatedValue() + "mWaveRadiusMax:" + mWaveRadiusMax);
if (!valueAnimator.getAnimatedValue().equals(mWaveRadiusMax)) {
//设置透明度
mPaint.setAlpha(getAlpha((Float) valueAnimator.getAnimatedValue()));
//画水波纹
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, (Float) valueAnimator.getAnimatedValue(), mPaint);
} else {
valueAnimator.cancel();
iterator.remove();
}
}
getAlpha()方法:
private int getAlpha(float mRadius) {
int alpha = 1;
if (mWaveRadiusMax > 0) {
alpha = (int) ((1 - (mRadius - mWaveRadiusMin) / (mWaveRadiusMax - mWaveRadiusMin)) * 255);
}
return alpha;
}
最后,onTouchEvent()实现点击中心控制动画开关
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//判断是否点击在中心图片范围内
mIsCenterClick = false;
//mCenterBitmapArea 方法在onSizeChanged()中初始化
if (mCenterBitmapArea.contains((int) event.getX(), (int) event.getY())) {
mIsCenterClick = true;
}
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (mIsCenterClick && !mIsRuning) {
//当点击了按钮,启动水波纹
start();
} else {
stop();
}
break;
}
return true;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWaveRadiusMax = Math.min(w, h) / 2;
//计算中间图标区域
mCenterBitmapArea.set((w - mCenterBitmap.getWidth()) / 2, (h - mCenterBitmap.getHeight()) / 2
, (w + mCenterBitmap.getWidth()) / 2, (h + mCenterBitmap.getHeight()) / 2);
}
public void start() {
if (!mIsRuning) {
mIsRuning = true;
mWaveRunable.run();
//旋转效果
operatingAnim = AnimationUtils.loadAnimation(getContext(), R.anim.roa);
LinearInterpolator lin = new LinearInterpolator();
operatingAnim.setInterpolator(lin);
operatingAnim.setDuration(mWaveDuration);
startAnimation(operatingAnim);
}
}
- anim中定义的旋转动画以及使用
最后 , xml中测试
由于没有写onMeasure(),所以wrap_content占据父控件的全部大小
效果如下:
当然,部分需要用到的属性,也通过getter 和setter暴露出去,方便java代码中灵活控制。
源码点击查看
。。。。以后应该能用上
代码中可能还有笔者未发现的bug和暂时不想解决的bug。。。欢迎交流~~~