天天看點

Android初級進階之自定義酷炫菜單

效果圖

Android初級進階之自定義酷炫菜單

image

前言

最近一直在學習自定義控件,發現自己依然是那麼的渣渣。上面的UI效果來自

這個網站

,果然,互動設計的效果與實際做出來的效果還是相差很大啊。

分析

整個效果實作起來還是十分的簡單,分析一下:

  1. 中間是一個圓用drawCircle()就可以畫出來。
  2. 圓中心是兩條線:drawLine()
  3. 後邊的背景是一個圓角矩形:drawRoundRect()
  4. 動畫效果使用ValueAnimator發散1--0之間的數值,不斷的更改長度和坐标即可完成。
  5. 不想吐槽那四個小正方形。

開始編碼

Step 1

繼承自原生的View,重寫構造方法,在構造方法中初始化畫筆等操作

/**
 * 初始化畫筆等
 */
private void initSomeThing() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(1);
}
           

Step 2 Measure(測量)

測量View的寬高,支援Padding,wrap_content等

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int measureWidth = measureSize(widthMeasureSpec, getWindowMsg(getContext())[0] - getPaddingLeft() - getPaddingRight() - mCenterMenuOffset);
    int measureHeight = measureSize(heightMeasureSpec, dp2px(getContext(), 90)) + getPaddingBottom() + getPaddingTop();
    setMeasuredDimension(measureWidth, measureHeight);
}
           

測量封裝方法

/**
 * 測量寬高 模式
 *
 * @param measureSpec
 * @return
 */
private  int measureSize(int measureSpec,int defaultSize) {
    int result;
    int size = MeasureSpec.getSize(measureSpec);
    int mode = MeasureSpec.getMode(measureSpec);
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else {
        result = defaultSize;
        if (mode == MeasureSpec.AT_MOST) {
            result = Math.min(result, size);
        }
    }
    return result;
}
           

擷取螢幕寬高

/**
 * 擷取螢幕寬高
 *
 * @return wh[1] 螢幕高度
 */
private   int[] getWindowMsg(Context context) {
    int[] wh = new int[2];
    WindowManager wm = (WindowManager) context
            .getSystemService(Context.WINDOW_SERVICE);
    wh[0] = wm.getDefaultDisplay().getWidth();
    wh[1] = wm.getDefaultDisplay().getHeight();
    //Log.d("vivi", "螢幕寬 =" + wh[0]);
    //Log.d("vivi", "螢幕高 =" + wh[1]);
    return wh;
}
           

dp2px

/**
 * dp轉px
 *
 * @param context
 * @param dpVal
 * @return
 */
private  int dp2px(Context context, float dpVal) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
            dpVal, context.getResources().getDisplayMetrics());
}
           

Step 3 在onSizeChanged中初始化寬高的一些屬性

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;//寬度
    mHeight = h;//高度
    mCenterMenuOffset = (int) (mHeight * 0.3);//中間 圓形菜單的上下偏移量
    mMenuBackgroundOffset = (int) ((mHeight * 0.5) / 2); //紫色圓角矩形的上下偏移量
    mCenterMenuRadius = (mHeight - mCenterMenuOffset) / 2;//中間圓形的半徑
    mForkLenght = (int) (mCenterMenuRadius * 0.4);//中間兩個線的長度

    //得到中心菜單的矩陣 start
    int l = mWidth / 2 - mCenterMenuRadius;
    int t = mCenterMenuOffset / 2;
    int b = mHeight - mCenterMenuOffset / 2;
    int r = mWidth / 2 + mCenterMenuRadius;
    mCenterMenuRect = new Rect(l, t, r, b);//中心的圓的矩陣,用來響應點選事件的
    //得到中心菜單的矩陣 end
}
           

Step 4 畫中心的圓

畫個圓,沒什麼難度,偏移量,半徑都在onSizeChanged中初始化完成了。這裡要給圓加上陰影

//畫中心的圓 start
    mPaint.setShadowLayer(7, 4, 5, 0xff999999);//設定陰影圖層
    this.setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
    mPaint.setColor(mCenterMenuColor);
    canvas.drawCircle(mWidth / 2, mHeight / 2, mCenterMenuRadius, mPaint);
           

Step 5 圓中心的兩根線

仔細看效果圖,兩個線上邊的點是相同的,隻是在動畫的時候更改X軸的坐标就可以從 ^ 變成X了。首先要将畫布的坐标點移到中心,這樣更友善繪制這兩條線。

