關于Android自定義UI-教你如何實現(xiàn)一個3d立體方塊

廢話不多說先看效果圖

立體方塊.gif

以前在網(wǎng)上看到前端有個3d立體相冊效果很好看,心想Android怎么能沒這個效果,這次就倉促花了兩天時間寫了這個控件。

這里貼代碼

public class Custom3DView extends ViewGroup {
    private Camera mCamera = new Camera();//攝像機
    private Matrix mMatrix = new Matrix();//矩陣

    public Custom3DView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private int childViewMaxWidth = 0, childViewMaxHeight = 0;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int viewGroupWidth = 0, viewGroupHeight = 0;
        measureChildren(widthMeasureSpec, heightMeasureSpec);//測量子view
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            childViewMaxWidth = childViewMaxWidth < childView.getMeasuredWidth() ? childView.getMeasuredWidth() : childViewMaxWidth;
            childViewMaxHeight = childViewMaxHeight < childView.getMeasuredHeight() ? childView.getMeasuredHeight() : childViewMaxHeight;
        }

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY://match_parent 100dp 確切值 父決定子的確切大小,子被限定在給定的邊界里,忽略本身想要的大小
                viewGroupWidth = widthSize > childViewMaxWidth ? widthSize : childViewMaxWidth;//選擇較大的值
                break;
            case MeasureSpec.AT_MOST://wrap_content  子最大可以達到的指定大小
                viewGroupWidth = childViewMaxWidth;
                break;
            case MeasureSpec.UNSPECIFIED:// 父容器不對子View的大小做限制.
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY://match_parent 100dp 確切值 父決定子的確切大小,子被限定在給定的邊界里,忽略本身想要的大小
                viewGroupHeight = heightSize > childViewMaxHeight ? heightSize : childViewMaxHeight;//選擇較大的值
                break;
            case MeasureSpec.AT_MOST://wrap_content  子最大可以達到的指定大小
                viewGroupHeight = childViewMaxHeight;
                break;
            case MeasureSpec.UNSPECIFIED:// 父容器不對子View的大小做限制.
                break;
        }
        setMeasuredDimension(viewGroupWidth, viewGroupHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left, right, top, bottom;
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            left = (getMeasuredWidth() - childView.getMeasuredWidth()) / 2;
            right = (getMeasuredWidth() - childView.getMeasuredWidth()) / 2 + childView.getMeasuredWidth();
            top = (getMeasuredHeight() - childView.getMeasuredHeight()) / 2;
            bottom = (getMeasuredHeight() - childView.getMeasuredHeight()) / 2 + childView.getMeasuredHeight();
            childView.layout(left, top, right, bottom);//所有childView居中疊加顯示
            childView.setVisibility(GONE);
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawChildView(canvas, 0);//0背面 1左面 2上面 3右面 4下面 5正面
        drawChildView(canvas, 1);
        drawChildView(canvas, 2);
        drawChildView(canvas, 3);
        drawChildView(canvas, 4);
        drawChildView(canvas, 5);
    }

    private float rotateX, rotateY;

    private void drawChildView(Canvas canvas, int page) {
        View childView = getChildAt(page);
        int childViewWidth = childView.getMeasuredWidth();
        int childViewHeight = childView.getMeasuredHeight();
        //坐標軸中點在左上角
        mCamera.save();//保存原先狀態(tài)
        switch (page) {
            case 0://背面
                mCamera.rotateX(rotateX + 180);
                mCamera.rotateY(-rotateY);//圖像圍繞Y軸旋轉
                break;
            case 1://左面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY - 90);
                break;
            case 2://上面
                mCamera.rotateX(rotateX + 90);
                mCamera.rotateZ(rotateY);//圖像圍繞Z軸旋轉
                break;
            case 3://右面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY + 90);
                break;
            case 4://下面
                mCamera.rotateX(rotateX + 270);
                mCamera.rotateZ(-rotateY);//圖像圍繞Z軸旋轉
                break;
            case 5://正面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY);//圖像圍繞Y軸旋轉
                break;

        }
        mMatrix.reset();
        mCamera.setLocation(0, 0, -Integer.MAX_VALUE);//設置攝像機的位置。此處參數(shù)單位不是像素,而是 inch英寸/72px
        mCamera.translate(0, 0, -childViewHeight / 2);//在所有三個軸上應用平移變換。
        mCamera.getMatrix(mMatrix);//將內部的Matrix的值復制到matrix(注意必須在restore之前)
        mCamera.restore();//恢復保存的狀態(tài)(如果有)
        mMatrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);//在隊列頭部添加Translate
        mMatrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);//在隊列尾部添加Translate
        //動畫執(zhí)行順序,preTranslate讓childView中心移動到坐標軸中點,rotateX,rotateY繞軸旋轉,postTranslate讓childView回到原來位置,實現(xiàn)對稱旋轉
        canvas.save();
        canvas.concat(mMatrix);
        switch (page) {//rotateX,rotateY浮動90以內畫出
            case 0://背面
                //        背面 y=180 x=0,y=0,x=180
                if ((rotateY <= 270 && rotateY >= 90) && (rotateX <= 90 || rotateX >= 270))
                    drawChild(canvas, childView, getDrawingTime());
                else if ((rotateY <= 90 || rotateY >= 270) && (rotateX >= 90 && rotateX <= 270))
                    drawChild(canvas, childView, getDrawingTime());
                break;
            case 1://左面
                //        左面 y=90 x=0,y=270,x=180
                if ((rotateY >= 0 && rotateY <= 180) && (rotateX <= 90 || rotateX >= 270))
                    drawChild(canvas, childView, getDrawingTime());
                else if ((rotateY >= 180 && rotateY <= 360) && (rotateX >= 90 && rotateX <= 270))
                    drawChild(canvas, childView, getDrawingTime());
                break;
            case 2://上面
                //        上面 x=270
                if (rotateX >= 180 && rotateX <= 360)
                    drawChild(canvas, childView, getDrawingTime());

                break;
            case 3://右面
                //        右面 y=270 x=0,y=90,x=180
                if ((rotateY >= 180 && rotateY <= 360) && (rotateX <= 90 || rotateX >= 270))
                    drawChild(canvas, childView, getDrawingTime());
                else if ((rotateY >= 0 && rotateY <= 180) && (rotateX >= 90 && rotateX <= 270))
                    drawChild(canvas, childView, getDrawingTime());
                break;
            case 4://下面
                //        下面 x=90
                if (rotateX >= 0 && rotateX <= 180)
                    drawChild(canvas, childView, getDrawingTime());
                break;
            case 5://正面
                //        正面 y=0 x=0,y=180 x=180
                if ((rotateY <= 90 || rotateY >= 270) && (rotateX <= 90 || rotateX >= 270))
                    drawChild(canvas, childView, getDrawingTime());
                else if ((rotateY >= 90 && rotateY <= 270) && (rotateX >= 90 && rotateX <= 270))
                    drawChild(canvas, childView, getDrawingTime());
                break;
        }
        canvas.restore();
    }

    float downX, downY, moveX, moveY;

    //實現(xiàn)點擊子view手指不移動,子view點擊事件有效,其他情況子view點擊事件無效
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        float rawX = event.getRawX();//相對父容器
        float rawY = event.getRawY();//相對父容器
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = rawX;
                downY = rawY;
                intercept = false;//按下不攔截
                break;
            case MotionEvent.ACTION_MOVE:
                intercept = true;//滑動自己處理
                moveX = rawX;
                moveY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    private boolean intercept = false;//默認不攔截

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (intercept) {//攔截
            return intercept;
        }
        return super.onInterceptTouchEvent(event);
    }

    private float dy, lastDy;
    private float dx, lastDx;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float rawX = event.getRawX();//相對父容器
        float rawY = event.getRawY();//相對父容器
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = rawX;
                downY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = rawX;
                moveY = rawY;
                dx = ((moveX - downX) + lastDx) % 360;
                dy = (-(moveY - downY) + lastDy) % 360;
                rotateX = dy < 0 ? dy % 360 + 360 : dy % 360;
                rotateY = dx < 0 ? dx % 360 + 360 : dx % 360;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                lastDx = dx;
                lastDy = dy;
                break;
        }
        return true;
    }

    public void stopAnimal() {
        exitAnimal = true;
        isAnimalRunning = false;
    }

    private boolean exitAnimal = true;
    private boolean isAnimalRunning = false;

    public void startAnimal() {
        if (isAnimalRunning) {
            return;
        }
        exitAnimal = false;
        isAnimalRunning = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!exitAnimal) {
                    try {
                        Thread.sleep(10);
                        lastDy += 1;
                        lastDx += 1;
                        dx = lastDx % 360;
                        dy = lastDy % 360;
                        rotateX = dy < 0 ? dy % 360 + 360 : dy % 360;
                        rotateY = dx < 0 ? dx % 360 + 360 : dx % 360;
                        postInvalidate();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

代碼還算比較簡單,第一次寫這個效果,歷時兩天,過程較艱苦,注釋寫的很清楚了,就不再贅述思路了, 有需要的可以直接復制粘貼。

下面這張圖的效果在項目的2.0控件中,和上面貼代碼的這個版本思路不太一樣,有興趣的可以去項目中看下代碼。

3d效果2.gif

最后附上項目地址 Custom3DViewApp

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容