1. 程式人生 > >一個炫字都不夠??!!!手把手帶你打造3D自定義view

一個炫字都不夠??!!!手把手帶你打造3D自定義view

分享一則最近流行的笑話:
最新科學研究表明:寒冷可以使人保持年輕,樓下的王大爺表示雖然今年已經60多歲了,但是仍然冷的跟孫子一樣。

呃。好吧,這個冬天確實有點冷,在廣州活生生的把我這個原生北方人,凍成一條狗。(研究表明:寒冷可以讓人類基因突變。。。。)

好了不扯了。前些日子有朋友讓我寫部落格來分析一下這個仿MIUI的時鐘,從中學到了一些炫酷效果的實現。

那麼是啥3D效果呢,先來看看效果圖,額。。有好多個:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

其實後兩個都是png來的。。

那麼請問,看到圖形的變換,你想到了什麼?
沒錯!就是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);
        ```
這裡寫圖片描述 
![這裡寫圖片描述](https://img-blog.csdn.net/20160127001011557)
呃。。。確實是變形了。。但是好像不是我們想要的結果? 
這是因為,矩陣的變換座標總從左上角(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);
    }

哈哈。。看看是什麼效果: 
這裡寫圖片描述
![這裡寫圖片描述](http://img.blog.csdn.net/20160127002007112)

什麼鬼,怎麼跟轉硬幣一樣。 因為旋轉的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下拉搶紅包:開啟連結