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

廢話不多說先看效果圖

立體方塊.gif

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

這里貼代碼

public class Custom3DView extends ViewGroup {
    private Camera mCamera = new Camera();//攝像機(jī)
    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  子最大可以達(dá)到的指定大小
                viewGroupWidth = childViewMaxWidth;
                break;
            case MeasureSpec.UNSPECIFIED:// 父容器不對(duì)子View的大小做限制.
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY://match_parent 100dp 確切值 父決定子的確切大小,子被限定在給定的邊界里,忽略本身想要的大小
                viewGroupHeight = heightSize > childViewMaxHeight ? heightSize : childViewMaxHeight;//選擇較大的值
                break;
            case MeasureSpec.AT_MOST://wrap_content  子最大可以達(dá)到的指定大小
                viewGroupHeight = childViewMaxHeight;
                break;
            case MeasureSpec.UNSPECIFIED:// 父容器不對(duì)子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();
        //坐標(biāo)軸中點(diǎn)在左上角
        mCamera.save();//保存原先狀態(tài)
        switch (page) {
            case 0://背面
                mCamera.rotateX(rotateX + 180);
                mCamera.rotateY(-rotateY);//圖像圍繞Y軸旋轉(zhuǎn)
                break;
            case 1://左面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY - 90);
                break;
            case 2://上面
                mCamera.rotateX(rotateX + 90);
                mCamera.rotateZ(rotateY);//圖像圍繞Z軸旋轉(zhuǎn)
                break;
            case 3://右面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY + 90);
                break;
            case 4://下面
                mCamera.rotateX(rotateX + 270);
                mCamera.rotateZ(-rotateY);//圖像圍繞Z軸旋轉(zhuǎn)
                break;
            case 5://正面
                mCamera.rotateX(rotateX);
                mCamera.rotateY(rotateY);//圖像圍繞Y軸旋轉(zhuǎn)
                break;

        }
        mMatrix.reset();
        mCamera.setLocation(0, 0, -Integer.MAX_VALUE);//設(shè)置攝像機(jī)的位置。此處參數(shù)單位不是像素,而是 inch英寸/72px
        mCamera.translate(0, 0, -childViewHeight / 2);//在所有三個(gè)軸上應(yīng)用平移變換。
        mCamera.getMatrix(mMatrix);//將內(nèi)部的Matrix的值復(fù)制到matrix(注意必須在restore之前)
        mCamera.restore();//恢復(fù)保存的狀態(tài)(如果有)
        mMatrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);//在隊(duì)列頭部添加Translate
        mMatrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);//在隊(duì)列尾部添加Translate
        //動(dòng)畫執(zhí)行順序,preTranslate讓childView中心移動(dòng)到坐標(biāo)軸中點(diǎn),rotateX,rotateY繞軸旋轉(zhuǎn),postTranslate讓childView回到原來位置,實(shí)現(xiàn)對(duì)稱旋轉(zhuǎn)
        canvas.save();
        canvas.concat(mMatrix);
        switch (page) {//rotateX,rotateY浮動(dòng)90以內(nèi)畫出
            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;

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

    private boolean intercept = false;//默認(rèn)不攔截

    @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();//相對(duì)父容器
        float rawY = event.getRawY();//相對(duì)父容器
        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();
    }
}

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

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

3d效果2.gif

最后附上項(xiàng)目地址 Custom3DViewApp

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容