手把手带你打造3D自定义view

blackblues 9年前

原文出处:http://blog.csdn.net/wingichoy/article/details/50590051 

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

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

好了不扯了。前些日子有朋友让我写博客来分析一下这个仿MIUI的时钟,从中学到了一些炫酷效果的实现。
https://github.com/AvatarQing/MiClockView  感谢原作者的开源精神!

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

这里写图片描述
这里写图片描述
这里写图片描述
其实后两个都是png来的。。

转载请注明出处: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);      }

最后大功告成!!! 看效果!
这里写图片描述

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

本项目地址:点击打开