A recent popular joke: The latest scientific research shows that cold can keep people young. Uncle Wang downstairs said that although he is over 60 this year, he is still as cold as his grandson.

Er. Well, this winter is really a bit cold, living in Guangzhou, I am a native northerner, frozen into a dog. (Study: Cold can mutate human genes…)

There you go. A few days ago, a friend asked me to write a blog to analyze this miUi-like clock and learned some cool effects from it. Github.com/AvatarQing/… Thanks for the open source spirit of the original author!

So what is the 3D effect, first take a look at the effect picture, um.. There are several:







In fact, the last two are PNG.

Reprint please indicate the source: blog.csdn.net/wingichoy/a…

So what do you think of when you look at the transformation of shapes? That’s right! Is the Matrix. You can go to Aige’s blog to learn about Matrix and its detailed explanation (thanks Aige!). .

Now let’s look at how we can use matrices to achieve this 3D effect.

Start by creating a custom View class.

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(); }}Copy the code

Then draw a circle in the center

 @Override
    protected void onDraw(Canvas canvas) {

        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
        canvas.drawCircle(mCenterX,mCenterY,100,mPaint);
    }Copy the code

Now it looks like this:

We know that we can use a matrix for an image, and we can use the Camera class for X and Y, and the Camera class generates a matrix for the specified effect. To get straight to the point:

private Camera mCamera;
private Matrix mMatrix;

mMatrix = new Matrix();
mCamera = new Camera();

Copy the code

Rotate the camera in onDraw and assign the resulting matrix to a matrix. Apply the matrix to the canvas and see what it looks like.

mMatrix.reset() mCamera.save() mCamera.rotateX(10) mCamera.rotateY(20) mCamera.getMatrix(mMatrix) mCamera.restore() // Apply the matrix to the entire canvas canvas.concat(mMatrix)Copy the code



Uh… It is indeed deformed. But it’s not what we want, right?

That’s because the transformation coordinates of a matrix always start at the top left corner (0,0). So we need to change the coordinate of transformation to the center point as follows:

mMatrix.reset() mCamera.save() mCamera.rotateX(10) mCamera.rotateY(20) mCamera.getMatrix(mMatrix) mCamera.restore() PreTranslate (-mcenterx, -mcentery) mMatrix. PostTranslate (mCenterX, mCenterY) canvas. Concat (mMatrix)Copy the code

The effect now looks like a tilt to the left:

Next let it follow the finger and override 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: {
                
                mCanvasRotateY = y;
                mCanvasRotateX = x;
                invalidate();
                return true;
            }
            case MotionEvent.ACTION_UP: {

                
                mCanvasRotateY = 0;
                mCanvasRotateX = 0;
                invalidate();

                return true;
            }
        }
        return super.onTouchEvent(event);
    }Copy the code

Ha ha.. See what it looks like:



What the hell? It’s like flipping coins. Because the rotated X, the rotated Y is too big. So we have to constrain it.

Define a rotation maximum

    private float mCanvasMaxRotateDegree = 20;Copy the code

Then use the percent idea (mentioned in the previous blog) to deal with the relationship between finger touch points and the change in degree:

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);

    }
Copy the code

Finally, call this function from ACTION_MOVE in TouchEvent. At this point, the complete code looks like this:

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); }}Copy the code

A simple 100 lines of code for a 3D view:

What do I do next?

Add this effect to our custom view, of course!

For example, add this effect to my PanelView:



Wow!!!! Instantly lofty!

So, would you like to come with me to define a view while it’s hot? Just do it!

Make changes to the existing class

To get a nice base color, draw a line

        mBgColor = Color.parseColor("#227BAE")
        canvas.drawLine(mCenterX,100,mCenterX,130,mPaint)Copy the code



Well. Good. There’s a line. Slightly adjust the spacing and rotate the canvas to create a full circle:


        canvas.save();
        for (int i = 0; i < 120; i++) {
            canvas.rotate(3,mCenterX,mCenterY);
            canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);
        }
        
        canvas.restore();Copy the code



B: well.. It looks like it’s getting better. Next adjust the transparency.

canvas.save();
        for (int i = 0; i < 120; i++) {
            
            mPaint.setAlpha(255-(mAlpha * i/120));
            canvas.drawLine(mCenterX, 150, mCenterX, 170, mPaint);

            canvas.rotate(3,mCenterX,mCenterY);
        }
        canvas.restore();
Copy the code



Ha ha.. Is it interesting.

Let’s draw a ball on it! CTRL + Alt + M before I draw it to bring out the way I drew the arc.

Draw a circle right next to each other

private void drawCircle(Canvas canvas) { mPaint.setAlpha(255); Canvas. Methods like drawCircle (mCenterX, 213, 10, mPaint); }Copy the code

All right, all right, let’s give it some motion, let’s make the dots follow where we touch them. How to do. Rotate the canvas, of course! What needs to be noted here is the calculation of the Angle formed between the touch point and the 12 o ‘clock direction. Let’s draw a picture

As you can see, we just need to call math.atan to figure out the radian of A and convert it to an Angle. Before doing the 3D rotation, rotate the canvas:

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;
        }Copy the code

Now look at the effect:



The effect is there, but there is still a fly in the ointment. The 3D effect looks a bit strange because of the middle space. Then put something in the middle! Like a pointer.

   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)
    }Copy the code

Finally done!! See the effect!

If you like my blog, please follow it. Comments are welcome

The project address: click open