android自定义View——圆形波纹扫描效果

蓝牙项目,考虑到后面可能会用到这个扫描的效果,所以参照大神写好的控件,增加了自己需要使用的接口。也顺便巩固一下自定义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。。。欢迎交流~~~

你可能感兴趣的:(android自定义View——圆形波纹扫描效果)