Android 3D立体旋转效果实现

说明:之前在网上到处搜寻类似的旋转效果 但搜到的结果都不是十分满意 原因不多追述 
     ( 由于之前只是交流学习使用 现在已经改进为能用状态   )
(最新去github下载   修复滑动时偶有卡顿的问题 , 修正设置某些视图旋转到超过90° 残影问题  页面切换 修正为滑动到 角度设置的一半时  ,添加了自动归位为正面显示效果)
 

(如果有人找到过相关 比较好的效果 可以发一下连接 一起共同进步)

一 效果展示 :如非您所需要的效果 也希望能给些微帮助 (已经添加了上下滚动内容 )

        

具体操作以及实现 效果 请看项目例子

二 使用方式

此控件继承FrameLayout 


 

//aar引用 现更新为如下 建议不要使用(Bintray 要停了 有空换个仓库)

implementation 'com.icefordog:3dviewGroup:2.0.0'

1.所有包含子控件 现已经自动 重新布局到相应位置以及调整了大小
2.添加了上下滚动 以及代码主动翻页 和翻页回调
3.添加了自定义属性调整部分功能机体如下

# 使用方式如下

   <com.burning.foethedog.Rota3DSwithView
        android:id="@+id/_test_Rota3DVSwithView"
                android:layout_width="400dp"
                android:layout_height="300dp"
                app:autoscroll="true"
                app:heightRatio="0.8"

                app:rotateV="true"
                app:rotation="40"
                app:widthRatio="0.6">

           #是否自动滚动      app:autoscroll="true"
           #子控件对于改viegroup 中的宽高比例 默认无为0.7  
                       app:heightRatio  app:widthRatio
           #垂直或者水平滑动    app:rotateV="true"
           #偏转角度      app:rotation="40"
           setR3DPagechange   监听滑动页面正面页的变化
                #此处list偷懒借用了RecyclerView.Adapter 暂时仅可设置单一模板
    <com.burning.foethedog.Rota3DSwithViewList
        android:id="@+id/rota3DSwithViewList_test"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/colorAccent"
        app:autoscroll="true" />


子空间直接添加如同framelayout 相同 如要如图效果 唯一要求子空间必须位于父控件中心且宽高等大小 为了方便扩展而做 如有其他需求可自行更改 (注 所有子控件 最好添加上背景 由于绘制机制和动画原因 没有背景会有部分重贴) 内部子view 可为 任意ViewGroup 。 弱使用过程中遇见任何BUG 欢迎提出。

三 实现原理

      实现原理由Camera 与Maxtrix 组合修改View的绘制而得 具体Camera 与Maxtrix 的变换 过程请自行搜索。 在此不班门弄斧。具体修改有

 

@Override
protected void dispatchDraw(Canvas canvas) {
    int indexleft = getWidth() / 2;//中间显示视图 ----左边的位置
    int postTranslateX = rotationX * childWith / 2 / rotation;//设-----定边移动 距离
    //定点  又称顶点
    //  chilDrawforCamera(canvas, postTranslateX, indexleft, 3);//预绘制 的 县绘制  防止遮挡
    for (int i = 0; i < 4; i++)
        chilDrawforCamera(canvas, postTranslateX, indexleft, i);
    if (!isTouch)
        handler.sendEmptyMessageDelayed(1, 100);
}

重新编写 dispathDraw() 从而达到 不必要去修改子view的内容 而添加扩展性 

具体变换包括 

 

private void chilDrawforCamera(Canvas canvas, int postTranslateX, int indexleft, int i) {
    canvas.save();
    mCamera.save();
    mMaxtrix.reset();
    mCamera.translate(postTranslateX, 0, 0);
    mCamera.rotateY(rotationX);
    mCamera.translate(postTranslateX, 0, 0);
    if (postTranslateX == 0) {
        if (isright)
            setCameraChange(childWith, rotation, i);
        else
            setCameraChange(-childWith, -rotation, i);
    } else if (postTranslateX > 0) {
        setCameraChange(childWith, rotation, i);
    } else if (postTranslateX < 0) {
        setCameraChange(-childWith, -rotation, i);
    }
    mCamera.getMatrix(mMaxtrix);
    mCamera.restore();
    mMaxtrix.preTranslate(-indexleft, -getHeight() / 2);//指定在 屏幕上 运行的棱 是哪一条
    mMaxtrix.postTranslate(indexleft, getHeight() / 2);//运行路径
    canvas.concat(mMaxtrix);
    //绘制
    View childAt = getChildAt((swithView(i) + 2 * getChildCount()) % getChildCount());
    drawChild(canvas, childAt, 0);
    canvas.restore();
}

