分享一則最近流行的笑話:?
最新科學研究表明:寒冷可以使人保持年輕,樓下的王大爺表示雖然今年已經60多歲了,但是仍然冷的跟孫子一樣。
呃。好吧,這個冬天確實有點冷,在廣州活生生的把我這個原生北方人,凍成一條狗。(研究表明:寒冷可以讓人類基因突變。。。。)
好了不扯了。前些日子有朋友讓我寫博客來分析一下這個仿MIUI的時鐘,從中學到了一些炫酷效果的實現。?
[https://github.com/AvatarQing/MiClockView](https://github.com/AvatarQing/MiClockView)?感謝原作者的開源精神!
那么是啥3D效果呢,先來看看效果圖,額。。有好多個:?
?
這里寫圖片描述?

其實后兩個都是png來的。。
轉載請注明出處:[http://blog.csdn.net/wingichoy/article/details/50590051](http://blog.csdn.net/wingichoy/article/details/50590051)
那么請問,看到圖形的變換,你想到了什么??
沒錯!就是Matrix。?
關于Matrix你可以到愛哥的博客了解到及其詳細的講解(謝謝愛哥!)。
下面我們就來研究一下如何用矩陣,實現這個3d的效果。
首先新建自定義view類。
~~~
public class TDView extends View {
private Paint mPaint;
private int mCenterX;
private int mCenterY;
public TDView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
}
}
~~~
然后在圓心畫一個圓出來
~~~
@Override
protected void onDraw(Canvas canvas) {
mCenterX = getWidth() / 2;
mCenterY = getHeight() / 2;
canvas.drawCircle(mCenterX,mCenterY,100,mPaint);
}
```
現在是這樣的:

我們知道,處理一個圖片的時候(切錯)可以使用矩陣來處理,同時處理X,Y的話可以使用Camera類,camera可以生成一個指定效果的矩陣。直接來看用法:
private Camera mCamera;
private Matrix mMatrix;
mMatrix = new Matrix();
mCamera = new Camera();
在onDraw里 把camera給旋轉一下,并把生成的矩陣給一個矩陣。再把矩陣應用到canvas,看一下效果。
~~~
~~~
mMatrix.reset();
mCamera.save();
mCamera.rotateX(10);
mCamera.rotateY(20);
mCamera.getMatrix(mMatrix);
mCamera.restore();
//將矩陣作用于整個canvas
canvas.concat(mMatrix);
```
~~~
這里寫圖片描述?
?
呃。。。確實是變形了。。但是好像不是我們想要的結果??
這是因為,矩陣的變換坐標總從左上角(0,0)開始。所以我們要把變換的坐標改為中心點,方法如下:
~~~
mMatrix.reset();
mCamera.save();
mCamera.rotateX(10);
mCamera.rotateY(20);
mCamera.getMatrix(mMatrix);
mCamera.restore();
//改變矩陣作用點
mMatrix.preTranslate(-mCenterX, -mCenterY);
mMatrix.postTranslate(mCenterX, mCenterY);
canvas.concat(mMatrix);
```
此時的效果看起來像是向左傾斜了:
這里寫圖片描述

接下來讓他跟隨手指移動,重寫onTouchEvent:
~~~
@Override?
public boolean onTouchEvent(MotionEvent event) {?
float x = event.getX();?
float y = event.getY();
~~~
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_MOVE: {
//這里將camera旋轉的變量賦值
mCanvasRotateY = y;
mCanvasRotateX = x;
invalidate();
return true;
}
case MotionEvent.ACTION_UP: {
//這里將camera旋轉的變量賦值
mCanvasRotateY = 0;
mCanvasRotateX = 0;
invalidate();
return true;
}
}
return super.onTouchEvent(event);
}
```
~~~
哈哈。。看看是什么效果:?
這里寫圖片描述?

什么鬼,怎么跟轉硬幣一樣。 因為旋轉的X,Y給的太大了唄。所以要約束一下。
定義一個旋轉最大值
~~~
private float mCanvasMaxRotateDegree = 20;
~~~
再用percent思想(前面博客有提到),來處理手指觸摸點和這個度數變化的關系:
~~~
private void rotateCanvasWhenMove(float x, float y) {
float dx = x - mCenterX;
float dy = y - mCenterY;
float percentX = dx / mCenterX;
float percentY = dy /mCenterY;
if (percentX > 1f) {
percentX = 1f;
} else if (percentX < -1f) {
percentX = -1f;
}
if (percentY > 1f) {
percentY = 1f;
} else if (percentY < -1f) {
percentY = -1f;
}
mCanvasRotateY = mCanvasMaxRotateDegree * percentX;
mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY);
}
```
最后將TouchEvent里面的ACTION_MOVE 調用此函數即可。
此時,完整的代碼如下:
~~~
~~~
private int mCenterX;
private int mCenterY;
private float mCanvasRotateX = 0;
private float mCanvasRotateY = 0;
private float mCanvasMaxRotateDegree = 20;
private Matrix mMatrix = new Matrix();
private Camera mCamera = new Camera();
private Paint mPaint;
public TDView(Context context) {
super(context);
}
public TDView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mCanvasMaxRotateDegree = 20;
}
@Override
protected void onDraw(Canvas canvas) {
mCenterX = getWidth() / 2;
mCenterY = getHeight() / 2;
rotateCanvas(canvas);
canvas.drawCircle(mCenterX, mCenterY, 100, mPaint);
}
private void rotateCanvas(Canvas canvas) {
mMatrix.reset();
mCamera.save();
mCamera.rotateX(mCanvasRotateX);
mCamera.rotateY(mCanvasRotateY);
mCamera.getMatrix(mMatrix);
mCamera.restore();
mMatrix.preTranslate(-mCenterX, -mCenterY);
mMatrix.postTranslate(mCenterX, mCenterY);
canvas.concat(mMatrix);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
rotateCanvasWhenMove(x, y);
return true;
}
case MotionEvent.ACTION_MOVE: {
rotateCanvasWhenMove(x, y);
invalidate();
return true;
}
case MotionEvent.ACTION_UP: {
mCanvasRotateY = 0;
mCanvasRotateX = 0;
invalidate();
return true;
}
}
return super.onTouchEvent(event);
}
private void rotateCanvasWhenMove(float x, float y) {
float dx = x - mCenterX;
float dy = y - mCenterY;
float percentX = dx / mCenterX;
float percentY = dy /mCenterY;
if (percentX > 1f) {
percentX = 1f;
} else if (percentX < -1f) {
percentX = -1f;
}
if (percentY > 1f) {
percentY = 1f;
} else if (percentY < -1f) {
percentY = -1f;
}
mCanvasRotateY = mCanvasMaxRotateDegree * percentX;
mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY);
}
~~~
}
簡簡單單100行代碼,實現了3D效果的view:

接下來做什么呢?
當然是把這個效果加到我們的自定義view里面!
比如,把我的PanelView加上這個效果:
這里寫圖片描述

嘩!瞬間高大上!
那么,你要不要跟我趁熱來一發自定義view?
說搞就搞!
在原有類上進行修改
給一個好看的底色,畫一條線
~~~
mBgColor = Color.parseColor("#227BAE");
canvas.drawLine(mCenterX,100,mCenterX,130,mPaint);
```
~~~
這里寫圖片描述?
?
嗯。不錯 有條線了。微調下間距,旋轉畫布,畫出整個圓形來:
~~~
//保存坐標系
canvas.save();
for (int i = 0; i < 120; i++) {
canvas.rotate(3,mCenterX,mCenterY);
canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);
}
//恢復坐標系
canvas.restore();
~~~
?
嗯。。看起來想點樣子了。 接下來調整透明度。
~~~
canvas.save();
for (int i = 0; i < 120; i++) {
//根據i調整透明度alpha
mPaint.setAlpha(255-(mAlpha * i/120));
canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);
canvas.rotate(3,mCenterX,mCenterY);
}
canvas.restore();
~~~
這里寫圖片描述?
哈哈。。有沒有點意思呢。。?
?
我們畫個圓球上去!畫之前先ctrl + alt + m 把之前畫弧的方法提出來。?
畫一個緊挨著的圓
~~~
private void drawCircle(Canvas canvas) {?
mPaint.setAlpha(255);?
canvas.drawCircle(mCenterX,213,10,mPaint);?
}?
~~~
這里寫圖片描述?
?
不錯不錯,給點動態效果吧,讓圓點跟著我們觸摸的地方走。怎么做呢。。 當然還是旋轉畫布了!?
這里需要注意的是觸摸點與12點鐘方向形成的夾角計算。畫圖分析一下?
?
可以看到 我們只需要調用Math.atan方法即可算出a的弧度,再將其轉換為角度即可,在進行3D旋轉之前,旋轉畫布:
~~~
protected void onDraw(Canvas canvas) {
canvas.drawColor(mBgColor);
mCenterX = getWidth() / 2;
mCenterY = getHeight() / 2;
Log.e("wing",alpha+"");
canvas.rotate((float) alpha,mCenterX,mCenterY);
alpha = Math.atan((mTouchX-mCenterX)/(mCenterY-mTouchY));
alpha = Math.toDegrees(alpha);
if(mTouchY>mCenterY){
alpha = alpha+180;
~~~
現在看一下效果:?
?
這里寫圖片描述
效果出來了,但是還美中不足呀。 因為中間太空了,所以這個3D效果看起來有點奇怪。那就給他中間加點東西吧! 比如一個指針。
~~~
private void drawPath(Canvas canvas) {
mPath.moveTo(mCenterX,223);
mPath.lineTo(mCenterX-30,mCenterY);
mPath.lineTo(mCenterX,2*mCenterY-223);
mPath.lineTo(mCenterX+30,mCenterY);
mPath.lineTo(mCenterX,233);
mPath.close();
canvas.drawPath(mPath,mPaint);
mPaint.setColor(Color.parseColor("#55227BAE"));
canvas.drawCircle(mCenterX,mCenterY,20,mPaint);
}
~~~
最后大功告成!!! 看效果!?
這里寫圖片描述?

如果你喜歡我的博客,請點擊關注。歡迎評論~~
下一篇! 手把手教你做一個qq下拉搶紅包:打開鏈接
本項目地址:[https://github.com/githubwing/compassView](https://github.com/githubwing/compassView)
- 前言
- android自定義viewgroup初步之一----抽屜菜單
- Android 自定義view --圓形百分比(進度條)
- Android 自定義View -- 簡約的折線圖
- 新手自定義view練習實例之(一) 泡泡彈窗
- 新手自定義view練習實例之(二) 波浪view
- 手把手帶你畫一個 時尚儀表盤 Android 自定義View
- 手把手帶你畫一個動態錯誤提示 Android自定義view
- 手把手帶你做一個超炫酷loading成功動畫view Android自定義view
- 關于Android自定義view 你所需要知道的基本函數
- Android自定義view進階-- 神奇的貝塞爾曲線
- wing帶你玩轉自定義view系列(1) 仿360內存清理效果
- wing帶你玩轉自定義view系列(2) 簡單模仿qq未讀消息去除效果
- wing帶你玩轉自定義view系列(3)模仿微信下拉眼睛
- 手把手教你畫一個 逼格滿滿圓形水波紋loadingview Android
- 有坑?? 為何wing墜入PorterDuffXferMode的萬丈深淵(PorterDuffXferMode深入試驗)
- 手把手帶你畫一個漂亮蜂窩view Android自定義view
- 一個炫字都不夠??!!!手把手帶你打造3D自定義view
- 恭喜發財! -- 手把手教你仿造一個qq下拉搶紅包 Android自定義view