Android 充電氣泡動畫

1. 前言

最近,開發一個手機充電氣泡動畫,在這裏和同學們分享一下。

2. 正文

2.1 效果圖

如下(看手機底部的部分,不是水波紋。):

2.2 代碼部分

BubbleView.java

/**
 * 氣泡自定義控件
 * 思路:
 * 1,定義 Bubble 類;
 * 2,隨機生成 Bubble 對象,存放於 List 中;
 * 3,刷新 List 中的數據: 邊界控制;
 * 4,刷新 UI。
 *
 * @author wangzhichao
 * @date 2019/10/30
 */
public class BubbleView extends View {
    private static final String TAG = BubbleView.class.getSimpleName();
    private HandlerThread handlerThread;
    private WeakHandler weakHandler;
    private static final int MESSAGE_UPDATE = 1;
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    private int bubbleNumLimit;
    private float minBubbleSpeedX;
    private float maxBubbleSpeedX;
    private float minBubbleSpeedY;
    private float maxBubbleSpeedY;
    private float minBubbleRadius;
    private float maxBubbleRadius;
    private float bubbleCreateLatch;
    private int width;
    private int height;
    private int defaultWidth = (int) dp2px(10);
    private int defaultHeight = (int) dp2px(20);

    public BubbleView(Context context) {
        this(context, null);
    }

