关于Android自定义UI-教你如何实现一个3d立体方块

废话不多说先看效果图

立体方块.gif

以前在网上看到前端有个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();//保存原先状态
        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);//设置摄像机的位置。此处参数单位不是像素,而是 inch英寸/72px
        mCamera.translate(0, 0, -childViewHeight / 2);//在所有三个轴上应用平移变换。
        mCamera.getMatrix(mMatrix);//将内部的Matrix的值复制到matrix(注意必须在restore之前)
        mCamera.restore();//恢复保存的状态(如果有)
        mMatrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);//在队列头部添加Translate
        mMatrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);//在队列尾部添加Translate
        //动画执行顺序,preTranslate让childView中心移动到坐标轴中点,rotateX,rotateY绕轴旋转,postTranslate让childView回到原来位置,实现对称旋转
        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;

    //实现点击子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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容