Android 圓形波浪控件

參考網絡上的代碼參考並加以修改,寫成了以下一個控件。

先上圖(視頻沒處理好,讓圓形少了個底...):

onDraw裏代碼量少,核心代碼就是使用正弦函數計算波浪的x、y座標。

我這裏波浪所處的高度是固定的,波浪的軸在原中間。這個修改也很容易,只要調整setPath方法的yTrans參數就可以。

分享出來以供大家參考。

public class WaveCircleView extends View {

    /**
     * 波浪畫筆
     */
    private Paint mWavePaint;

    /**
     * 波浪路徑
     */
    private Path mWavePath;

    /**
     * 圓環畫筆
     */
    private Paint mCirclePaint;

    /**
     * 圓環路徑
     */
    private Path mCirclePath;

    /**
     * 波的顏色
     */
    private int mWaveColor = Color.parseColor("#449FFF");

    private int mBackColor = Color.parseColor("#1285FF");

    private int mViewHeight;

    private int mViewWidth;

    private float mWaveLength;

    /**
     * 波浪x軸平移的距離
     */
    private float mWaveXtrans = 0;

    private float mSpeed;

    private MyHandler mHandler = new MyHandler(this);

    /**
     * 初始化波的移動
     */
    private void initWaveMove(){
        // 移動距離增加mSpeed;
        mWaveXtrans += mSpeed;
        if (mWaveXtrans >= mWaveLength){
            // 超過寬度時
            mWaveXtrans = 0;
        }
        invalidate();

        mHandler.removeCallbacksAndMessages(null);
        mHandler.sendEmptyMessageDelayed(0, 50);
    }

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

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

    public NetworkDetectCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();

        mWaveLength = mViewWidth / 2F;

        mSpeed = mViewWidth / 800F;

        int circleRadius = mViewHeight < mViewWidth ? mViewHeight / 2 : mViewWidth / 2;
        mCirclePath.addCircle(mViewWidth / 2, mViewHeight / 2, circleRadius, Path.Direction.CCW);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 繪製背後的圓
        canvas.drawPath(mCirclePath, mCirclePaint);

        // 在裁切的圓上畫波浪
        canvas.save();
        canvas.clipPath(mCirclePath);
        setPath(mWavePath, mViewWidth, mViewHeight, mViewHeight / 20F, mWaveLength,
                mWaveXtrans, mViewHeight / 2F);
        canvas.drawPath(mWavePath, mWavePaint);
        canvas.restore();
    }

    private void init() {
        // 波浪畫筆
        mWavePaint = new Paint();
        mWavePaint.setAntiAlias(true);
        mWavePaint.setColor(mWaveColor);
        mWavePaint.setStyle(Paint.Style.FILL_AND_STROKE);

        mWavePath = new Path();

        // 圓畫筆
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setColor(mBackColor);
        mCirclePaint.setStyle(Paint.Style.FILL);

        mCirclePath = new Path();

        mHandler.sendEmptyMessage(0);
    }

    /**
     * 設置波浪路徑
     * @param path 路徑
     * @param width 控件寬度
     * @param height 控件高度
     * @param waveHeight 波浪的高度
     * @param waveLength 波長
     * @param xTrans x軸平移長度
     * @param yTrans y軸平移長度
     */
    private void setPath(Path path, int width, int height, float waveHeight, float waveLength, float xTrans, float yTrans) {
        int x;
        int y;
        // 每次進來都把path重置一下
        path.reset();
        for (int i = 0; i < width; i++) {
            x = i;
            y = (int) (waveHeight * Math.sin((i / waveLength) * Math.PI * 2 + xTrans) + yTrans);
            if (i == 0) {
                // x=0的時候,即左上角的點,移動畫筆於此
                path.moveTo(x, y);
            }
            // 用每個x求得每個y,用quadTo方法連接成一條貝塞爾曲線
            path.quadTo(x, y, x + 1, y);
        }
        //最後連接到右下角底部
        path.lineTo(width, height);
        //連接到左下角底部
        path.lineTo(0, height);
        //起點在左上角,這個時候可以封閉路徑了,封閉。
        path.close();
    }

    private static class MyHandler extends Handler {

        WeakReference<WaveCircleView> mReference;

        MyHandler(WaveCircleView waveCircleView) {
            mReference = new WeakReference<>(waveCircleView);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mReference.get() != null) {
                mReference.get().initWaveMove();
            }
        }
    }
}

後續優化:

控件中的Handler主要作用是調用View的invalidate()方法。代碼中通過handler發送時間和增加speed橫向距離來控制速度。這樣來控制速度就需要調整兩個變量。通過speed變量來計算每次onDraw時應該移動的距離是不錯的方式,代碼如下:

if (mXMoveTimeLastTime > 0L) {
   float duration = System.currentTimeMillis() - mXMoveTimeLastTime;
   float dx = mSpeed * duration / 1000;
   // 移動距離增加dx
   mWaveXtrans += dx;
   if (mWaveXtrans >= mWaveLength) {
       // 超過寬度時
       mWaveXtrans = 0;
   }
}

mXMoveTimeLastTime = System.currentTimeMillis();
mMyHandler.sendEmptyMessageDelayed(0, 50);

 

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