效果圖
image
前言
最近一直在學習自定義控件,發現自己依然是那麼的渣渣。上面的UI效果來自
這個網站,果然,互動設計的效果與實際做出來的效果還是相差很大啊。
分析
整個效果實作起來還是十分的簡單,分析一下:
- 中間是一個圓用drawCircle()就可以畫出來。
- 圓中心是兩條線:drawLine()
- 後邊的背景是一個圓角矩形:drawRoundRect()
- 動畫效果使用ValueAnimator發散1--0之間的數值,不斷的更改長度和坐标即可完成。
- 不想吐槽那四個小正方形。
開始編碼
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);
效果就出來啦:
隻要将左邊的線的坐标更改為負數,右邊更改為正數,就變成了X了
canvas.drawLine(mForkLenght, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
canvas.drawLine(1mForkLenght, -mForkLenght, mForkLenght, mForkLenght, mPaint);
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);
效果出來了有沒有?
展開初始狀态
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);
}
效果如下:
酷炫菜單2
// canvas.rotate(360 * mPreValue, mWidth / 2,
這句注釋打開後就會有一個旋轉的效果,如下:
酷炫菜單3
最後
OK,這個效果基本上到這裡也就完成了,不要問我為什麼沒有畫矩形,我隻是想偷懶,最後會附上源碼的位址,自己研究去,也就是計算矩形的位置加上動畫而已。
自己造輪子,才是最快的進步方式。
源碼