    public BubbleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BubbleView);
        minBubbleSpeedX = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMinBubbleSpeedX, (int) dp2px(1f));
        maxBubbleSpeedX = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMaxBubbleSpeedX, (int) dp2px(2));
        minBubbleSpeedY = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMinBubbleSpeedY, (int) dp2px(2));
        maxBubbleSpeedY = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMaxBubbleSpeedY, (int) dp2px(4));
        minBubbleRadius = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMinBubbleRadius, (int) dp2px(1));
        maxBubbleRadius = ta.getDimensionPixelSize(R.styleable.BubbleView_bvMaxBubbleRadius, (int) dp2px(4));
        bubbleNumLimit = ta.getInt(R.styleable.BubbleView_bvBubbleNumLimit, 10);
        bubbleCreateLatch = ta.getFloat(R.styleable.BubbleView_bvBubbleCreateLatch, 0.5f);
        int color = ta.getColor(R.styleable.BubbleView_bvBubbleColor, getResources().getColor(R.color.white));
        int alpha = ta.getInt(R.styleable.BubbleView_bvBubbleAlpha, 128);
        ta.recycle();

        handlerThread = new HandlerThread(TAG);

        paint.setColor(color);
        paint.setAlpha(alpha);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        handlerThread.start();
        Looper looper = handlerThread.getLooper();
        weakHandler = new WeakHandler(looper, msg -> {
            if (msg.what == MESSAGE_UPDATE) {
                tryCreateBubbles();
                updateBubbles();
                postInvalidate();
            }
            return true;
        });
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        weakHandler.removeCallbacksAndMessages(null);
        handlerThread.quit();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        width = getWidth() - getPaddingLeft() - getPaddingRight();
        height = getHeight() - getPaddingTop() - getPaddingBottom();
        drawBubbles(canvas);
        weakHandler.sendEmptyMessage(MESSAGE_UPDATE);
    }

    private void drawBubbles(Canvas canvas) {
        for (Bubble bubble : bubbles) {
            canvas.drawCircle(bubble.getCenterX(), bubble.getCenterY(), bubble.getRadius(), paint);
        }
    }

    private void updateBubbles() {
        Iterator<Bubble> iterator = bubbles.iterator();
        while (iterator.hasNext()) {
            Bubble bubble = iterator.next();
            bubble.setCenterX(bubble.getCenterX() + bubble.getSpeedX());
            bubble.setCenterY(bubble.getCenterY() - bubble.getSpeedY());
            if (isBubbleTouchTop(bubble)) {
                iterator.remove();
            } else if (isBubbleTouchLeft(bubble)) {
                bubble.setSpeedX(-bubble.getSpeedX());
                bubble.setCenterX(bubble.getRadius());
            } else if (isBubbleTouchRight(bubble)) {
                bubble.setSpeedX(-bubble.getSpeedX());
                bubble.setCenterX(width - bubble.getRadius());
            }
        }
    }

    private boolean isBubbleTouchLeft(Bubble bubble) {
        return bubble.getCenterX() - bubble.getRadius() <= 0;
    }

    private boolean isBubbleTouchRight(Bubble bubble) {
        return bubble.getCenterX() + bubble.getRadius() >= width;
    }

    private boolean isBubbleTouchTop(Bubble bubble) {
        return bubble.getCenterY() - bubble.getRadius() <= 0;
    }

    private List<Bubble> bubbles = new ArrayList<>();

    private void tryCreateBubbles() {
        if (bubbles.size() >= bubbleNumLimit) {
            return;
        }
        if (random.nextFloat() < bubbleCreateLatch) {
            return;
        }
        bubbles.add(createBubble());
    }

    private Bubble createBubble() {
        Bubble bubble = new Bubble();
        bubble.setRadius(getRandomRadius());
        bubble.setSpeedX(getRandomSpeedX());
        bubble.setSpeedY(getRandomSpeedY());
        bubble.setCenterX(width / 2f);
        bubble.setCenterY(height + bubble.getRadius());
        return bubble;
    }

    private Random random = new Random();

    private float getRandomSpeedX() {
        float value = minBubbleSpeedX + random.nextFloat() * (maxBubbleSpeedX - minBubbleSpeedX);
        if (random.nextBoolean()) {
            return value;
        } else {
            return -value;
        }
    }

    private float getRandomSpeedY() {
        return minBubbleSpeedY + random.nextFloat() * (maxBubbleSpeedY - minBubbleSpeedY);
    }

    private float getRandomRadius() {
        return minBubbleRadius + random.nextFloat() * (maxBubbleRadius - minBubbleRadius);
    }

    private int measureWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int result;
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = defaultWidth;
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int result;
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = defaultHeight;
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    /**
     * 氣泡
     */
    class Bubble {
        /**
         * 氣泡的半徑
         */
        private float radius;
        /**
         * 氣泡 x 向的移動速度
         */
        private float speedX;
        /**
         * 氣泡 y 向的移動速度
         */
        private float speedY;
        /**
         * 氣泡的中心 x 座標
         */
        private float centerX;
        /**
         * 氣泡的中心 y 座標
         */
        private float centerY;

        public float getRadius() {
            return radius;
        }

        public void setRadius(float radius) {
            this.radius = radius;
        }

        public float getSpeedX() {
            return speedX;
        }

        public void setSpeedX(float speedX) {
            this.speedX = speedX;
        }

        public float getSpeedY() {
            return speedY;
        }

        public void setSpeedY(float speedY) {
            this.speedY = speedY;
        }

        public float getCenterX() {
            return centerX;
        }

        public void setCenterX(float centerX) {
            this.centerX = centerX;
        }

        public float getCenterY() {
            return centerY;
        }

        public void setCenterY(float centerY) {
            this.centerY = centerY;
        }
    }

    private float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
    }
}

自定義屬性:

<declare-styleable name="BubbleView">
        <attr name="bvMinBubbleSpeedX" format="dimension" />
        <attr name="bvMaxBubbleSpeedX" format="dimension" />
        <attr name="bvMinBubbleSpeedY" format="dimension" />
        <attr name="bvMaxBubbleSpeedY" format="dimension" />
        <attr name="bvMinBubbleRadius" format="dimension" />
        <attr name="bvMaxBubbleRadius" format="dimension" />
        <attr name="bvBubbleNumLimit" format="integer" />
        <attr name="bvBubbleColor" format="color" />
        <attr name="bvBubbleAlpha" format="integer" />
        <attr name="bvBubbleCreateLatch" format="float" />
    </declare-styleable>

3. 最後

代碼還是有一些不足,大家多提寶貴意見。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章