分享一则最近流行的笑话:

    最新科学研究表明:寒冷可以使人保持年轻,楼下的王大爷表示虽然今年已经60多岁了,但是仍然冷的跟孙子一样。

    呃。好吧,这个冬天确实有点冷,在广州活生生的把我这个原生北方人,冻成一条狗。(研究表明:寒冷可以让人类基因突变。。。。)

    好了不扯了。前些日子有朋友让我写博客来分析一下这个仿MIUI的时钟,从中学到了一些炫酷效果的实现。

    https://github.com/AvatarQing/MiClockView 感谢原作者的开源精神!

    那么是啥3D效果呢,先来看看效果图,额。。有好多个:

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图1

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图2

    其实后两个都是png来的。。

    转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50590051

    那么请问,看到图形的变换,你想到了什么?

    没错!就是Matrix。

    关于Matrix你可以到爱哥的博客了解到及其详细的讲解(谢谢爱哥!)。

    下面我们就来研究一下如何用矩阵,实现这个3d的效果。

    首先新建自定义view类。

    1. public class TDView extends View {
    2. private Paint mPaint;
    3. private int mCenterX;
    4. private int mCenterY;
    5. public TDView(Context context, AttributeSet attrs) {
    6. super(context, attrs);
    7. mPaint = new Paint();
    8. }
    9. }

    然后在圆心画一个圆出来

    1. @Override
    2. protected void onDraw(Canvas canvas) {
    3. mCenterX = getWidth() / 2;
    4. mCenterY = getHeight() / 2;
    5. canvas.drawCircle(mCenterX,mCenterY,100,mPaint);
    6. }
    7. ```
    8. 现在是这样的:
    9. ![这里写图片描述](http://img.blog.csdn.net/20160126235856787)
    10. 我们知道,处理一个图片的时候(切错)可以使用矩阵来处理,同时处理X,Y的话可以使用Camera类,camera可以生成一个指定效果的矩阵。直接来看用法:
    11. private Camera mCamera;
    12. private Matrix mMatrix;
    13. mMatrix = new Matrix();
    14. mCamera = new Camera();
    15. 在onDraw里 把camera给旋转一下,并把生成的矩阵给一个矩阵。再把矩阵应用到canvas,看一下效果。
    1. mMatrix.reset();
    2. mCamera.save();
    3. mCamera.rotateX(10);
    4. mCamera.rotateY(20);
    5. mCamera.getMatrix(mMatrix);
    6. mCamera.restore();
    7. //将矩阵作用于整个canvas
    8. canvas.concat(mMatrix);
    9. ```

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图3

    呃。。。确实是变形了。。但是好像不是我们想要的结果?

    这是因为,矩阵的变换坐标总从左上角(0,0)开始。所以我们要把变换的坐标改为中心点,方法如下:

    1. mMatrix.reset();
    2. mCamera.save();
    3. mCamera.rotateX(10);
    4. mCamera.rotateY(20);
    5. mCamera.getMatrix(mMatrix);
    6. mCamera.restore();
    7. //改变矩阵作用点
    8. mMatrix.preTranslate(-mCenterX, -mCenterY);
    9. mMatrix.postTranslate(mCenterX, mCenterY);
    10. canvas.concat(mMatrix);
    11. ```
    12. 此时的效果看起来像是向左倾斜了:
    13. 这里写图片描述
    14. ![这里写图片描述](http://img.blog.csdn.net/20160127001356605)
    15. 接下来让他跟随手指移动,重写onTouchEvent:

    @Override

    public boolean onTouchEvent(MotionEvent event) {

    float x = event.getX();

    float y = event.getY();

    1. int action = event.getActionMasked();
    2. switch (action) {
    3. case MotionEvent.ACTION_MOVE: {
    4. //这里将camera旋转的变量赋值
    5. mCanvasRotateY = y;
    6. mCanvasRotateX = x;
    7. invalidate();
    8. return true;
    9. }
    10. case MotionEvent.ACTION_UP: {
    11. //这里将camera旋转的变量赋值
    12. mCanvasRotateY = 0;
    13. mCanvasRotateX = 0;
    14. invalidate();
    15. return true;
    16. }
    17. }
    18. return super.onTouchEvent(event);
    19. }
    20. ```

    哈哈。。看看是什么效果:

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图4

    什么鬼,怎么跟转硬币一样。 因为旋转的X,Y给的太大了呗。所以要约束一下。

    定义一个旋转最大值

    1. private float mCanvasMaxRotateDegree = 20;

    再用percent思想(前面博客有提到),来处理手指触摸点和这个度数变化的关系:

    1. private void rotateCanvasWhenMove(float x, float y) {
    2. float dx = x - mCenterX;
    3. float dy = y - mCenterY;
    4. float percentX = dx / mCenterX;
    5. float percentY = dy /mCenterY;
    6. if (percentX > 1f) {
    7. percentX = 1f;
    8. } else if (percentX < -1f) {
    9. percentX = -1f;
    10. }
    11. if (percentY > 1f) {
    12. percentY = 1f;
    13. } else if (percentY < -1f) {
    14. percentY = -1f;
    15. }
    16. mCanvasRotateY = mCanvasMaxRotateDegree * percentX;
    17. mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY);
    18. }
    19. ```
    20. 最后将TouchEvent里面的ACTION_MOVE 调用此函数即可。
    21. 此时,完整的代码如下:
    1. private int mCenterX;
    2. private int mCenterY;
    3. private float mCanvasRotateX = 0;
    4. private float mCanvasRotateY = 0;
    5. private float mCanvasMaxRotateDegree = 20;
    6. private Matrix mMatrix = new Matrix();
    7. private Camera mCamera = new Camera();
    8. private Paint mPaint;
    9. public TDView(Context context) {
    10. super(context);
    11. }
    12. public TDView(Context context, AttributeSet attrs) {
    13. super(context, attrs);
    14. mPaint = new Paint();
    15. mCanvasMaxRotateDegree = 20;
    16. }
    17. @Override
    18. protected void onDraw(Canvas canvas) {
    19. mCenterX = getWidth() / 2;
    20. mCenterY = getHeight() / 2;
    21. rotateCanvas(canvas);
    22. canvas.drawCircle(mCenterX, mCenterY, 100, mPaint);
    23. }
    24. private void rotateCanvas(Canvas canvas) {
    25. mMatrix.reset();
    26. mCamera.save();
    27. mCamera.rotateX(mCanvasRotateX);
    28. mCamera.rotateY(mCanvasRotateY);
    29. mCamera.getMatrix(mMatrix);
    30. mCamera.restore();
    31. mMatrix.preTranslate(-mCenterX, -mCenterY);
    32. mMatrix.postTranslate(mCenterX, mCenterY);
    33. canvas.concat(mMatrix);
    34. }
    35. @Override
    36. public boolean onTouchEvent(MotionEvent event) {
    37. float x = event.getX();
    38. float y = event.getY();
    39. int action = event.getActionMasked();
    40. switch (action) {
    41. case MotionEvent.ACTION_DOWN: {
    42. rotateCanvasWhenMove(x, y);
    43. return true;
    44. }
    45. case MotionEvent.ACTION_MOVE: {
    46. rotateCanvasWhenMove(x, y);
    47. invalidate();
    48. return true;
    49. }
    50. case MotionEvent.ACTION_UP: {
    51. mCanvasRotateY = 0;
    52. mCanvasRotateX = 0;
    53. invalidate();
    54. return true;
    55. }
    56. }
    57. return super.onTouchEvent(event);
    58. }
    59. private void rotateCanvasWhenMove(float x, float y) {
    60. float dx = x - mCenterX;
    61. float dy = y - mCenterY;
    62. float percentX = dx / mCenterX;
    63. float percentY = dy /mCenterY;
    64. if (percentX > 1f) {
    65. percentX = 1f;
    66. } else if (percentX < -1f) {
    67. percentX = -1f;
    68. }
    69. if (percentY > 1f) {
    70. percentY = 1f;
    71. } else if (percentY < -1f) {
    72. percentY = -1f;
    73. }
    74. mCanvasRotateY = mCanvasMaxRotateDegree * percentX;
    75. mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY);
    76. }

    }

    简简单单100行代码,实现了3D效果的view:

    这里写图片描述

    接下来做什么呢?

    当然是把这个效果加到我们的自定义view里面!

    比如,把我的PanelView加上这个效果:

    这里写图片描述

    这里写图片描述

    哗!瞬间高大上!

    那么,你要不要跟我趁热来一发自定义view?

    说搞就搞!

    在原有类上进行修改

    给一个好看的底色,画一条线

    1. mBgColor = Color.parseColor("#227BAE");
    2. canvas.drawLine(mCenterX,100,mCenterX,130,mPaint);
    3. ```

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图7

    嗯。不错 有条线了。微调下间距,旋转画布,画出整个圆形来:

    1. //保存坐标系
    2. canvas.save();
    3. for (int i = 0; i < 120; i++) {
    4. canvas.rotate(3,mCenterX,mCenterY);
    5. canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);
    6. }
    7. //恢复坐标系
    8. canvas.restore();

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图8

    嗯。。看起来想点样子了。 接下来调整透明度。

    1. canvas.save();
    2. for (int i = 0; i < 120; i++) {
    3. //根据i调整透明度alpha
    4. mPaint.setAlpha(255-(mAlpha * i/120));
    5. canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);
    6. canvas.rotate(3,mCenterX,mCenterY);
    7. }
    8. canvas.restore();

    这里写图片描述

    哈哈。。有没有点意思呢。。

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图9

    我们画个圆球上去!画之前先ctrl + alt + m 把之前画弧的方法提出来。

    画一个紧挨着的圆

    1. private void drawCircle(Canvas canvas) {
    2. mPaint.setAlpha(255);
    3. canvas.drawCircle(mCenterX,213,10,mPaint);
    4. }

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图10

    不错不错,给点动态效果吧,让圆点跟着我们触摸的地方走。怎么做呢。。 当然还是旋转画布了!

    这里需要注意的是触摸点与12点钟方向形成的夹角计算。画图分析一下

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图11

    可以看到 我们只需要调用Math.atan方法即可算出a的弧度,再将其转换为角度即可,在进行3D旋转之前,旋转画布:

    1. protected void onDraw(Canvas canvas) {
    2. canvas.drawColor(mBgColor);
    3. mCenterX = getWidth() / 2;
    4. mCenterY = getHeight() / 2;
    5. Log.e("wing",alpha+"");
    6. canvas.rotate((float) alpha,mCenterX,mCenterY);
    7. alpha = Math.atan((mTouchX-mCenterX)/(mCenterY-mTouchY));
    8. alpha = Math.toDegrees(alpha);
    9. if(mTouchY>mCenterY){
    10. alpha = alpha+180;

    现在看一下效果:

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图12

    这里写图片描述

    效果出来了,但是还美中不足呀。 因为中间太空了,所以这个3D效果看起来有点奇怪。那就给他中间加点东西吧! 比如一个指针。

    1. private void drawPath(Canvas canvas) {
    2. mPath.moveTo(mCenterX,223);
    3. mPath.lineTo(mCenterX-30,mCenterY);
    4. mPath.lineTo(mCenterX,2*mCenterY-223);
    5. mPath.lineTo(mCenterX+30,mCenterY);
    6. mPath.lineTo(mCenterX,233);
    7. mPath.close();
    8. canvas.drawPath(mPath,mPaint);
    9. mPaint.setColor(Color.parseColor("#55227BAE"));
    10. canvas.drawCircle(mCenterX,mCenterY,20,mPaint);
    11. }

    最后大功告成!!! 看效果!

    这里写图片描述

    一个炫字都不够??!!!手把手带你打造3D自定义view - 图13

    如果你喜欢我的博客,请点击关注。欢迎评论~~

    下一篇! 手把手教你做一个qq下拉抢红包:打开链接

    本项目地址:https://github.com/githubwing/compassView