指定需要绘制的子view 先后顺序以及哪些子view 

 

private int swithView(int i) {
    int k = 0;

    switch (i) {
        case 0:
            if (isright)
                k = index - 2;
            else
                k = index + 2;
            break;
        case 1:
            if (isright)
                k = index + 1;
            else
                k = index - 1;
            break;
        case 2:
            if (isright)
                k = index - 1;
            else
                k = index + 1;
            break;
        case 3:
            k = index;
            break;
    }
    return k;
  
 

 具体的网上其他 类似效果到底有什么不同于优势在此不多做描述 实现方式上有哪些不同 嗯 下次有空再细说(由于需要大量图文描述 3D的变换过程才讲的清楚,有些间隔的时间也稍长 只记得大概的思路过程)。

private void setCameraChange(int translate, int roat, int i) {
    switch (i) {
        case 0:
            //预绘制 的VIEW
            mCamera.translate(-translate / 2, 0, 0);
            mCamera.rotateY(-roat);
            mCamera.translate(-translate / 2, 0, 0);

            mCamera.translate(-translate / 2, 0, 0);
            mCamera.rotateY(-roat);
            mCamera.translate(-translate / 2, 0, 0);
            break;
        //当前位置两侧的View
        case 1:
            mCamera.translate(translate / 2, 0, 0);
            mCamera.rotateY(roat);
            mCamera.translate(translate / 2, 0, 0);
            break;

        case 2:
            mCamera.translate(-translate / 2, 0, 0);
            mCamera.rotateY(-roat);
            mCamera.translate(-translate / 2, 0, 0);
            break;
        //最后绘制 当前显示位置 防止 被遮挡
        case 3:
            mCamera.rotateY(0);
            break;
    }


} 
 

其他剩下的就是 index 选中切换 以及滑动内容 和分发修改等。

整个类复制也可以(以下为重新修改后的完整代码)

 

package com.burning.foethedog;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;


/**
 * Created by burning on 2017/5/2.
 * When I wrote this, only God and I understood what I was doing
 * Now, God only knows
 * -------------------------//┏┓   ┏┓
 * -------------------------//┏┛┻━━━┛┻┓
 * -------------------------//┃       ┃
 * -------------------------//┃   ━   ┃
 * -------------------------//┃ ┳┛ ┗┳ ┃
 * -------------------------//┃       ┃
 * -------------------------//┃   ┻   ┃
 * -------------------------//┃       ┃
 * -------------------------//┗━┓   ┏━┛
 * -------------------------//┃   ┃  神兽保佑
 * -------------------------//┃   ┃  代码无BUG!
 * -------------------------//┃   ┗━━━┓
 * -------------------------//┃       ┣┓
 * -------------------------//┃       ┏┛
 * -------------------------//┗┓┓┏━┳┓┏┛
 * -------------------------// ┃┫┫ ┃┫┫
 * -------------------------// ┗┻┛ ┗┻┛
 */

public class Rota3DSwithView extends FrameLayout {
    Camera mCamera;
    Matrix mMaxtrix;

    public Rota3DSwithView(Context context) {
        super(context);
        initRoat3D();
    }

    private void initRoat3D() {
        mCamera = new Camera();
        mMaxtrix = new Matrix();
        setWillNotDraw(false);
    }

    private void initRoat3DStyle(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.rota3DVSwithView);
        rotateV = typedArray.getBoolean(R.styleable.rota3DVSwithView_rotateV, false);
        autoscroll = typedArray.getBoolean(R.styleable.rota3DVSwithView_autoscroll, true);
        setAutoscroll(autoscroll);
        rotation = typedArray.getInt(R.styleable.rota3DVSwithView_rotation, 40);
        heightRatio = typedArray.getFloat(R.styleable.rota3DVSwithView_heightRatio, 0.7f);
        widthRatio = typedArray.getFloat(R.styleable.rota3DVSwithView_widthRatio, 0.7f);

    }

    float widthRatio = 0.7f;
    float heightRatio = 0.7f;

    public Rota3DSwithView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initRoat3DStyle(attrs);
        initRoat3D();
    }

    public Rota3DSwithView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initRoat3DStyle(attrs);
        initRoat3D();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public Rota3DSwithView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initRoat3DStyle(attrs);
        initRoat3D();
    }

    //获取子View的宽或者高--作为旋转和移动依据
    int childHeight;
    int childWith;

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int rx = (int) ((right - left) * (1 - widthRatio) / 2);
        int ry = (int) ((bottom - top) * (1 - heightRatio) / 2);
        childHeight = (int) ((bottom - top) * heightRatio);
        childWith = (int) ((right - left) * widthRatio);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(rx, ry, right - left - rx, bottom - top - ry);
            child.setClickable(true);
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            if (layoutParams.width != childWith) {
                layoutParams.width = childWith;
                layoutParams.height = childHeight;
                child.setLayoutParams(layoutParams);
            }
        }

    }

    //摄像机 为点光源  正真的直角  反而看起来 并不是直角
    int rotation = 40;// 设定外角
    int moveRotation = 00;
    int index = 0;

    private void disDrawrX(Canvas canvas) {
        int indexleft = getWidth() / 2;//中间显示视图 ----左边的位置
        int postTranslateX = moveRotation * childWith / 2 / rotation;//设-----定边移动 距离
        for (int i = 0; i < 4; i++)
            chilDrawforCameraX(canvas, postTranslateX, indexleft, i);
    }

    private void disDrawrY(Canvas canvas) {
        int indexleft = getHeight() / 2;//中间显示视图 ----左边的位置
        int postTranslateX = moveRotation * childHeight / 2 / rotation;//设-----定边移动 距离
        //定点  又称顶点
        for (int i = 0; i < 4; i++) {
            chilDrawforCameraY(canvas, postTranslateX, indexleft, i);
        }
    }

    boolean rotateV = false;

    public boolean isRotateV() {
        return rotateV;
    }

    public void setRotateV(boolean rotateV) {
        this.rotateV = rotateV;
        this.invalidate();
    }

    public int getmoveRotation() {
        return moveRotation;
    }

    public void setmoveRotation(int moveRotation) {
        this.moveRotation = moveRotation;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (getChildCount() == 0) {
            return;
        }
        if (rotateV) {
            disDrawrY(canvas);
        } else {
            disDrawrX(canvas);
        }
    }

    boolean autoscroll = true;

    public boolean isAutoscroll() {
        return autoscroll;
    }

    public void setAutoscroll(boolean autoscroll) {
        if (autoscroll) {
            senMessageStart();
        }
        this.autoscroll = autoscroll;
    }

    private void senMessageStart() {
        handler.sendEmptyMessageDelayed(2, 10);
    }

    private void setCameraChangeY(int translate, int roat, int i) {
        switch (i) {
            case 0:
                //预绘制 的VIEW
                mCamera.translate(0, -translate / 2, 0);
                mCamera.rotateX(-roat);
                mCamera.translate(0, -translate / 2, 0);

                mCamera.translate(0, -translate / 2, 0);
                mCamera.rotateX(-roat);
                mCamera.translate(0, -translate / 2, 0);
                break;
            //当前位置两侧的View
            case 1:
                mCamera.translate(0, translate / 2, 0);
                mCamera.rotateX(roat);
                mCamera.translate(0, translate / 2, 0);
                break;

            case 2:
                mCamera.translate(0, -translate / 2, 0);
                mCamera.rotateX(-roat);
                mCamera.translate(0, -translate / 2, 0);
                break;
            //最后绘制 当前显示位置 防止 被遮挡
            case 3:
                mCamera.rotateX(0);
                break;
        }


    }

    private boolean isleftortop = false;

    public boolean isIsleftortop() {
        return isleftortop;
    }

    public void setIsleftortop(boolean isleftortop) {
        startAnimation(isleftortop);
    }

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 2: {
                    if (getChildCount() == 0) {
                        return;
                    }
                    if (isTouch) {
                        return;
                    }
                    if (isleftortop)
                        moveRotation++;
                    else
                        moveRotation--;
                    if (Math.abs(moveRotation) == rotation) {
                        moveRotation = 0;
                        int position = index % getChildCount();
                        reSetIndex(position);
                        if (isleftortop) {
                            position = index - 1;
                        } else {
                            position = index + 1;
                        }
                        reSetIndex(position);
                    }
                    showIndex = index;
                    rotaViewtangle(moveRotation);
                    if (isAutoscroll())
                        senMessageStart();
                }
            }
        }
    };


    private void chilDrawforCameraY(Canvas canvas, int postTranslateX, int indexleft, int i) {
        canvas.save();
        mCamera.save();
        mMaxtrix.reset();
        mCamera.translate(0, postTranslateX, 0);
        mCamera.rotateX(moveRotation);
        mCamera.translate(0, postTranslateX, 0);
        if (postTranslateX == 0) {
            if (isleftortop)
                setCameraChangeY(childHeight, rotation, i);
            else
                setCameraChangeY(-childHeight, -rotation, i);
        } else if (postTranslateX > 0) {
            setCameraChangeY(childHeight, rotation, i);
        } else if (postTranslateX < 0) {
            setCameraChangeY(-childHeight, -rotation, i);
        }
        mCamera.getMatrix(mMaxtrix);
        mCamera.restore();
        mMaxtrix.preTranslate(-getWidth() / 2, -indexleft);//指定在 屏幕上 运行的棱 是哪一条
        mMaxtrix.postTranslate(getWidth() / 2, indexleft);//运行路径
        canvas.concat(mMaxtrix);
        //绘制
        View childAt = getChildAt(swithView(i)/*(swithView(i) + 2 * getChildCount()) % getChildCount()*/);
        if (childAt != null)
            drawChild(canvas, childAt, 0);
        canvas.restore();
    }

    private int swithView(int i) {
        int k = 0;

        switch (i) {
            case 0:
                if (isleftortop)
                    k = index - 2;
                else
                    k = index + 2;
                break;
            case 1:
                if (isleftortop)
                    k = index + 1;
                else
                    k = index - 1;
                break;
            case 2:
                if (isleftortop)
                    k = index - 1;
                else
                    k = index + 1;
                break;
            case 3:
                k = index;
                break;
        }
        int j = k % getChildCount();
        if (j >= 0) {
            return j;
        } else {
            return (j + getChildCount());
        }
//        return k;
    }

    boolean isTouch = false;
    int downXorY = 0;

    public boolean dispatchTouchEvent(MotionEvent event) {
        if (getChildCount() == 0) {
            return super.dispatchTouchEvent(event);
        }
        //这里我们就 就只分发给当前index子View
        if (!onInterceptTouchEvent(event)) {
            return getChildAt(swithView(3)).dispatchTouchEvent(event);
        }
        return super.dispatchTouchEvent(event);
    }

    int thisRx = 0;
    int showIndex;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (getChildCount() == 0) {
            return super.onInterceptTouchEvent(event);
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (isRotateV()) {
                    downXorY = (int) event.getY();
                } else {
                    downXorY = (int) event.getX();
                }
                showIndex = index;
                thisRx = moveRotation;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isRotateV()) {
                    if (Math.abs(event.getY() - downXorY) > 50) {
                        return true /*onTouchEvent(event)*/;
                    }
                } else {
                    if (Math.abs(event.getX() - downXorY) > 50) {
                        return true /*onTouchEvent(event)*/;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                return isTouch;
        }
        return super.onInterceptTouchEvent(event);
    }

    boolean touching_auto = false;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (getChildCount() == 0) {
            return super.onTouchEvent(event);
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                isTouch = true;
                ontouchView(event);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isTouch = false;
                senMessageStart();
                break;
        }
        return true;
    }

    private int ontouchView(MotionEvent event) {
        int movedistance = 0;
        int moveRxory = 0;
        if (isRotateV()) {
            movedistance = -((int) event.getY() - downXorY);
            moveRxory = thisRx + movedistance * rotation * 2 / (getHeight() + 100);
        } else {
            movedistance = (int) event.getX() - downXorY;
            moveRxory = thisRx + movedistance * rotation * 2 / (getWidth() + 100);
        }
        isleftortop = (moveRxory > 0) ? true : false;
        rotaViewtangle(moveRxory);
        return movedistance;
    }

    private int rotaViewtangle(int moveRxory) {

        int removeItem = moveRxory / rotation;
        int position = showIndex - removeItem;
        reSetIndex(position);
        moveRotation = moveRxory % rotation;
        this.invalidate();
        return moveRotation;
    }

    private void setCameraChangeX(int translate, int roat, int i) {
        switch (i) {
            case 0:
                //预绘制 的VIEW
                mCamera.translate(-translate / 2, 0, 0);
                mCamera.rotateY(-roat);
                mCamera.translate(-translate / 2, 0, 0);

                mCamera.translate(-translate / 2, 0, 0);
                mCamera.rotateY(-roat);
                mCamera.translate(-translate / 2, 0, 0);
                break;
            //当前位置两侧的View
            case 1:
                mCamera.translate(translate / 2, 0, 0);
                mCamera.rotateY(roat);
                mCamera.translate(translate / 2, 0, 0);
                break;

            case 2:
                mCamera.translate(-translate / 2, 0, 0);
                mCamera.rotateY(-roat);
                mCamera.translate(-translate / 2, 0, 0);
                break;
            //最后绘制 当前显示位置 防止 被遮挡
            case 3:
                mCamera.rotateY(0);
                break;
        }


    }

    private void chilDrawforCameraX(Canvas canvas, int postTranslateX, int indexleft, int i) {
        canvas.save();
        mCamera.save();
        mMaxtrix.reset();
        mCamera.translate(postTranslateX, 0, 0);
        mCamera.rotateY(moveRotation);
        mCamera.translate(postTranslateX, 0, 0);
        if (postTranslateX == 0) {
            if (isleftortop)
                setCameraChangeX(childWith, rotation, i);
            else
                setCameraChangeX(-childWith, -rotation, i);
        } else if (postTranslateX > 0) {
            setCameraChangeX(childWith, rotation, i);
        } else if (postTranslateX < 0) {
            setCameraChangeX(-childWith, -rotation, i);
        }
        mCamera.getMatrix(mMaxtrix);
        mCamera.restore();
        mMaxtrix.preTranslate(-indexleft, -getHeight() / 2);//指定在 屏幕上 运行的棱 是哪一条
        mMaxtrix.postTranslate(indexleft, getHeight() / 2);//运行路径
        canvas.concat(mMaxtrix);
        //绘制
        View childAt = getChildAt(swithView(i) /*( + 2 * getChildCount()) % getChildCount()*/);
        if (childAt != null)
            drawChild(canvas, childAt, 0);
        canvas.restore();
    }


    public void destory() {
        handler.removeCallbacksAndMessages(null);
        handler = null;
    }

    private void reSetIndex(int position) {
        index = position;
        showDataPage(position);
    }


    @SuppressLint("ObjectAnimatorBinding")
    public void returnPage() {
        startAnimation(true);
    }

    private boolean isAnimationStarting = false;

    @SuppressLint("ObjectAnimatorBinding")
    public void nextPage() {
        startAnimation(false);
    }

    @SuppressLint("ObjectAnimatorBinding")
    private void startAnimation(boolean rightortop) {
        if (isAnimationStarting) {
            return;
        }
        isAnimationStarting = true;
        isleftortop = rightortop;
        showIndex = index;
        Interpolator interpolator = new AccelerateInterpolator();
        ObjectAnimator mAnimator = ObjectAnimator.ofInt(this, "xxxxx", moveRotation, rightortop ? rotation : -rotation);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animatedValue = (int) animation.getAnimatedValue();
                rotaViewtangle(animatedValue);
            }
        });
        mAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isAnimationStarting = false;
            }
        });
        mAnimator.setInterpolator(interpolator);
        mAnimator.setDuration(120);
        mAnimator.start();
    }

    int showPage = 0;

    private void showDataPage(int position) {
        int i = position % getChildCount();
        int mathpage = 0;
        if (i >= 0) {
            mathpage = i;
        } else {
            mathpage = i + getChildCount();
        }
        if (mathpage != showPage) {
            showPage = mathpage;
            if (r3DPagechange != null) {
                r3DPagechange.onPageChanged(showPage);
            }
        }
    }

    R3DPagechange r3DPagechange;

    public void setR3DPagechange(R3DPagechange r3DPagechange) {
        this.r3DPagechange = r3DPagechange;
    }

    public interface R3DPagechange {
        void onPageChanged(int position);
    }
}

 

 

*为下载了之前资源的说声抱歉-之前那个并不适用直接使用仅作为一个半成品

github下载地址:https://github.com/liuyangxiao/3DViewGroup

如有疑问可私信交流^_^

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值