//圓形菜單中的 叉叉
    canvas.translate(mWidth / 2, mHeight / 2);//移動坐标點
    mPaint.setColor(Color.WHITE);
    mPaint.setStrokeWidth(10);
    canvas.drawLine(0, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
    canvas.drawLine(0, -mForkLenght, mForkLenght, mForkLenght, mPaint);
           

效果就出來啦:

Android初級進階之自定義酷炫菜單

隻要将左邊的線的坐标更改為負數,右邊更改為正數,就變成了X了

canvas.drawLine(mForkLenght, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
 canvas.drawLine(1mForkLenght, -mForkLenght, mForkLenght, mForkLenght, mPaint);
           
Android初級進階之自定義酷炫菜單

Step 6 繪制背景矩形

繪制靜态的矩形,也就是整個展開的效果

// 菜單背景 start
int left = mWidth / 2;//100
float nowWidth = (left - mMenuBackgroundOffset / 2);
RectF mMenuRectF = new RectF(left - nowWidth, mMenuBackgroundOffset, left + nowWidth, mHeight - mMenuBackgroundOffset);
mPaint.setColor(mMenuBackgroundColor);
canvas.drawRoundRect(mMenuRectF, mCenterMenuRadius, mCenterMenuRadius, mPaint);
           

效果出來了有沒有?

Android初級進階之自定義酷炫菜單

展開初始狀态

Step 7 為中心的按鈕響應點選事件

自定義原生View應該如何響應點選事件呢?其實非常的簡單,在onSizeChanged裡面計算出了中心圓的矩形區域mCenterMenuRect。而Rect提供了一個方法contains(x, y)來判斷xy坐标是不是在這個矩形當中,于是這就簡單了,隻需要重寫onTouch方法判斷xy坐标就好。

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();//獲得點選的坐标
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_UP:
            if (mCenterMenuRect.contains(x, y)) {//包含 打開菜單獲關閉
                openOrClose();
            }
            break;
    }
    return true;
}

/**
 * 打開或關閉
 */
public void openOrClose() {
    if (isOpen) {
        startAnimator(1, 0);
    } else {
        startAnimator(0, 1);
    }
    isOpen = !isOpen;
}
           

Step 8 增加動畫

使用ValueAnimator來發散1--0之間的數值,相當于百分比,然後不斷的postInvalidate達到效果

/**
 * 開啟啟動動畫
 *
 * @param from
 * @param end
 */
private void startAnimator(final int from, int end) {
    if (mAnimatorBackground != null && mAnimatorBackground.isRunning()) {
        mAnimatorBackground.cancel();
        mAnimatorBackground = null;
    }
    mAnimatorBackground = ValueAnimator.ofFloat(from, end).setDuration(500);
    mAnimatorBackground.setInterpolator(new OvershootInterpolator());
    mAnimatorBackground.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mPreValue = (float) animation.getAnimatedValue();
            postInvalidate();
        }
    });
    mAnimatorBackground.start();
}
           

onDraw方法代碼

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 菜單背景 start
    int left = mWidth / 2;//100
    float nowWidth = (left - mMenuBackgroundOffset / 2) * mPreValue;
    RectF mMenuRectF = new RectF(left - nowWidth, mMenuBackgroundOffset, left + nowWidth, mHeight - mMenuBackgroundOffset);
    mPaint.setColor(mMenuBackgroundColor);
    canvas.drawRoundRect(mMenuRectF, mCenterMenuRadius, mCenterMenuRadius, mPaint);
    // 菜單背景 end
    //畫中心的圓 start
    mPaint.setShadowLayer(7, 4, 5, 0xff999999);//設定陰影圖層
    this.setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
    mPaint.setColor(mCenterMenuColor);
    canvas.drawCircle(mWidth / 2, mHeight / 2, mCenterMenuRadius, mPaint);
    mPaint.setShadowLayer(0, 0, 0, Color.TRANSPARENT);
    // canvas.rotate(360 * mPreValue, mWidth / 2, mHeight / 2);

    //畫中心的圓 end
    //圓形菜單中的 叉叉
    canvas.translate(mWidth / 2, mHeight / 2);//移動坐标點
    mPaint.setColor(Color.WHITE);
    mPaint.setStrokeWidth(10);
    canvas.drawLine(mForkLenght * mPreValue, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
    canvas.drawLine(-mForkLenght * mPreValue, -mForkLenght, mForkLenght, mForkLenght, mPaint);
}
           

效果如下:

Android初級進階之自定義酷炫菜單

酷炫菜單2

// canvas.rotate(360 * mPreValue, mWidth / 2, 
           

這句注釋打開後就會有一個旋轉的效果,如下:

Android初級進階之自定義酷炫菜單

酷炫菜單3

最後

OK,這個效果基本上到這裡也就完成了,不要問我為什麼沒有畫矩形,我隻是想偷懶,最後會附上源碼的位址,自己研究去,也就是計算矩形的位置加上動畫而已。

自己造輪子,才是最快的進步方式。

源碼

繼續閱讀