澳门新葡萄京官网注册 22

澳门新葡萄京官网注册从零开始打造一个Android 3D立体旋转容器

1.概述

回到正题,这次带来的效果,是一个Android 的3D立体旋转的效果。

当然灵感的来源,来自早些时间微博上看到的效果图。

非常酷有木有!作为程序猿我当然要把它加入我的下一个项目中啦!

原效果

澳门新葡萄京官网注册 1

我们实现的效果:

(为了更加可定制化,我在原图基础上新增了新的效果)

澳门新葡萄京官网注册 2

可以快速滚动,并且无限循环

澳门新葡萄京官网注册 3

这个是对一些参数的进行设定

澳门新葡萄京官网注册 4

对图片的包裹效果

澳门新葡萄京官网注册 5

因为本身继承自ViewGroup,所以基本控件都是可以包裹的

本文地址,转载请注明
代码下载地址
:https://github.com/ImmortalZ/StereoView

澳门新葡萄京官网注册 6Camera

2.分析

因为代码量有点大,感觉把代码全部粘贴上来也不现实。所以想了解我的思路的盆友可以先来这里下载代码。然后边看代码边看我的分析

下载地址 :

通过我们实现的效果图可以发现:

1.切换的时候是一个3D立体的效果

2.布局中的每一个Item可以自由切换,且无限循环滚动

要解决上面的效果,我们需要什么技术点呢?

1.要想实现一个3D效果,我们可以借助Android中的Camera、Matrix

2.要想实现滚动,毫无疑问,我们需要借助Scroller

当然一切看起来很简单,其实不然,除此之外,你还需要对于滑动冲突进行处理等等,下面我开始介绍啦。

这就是我们这次项目的大致

澳门新葡萄京官网注册 7

嗯,2个月没有写博客,是要好好反省下,趁着放暑假把这两个月看的东西好好沉淀下。嗯,就立下这个Flag,希望不要自己再打自己脸。

本文行文目录:一、Camera与Matrix初步认识二、Camera与Matrix旋转效果拆分介绍三、Camera与Matrix实现立体3D切换效果

3.实现

因为我们是要打造一个容器类,所以肯定得继承自 ViewGroup
按照一般的思路,我们肯定是先要进行一些变量的申明,onMeasure,onLayout操作

private void init(Context context) {
    mCamera = new Camera();
    mMatrix = new Matrix();
    if (mScroller == null) {
        mScroller = new Scroller(context);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();
    //滑动到设置的StartScreen位置
    scrollTo(0, mStartScreen * mHeight);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childTop = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            child.layout(0, childTop,
                    child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
            childTop = childTop + child.getMeasuredHeight();
        }
    }
}

完成这些操作后,我们需要在onTouchEvent中进行滑动事件的处理

3.1 完成无限循环滑动滚动

我们的item数量是有限的,如何实现无限循环滚动呢?很简单,以3个item为例子(分别为1,2,3),我们让屏幕显示的是2

如此反复,屏幕所在的位置始终是第2个item所在的位置,这样就实现了我们的无限循环滚动,向下滚动也是如此

澳门新葡萄京官网注册 8

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    //当上一次滑动没有结束时,再次点击,强制滑动在点击位置结束
                    mScroller.setFinalY(mScroller.getCurrY());
                    mScroller.abortAnimation();
                    scrollTo(0, getScrollY());
                }
                mDownY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int realDelta = (int) (mDownY - y);
                mDownY = y;
                if (mScroller.isFinished()) {
                    //因为要循环滚动
                    recycleMove(realDelta);
                }
                break;
            case MotionEvent.ACTION_UP:
                mVelocityTracker.computeCurrentVelocity(1000);
                float yVelocity = mVelocityTracker.getYVelocity();
                //滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为State.ToPre
                if (yVelocity > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)) {
                    mState = State.ToPre;
                } else if (yVelocity < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)) {
                    //滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为State.ToNext
                    mState = State.ToNext;
                } else {
                    mState = State.Normal;
                }
                //根据mState进行相应的变化
                changeByState(yVelocity);
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
        }
        //返回true,消耗点击事件
        return true;
    }

当手从屏幕上移开时,我们来看下这个方法changeByState(yVelocity);

澳门新葡萄京官网注册 9

我们以mState = State.ToPre 为例子来说明

/**
 * mState = State.ToPre 时进行的动作
 * @param yVelocity 竖直方向的速度
 */
private void toPreAction(float yVelocity) {
    int startY;
    int delta;
    int duration;
    mState = State.ToPre;
    addPre();//增加新的页面
    //计算松手后滑动的item个数
    int flingSpeedCount= (yVelocity - standerSpeed) > 0 ? (int) (yVelocity - standerSpeed) : 0;
    addCount = flingSpeedCount/ flingSpeed + 1;
    //mScroller开始的坐标
    startY = getScrollY() + mHeight;
    setScrollY(startY);
    //mScroller 移动的距离
    delta = -(startY - mStartScreen * mHeight) - (addCount - 1) * mHeight;
    duration = (Math.abs(delta)) * 3;
    mScroller.startScroll(0, startY, 0, delta, duration);
    addCount--;
}

然后会进入addPre方法中

/**
 * 把最后一个item移动到第一个item位置
 */
private void addPre() {
    mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();
    int childCount = getChildCount();
    View view = getChildAt(childCount - 1);
    removeViewAt(childCount - 1);
    addView(view, 0);
    if (iStereoListener != null) {
        iStereoListener.toPre(mCurScreen);
    }
}

最后mScroller.startScroll(0, startY, 0, delta, duration); 开始执行。

执行的过程中会回调这个函数方法computeScroll

澳门新葡萄京官网注册 10

完成到这一步,我们的无限滑动滚动就算是完成了

3.2 实现3D切换效果。

正常情况下,我们自定义ViewGroup并不需要重写dispatchDraw 方法。
而这里我们则需要重写

 @Override
    protected void dispatchDraw(Canvas canvas) {
        if (!isAdding && isCan3D) {
            //当开启3D效果并且当前状态不属于 computeScroll中 addPre() 或者addNext()
            //如果不做这个判断,addPre() 或者addNext()时页面会进行闪动一下
            //我当时写的时候就被这个坑了,后来通过log判断,原来是computeScroll中的onlayout,和子Child的draw触发的顺序导致的。
            //知道原理的朋友希望可以告知下
            for (int i = 0; i < getChildCount(); i++) {
                drawScreen(canvas, i, getDrawingTime());
            }
        } else {
            isAdding = false;
            super.dispatchDraw(canvas);
        }
    }

好,我们来drawScreen这个方法

private void drawScreen(Canvas canvas, int i, long drawingTime) {
        int curScreenY = mHeight * i;
        //屏幕中不显示的部分不进行绘制
        if (getScrollY() + mHeight < curScreenY) {
            return;
        }
        if (curScreenY < getScrollY() - mHeight) {
            return;
        }
        float centerX = mWidth / 2;
        float centerY = (getScrollY() > curScreenY) ? curScreenY + mHeight : curScreenY;
        float degree = mAngle * (getScrollY() - curScreenY) / mHeight;
        if (degree > 90 || degree < -90) {
            return;
        }
        canvas.save();

        mCamera.save();
        mCamera.rotateX(degree);
        mCamera.getMatrix(mMatrix);
        mCamera.restore();

        mMatrix.preTranslate(-centerX, -centerY);
        mMatrix.postTranslate(centerX, centerY);
        canvas.concat(mMatrix);
        drawChild(canvas, getChildAt(i), drawingTime);
        canvas.restore();

    }

这里面的关键就在于

mCamera.rotateX(degree);
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);

对于Camera我们知道我们整个布局都是平铺的,为什么会产生3D的效果呢?原因就是这个Camera类,人如其名,它就相当于一个相机,它对物体进行拍照。我们把相机正对物体拍摄,拍摄出的效果就是平面的,当我们把相机旋转了90度再来拍摄原来物体,物体就相当于旋转了90度。
Camera拍摄完毕后,然后把拍摄的参数值传到Matrix中,Matrix再和Canvas绑定,由Canvas进行绘制。最终显示在屏幕中。

那么preTranslate,postTranslate又是怎么一回事呢?
很简单,我们知道坐标系是以(0,0)作为参照点的。现在我们对拍摄的对象进行的缩放变形操作是在物体的中心。我们需要把物体的中心先移动到(0,0)位置,最后再移动到物体原来中心位置即可。

具体的大家可以参考下这篇文章
 ( Android
postTranslate和preTranslate的理解)

不过对于Camera的坐标系我还有一点点疑问,我准备有机会写一篇关于Camera和Matrix文章。

3.3 滑动事件冲突的处理

完成上面两个步骤,那么我们就算Over了吗?

不!还有很重要的一点,就是事件冲突的处理。
举个例子:我们把手放到我们的容器上,系统怎么知道我们这个滑动事件是给容器还是要给容器的子类的呢?

(给容器自己,则进行滑动的操作,给容器的子类,则容器的子类可以进行点击事件的判断处理)

对于这种情况,我就很大度啦,全部交给容器子类处理!子类不要,OK,那容器你自己拿来玩吧。

————之所以不走寻常路:交给容器处理,容器不需要再交给子类

原因在于:容器拿到滑动事件只需要做滑动操作,而子类则不同,它有点击事件需要判断,一个容器有很多子类,而很多子类只有一个共同的容器,如果把控制权交给容器,那么容器怎么可能能够判断得出不同的子类到底需不需要这个滑动事件呢?所以,既然这么麻烦,那么统统交给子类处理。

交给子类处理,则容器中onInterceptTouchEvent需要做如下操作

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        }
        return true;
    }

而子类(用CustomEdittext为例)的dispatchTouchEvent需要做如下判断

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isContain(event)) {
                    //子类不需要,交给容器自己处理
                    getParent().requestDisallowInterceptTouchEvent(false);
                    setFocusable(false);
                } else {
                    //子类自己做操作
                    setFocusableInTouchMode(true);
                }
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return super.dispatchTouchEvent(event);
    }

在isContain中,我做的是点击的坐标是否在Edittext中,在则拦截,子类处理,不在,则交给父类容器

 private boolean isContain(MotionEvent event) {
        region.set(rect);
        if (region.contains((int) event.getX(), (int) event.getY())) {
            return true;
        }
        return false;
    }

当然交给子类这样也导致了一个问题,就是我如果需要给容器中的子类进行点击事件,则都需要自定义一个View(例如上面的CustomEdittext
继承自Edittext)。

例如我就自定义了三个View,不过还是很简单的,几分钟的事就搞定了(在自定义View中dispatchTouchEvent进行判断)。

具体的可以参考代码。

澳门新葡萄京官网注册 11

3.4 点击水纹波效果

细心的人会发现,我这里还有个RippleView。
没错这就是点击后有水纹波的效果。
Android本身可以在XML中用ripple实现,不过是Android
5.0以上,个人觉得兼容性不太好,就自己随便写了一个简易的,哈哈,效率不能保证,各位看客看看就好啦。

1.概述

回到正题,这次带来的效果,是一个Android 的3D立体旋转的效果。
当然灵感的来源,来自早些时间微博上看到的效果图。
澳门新葡萄京官网注册,非常酷有木有!作为程序猿我当然要把它加入我的下一个项目中啦!
原效果

这里写图片描述

我们实现的效果:

(为了更加可定制化,我在原图基础上新增了新的效果)

这里写图片描述

可以快速滚动,并且无限循环

这里写图片描述

这个是对一些参数的进行设定

这里写图片描述

对图片的包裹效果

这里写图片描述

因为本身继承自ViewGroup,所以基本控件都是可以包裹的

【本文简书地址:

4.应用

4.1 定义的方法

使用方法也和其他的没有什么区别,我这里自定义了几个方法,我这里说明下。

自定义的方法

setStartScreen(int startScreen) :设置第一页展示的页面 @param
startScreen (0,getChildCount-1)

setResistance(float resistance) : 设置滑动阻力 @param resistance
(0,…)

setInterpolator(Interpolator mInterpolator) :
设置滚动时interpolator插补器

setAngle(float mAngle):设置滚动时两个item的夹角度数 [0f,180f]

setCan3D(boolean can3D) : 是否开启3D效果

setItem(int itemId) : 跳转到指定的item @param itemId
[0,getChildCount-1]

toPre() : 上一页

toNext() : 下一页

定义的回调接口

澳门新葡萄京官网注册 12

4.2 使用方法

直接在布局中

澳门新葡萄京官网注册 13

在代码中

澳门新葡萄京官网注册 14

4.3 缺陷说明

目前容器的item数量需要大于等于3,小于3个滑动时会些问题。设置的最开始展示的item位置不能是第一个或者最后一个,这么做是为了保证第1个或者最后一个被隐藏,从而保证最开始向上滑动或者向下滑动时的正常。

2.分析

因为代码量有点大,感觉把代码全部粘贴上来也不现实。所以想了解我的思路的盆友可以先来这里下载代码。然后边看代码边看我的分析

下载地址
:https://github.com/ImmortalZ/StereoView

通过我们实现的效果图可以发现:

1.切换的时候是一个3D立体的效果

2.布局中的每一个Item可以自由切换,且无限循环滚动

要解决上面的效果,我们需要什么技术点呢?

1.要想实现一个3D效果,我们可以借助Android中的Camera、Matrix

2.要想实现滚动,毫无疑问,我们需要借助Scroller

当然一切看起来很简单,其实不然,除此之外,你还需要对于滑动冲突进行处理等等,下面我开始介绍啦。

这就是我们这次项目的大致

这里写图片描述

一、Camera与Matrix初步认识

android中一共有两个Camera,分别为:

android.graphics.Cameraandroid.hardware.Camera

今天我们要说的是第一个Camera,第二个主要应用在相机开发中。首先看下这个类的官方介绍:

A camera instance can be used to compute 3D transformations and
generate a matrix that can be applied, for instance, on
aCanvas.一个照相机实例可以被用于计算3D变换,生成一个可以被使用的Matrix矩阵,一个实例,用在画布上。

Camera内部机制实际上还是opengl,不过大大简化了使用。有了感性的认识之后,我们再来看下它的常用API定义:

Camera() 创建一个没有任何转换效果的新的Camera实例applyToCanvas(Canvas
canvas)
根据当前的变换计算出相应的矩阵,然后应用到制定的画布上getLocationX()
获取Camera的x坐标getLocationY() 获取Camera的y坐标getLocationZ()
获取Camera的z坐标getMatrix(Matrixmatrix)
获取转换效果后的Matrix对象restore() 恢复保存的状态rotate(float x,
float y, float z) 沿X、Y、Z坐标进行旋转rotateX(float deg)rotateY(float
deg)rotateZ(float deg)save() 保存状态setLocation(float x, float y,
float z)translate(float x, float y, float z)沿X、Y、Z轴进行平移

不得不说下Matrix,它是Android提供的一个矩阵工具类,是一个3×3的矩阵,一般要实现2D的旋转、缩放、平移、倾斜用这个作用于画布,这四种操作的内部实现过程都是通过matrix.setValues来设置矩阵的值来达到变换的效果。

setTranslate(floatdx,floatdy):控制Matrix进行平移setSkew(floatkx,floatky,floatpx,floatpy):控制Matrix以px,py为轴心进行倾斜,kx,ky为X,Y方向上的倾斜距离setRotate(floatdegress):控制Matrix进行旋转,degress控制旋转的角度setRorate(floatdegress,floatpx,floatpy):设置以px,py为轴心进行旋转,degress控制旋转角度setScale(floatsx,floatsy):设置Matrix进行缩放,sx,sy控制X,Y方向上的缩放比例setScale(floatsx,floatsy,floatpx,floatpy):设置Matrix以px,py为轴心进行缩放,sx,sy控制X,Y方向上的缩放比例

API提供了set、post和pre三种操作,下面这个重点看下,之后效果会用到

post是后乘,当前的矩阵乘以参数给出的矩阵。可以连续多次使用post,来完成所需的整个变换。pre是前乘,参数给出的矩阵乘以当前的矩阵。所以操作是在当前矩阵的最前面发生的。

好了,上面基本方法先简单了解下,现在让我们看看能做出什么效果,之后回头再重新看看会有更深的理解。

5.下载

如果觉得对你有帮助,欢迎 star,fork,如果对于我感兴趣,欢迎follow 我

下载地址 :

3.实现

因为我们是要打造一个容器类,所以肯定得继承自 ViewGroup

按照一般的思路,我们肯定是先要进行一些变量的申明,onMeasure,onLayout操作

private void init(Context context) {
    mCamera = new Camera();
    mMatrix = new Matrix();
    if (mScroller == null) {
        mScroller = new Scroller(context);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();
    //滑动到设置的StartScreen位置
    scrollTo(0, mStartScreen * mHeight);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childTop = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            child.layout(0, childTop,
                    child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
            childTop = childTop + child.getMeasuredHeight();
        }
    }
}

完成这些操作后,我们需要在onTouchEvent中进行滑动事件的处理

3.1 完成无限循环滑动滚动

我们的item数量是有限的,如何实现无限循环滚动呢?很简单,以3个item为例子(分别为1,2,3),我们让屏幕显示的是2

如此反复,屏幕所在的位置始终是第2个item所在的位置,这样就实现了我们的无限循环滚动,向下滚动也是如此

QQ截图20160715190642.png

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    //当上一次滑动没有结束时,再次点击,强制滑动在点击位置结束
                    mScroller.setFinalY(mScroller.getCurrY());
                    mScroller.abortAnimation();
                    scrollTo(0, getScrollY());
                }
                mDownY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int realDelta = (int) (mDownY - y);
                mDownY = y;
                if (mScroller.isFinished()) {
                    //因为要循环滚动
                    recycleMove(realDelta);
                }
                break;
            case MotionEvent.ACTION_UP:
                mVelocityTracker.computeCurrentVelocity(1000);
                float yVelocity = mVelocityTracker.getYVelocity();
                //滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为State.ToPre
                if (yVelocity > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)) {
                    mState = State.ToPre;
                } else if (yVelocity < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)) {
                    //滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为State.ToNext
                    mState = State.ToNext;
                } else {
                    mState = State.Normal;
                }
                //根据mState进行相应的变化
                changeByState(yVelocity);
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
        }
        //返回true,消耗点击事件
        return true;
    }

当手从屏幕上移开时,我们来看下这个方法changeByState(yVelocity);

这里写图片描述

我们以mState = State.ToPre 为例子来说明

/**
 * mState = State.ToPre 时进行的动作
 * @param yVelocity 竖直方向的速度
 */
private void toPreAction(float yVelocity) {
    int startY;
    int delta;
    int duration;
    mState = State.ToPre;
    addPre();//增加新的页面
    //计算松手后滑动的item个数
    int flingSpeedCount= (yVelocity - standerSpeed) > 0 ? (int) (yVelocity - standerSpeed) : 0;
    addCount = flingSpeedCount/ flingSpeed + 1;
    //mScroller开始的坐标
    startY = getScrollY() + mHeight;
    setScrollY(startY);
    //mScroller 移动的距离
    delta = -(startY - mStartScreen * mHeight) - (addCount - 1) * mHeight;
    duration = (Math.abs(delta)) * 3;
    mScroller.startScroll(0, startY, 0, delta, duration);
    addCount--;
}

然后会进入addPre方法中

/**
 * 把最后一个item移动到第一个item位置
 */
private void addPre() {
    mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();
    int childCount = getChildCount();
    View view = getChildAt(childCount - 1);
    removeViewAt(childCount - 1);
    addView(view, 0);
    if (iStereoListener != null) {
        iStereoListener.toPre(mCurScreen);
    }
}

最后mScroller.startScroll(0, startY, 0, delta, duration); 开始执行。
执行的过程中会回调这个函数方法computeScroll

这里写图片描述

完成到这一步,我们的无限滑动滚动就算是完成了

3.2 实现3D切换效果。

正常情况下,我们自定义ViewGroup并不需要重写dispatchDraw 方法。
而这里我们则需要重写

 @Override
    protected void dispatchDraw(Canvas canvas) {
        if (!isAdding && isCan3D) {
            //当开启3D效果并且当前状态不属于 computeScroll中 addPre() 或者addNext()
            //如果不做这个判断,addPre() 或者addNext()时页面会进行闪动一下
            //我当时写的时候就被这个坑了,后来通过log判断,原来是computeScroll中的onlayout,和子Child的draw触发的顺序导致的。
            //知道原理的朋友希望可以告知下
            for (int i = 0; i < getChildCount(); i++) {
                drawScreen(canvas, i, getDrawingTime());
            }
        } else {
            isAdding = false;
            super.dispatchDraw(canvas);
        }
    }

好,我们来drawScreen这个方法

private void drawScreen(Canvas canvas, int i, long drawingTime) {
        int curScreenY = mHeight * i;
        //屏幕中不显示的部分不进行绘制
        if (getScrollY() + mHeight < curScreenY) {
            return;
        }
        if (curScreenY < getScrollY() - mHeight) {
            return;
        }
        float centerX = mWidth / 2;
        float centerY = (getScrollY() > curScreenY) ? curScreenY + mHeight : curScreenY;
        float degree = mAngle * (getScrollY() - curScreenY) / mHeight;
        if (degree > 90 || degree < -90) {
            return;
        }
        canvas.save();

        mCamera.save();
        mCamera.rotateX(degree);
        mCamera.getMatrix(mMatrix);
        mCamera.restore();

        mMatrix.preTranslate(-centerX, -centerY);
        mMatrix.postTranslate(centerX, centerY);
        canvas.concat(mMatrix);
        drawChild(canvas, getChildAt(i), drawingTime);
        canvas.restore();

    }

这里面的关键就在于
mCamera.rotateX(degree);
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);

对于Camera我们知道我们整个布局都是平铺的,为什么会产生3D的效果呢?原因就是这个Camera类,人如其名,它就相当于一个相机,它对物体进行拍照。我们把相机正对物体拍摄,拍摄出的效果就是平面的,当我们把相机旋转了90度再来拍摄原来物体,物体就相当于旋转了90度。
Camera拍摄完毕后,然后把拍摄的参数值传到Matrix中,Matrix再和Canvas绑定,由Canvas进行绘制。最终显示在屏幕中。

那么preTranslate,postTranslate又是怎么一回事呢?
很简单,我们知道坐标系是以(0,0)作为参照点的。现在我们对拍摄的对象进行的缩放变形操作是在物体的中心。我们需要把物体的中心先移动到(0,0)位置,最后再移动到物体原来中心位置即可。

具体的大家可以参考下这篇文章
http://blog.csdn.net/rav009/article/details/7763223
( Android postTranslate和preTranslate的理解)

不过对于Camera的坐标系我还有一点点疑问,我准备有机会写一篇关于Camera和Matrix文章。

3.3 滑动事件冲突的处理(先看后面的更新说明)

完成上面两个步骤,那么我们就算Over了吗?

不!还有很重要的一点,就是事件冲突的处理。
举个例子:我们把手放到我们的容器上,系统怎么知道我们这个滑动事件是给容器还是要给容器的子类的呢?

(给容器自己,则进行滑动的操作,给容器的子类,则容器的子类可以进行点击事件的判断处理)

对于这种情况,我就很大度啦,全部交给容器子类处理!子类不要,OK,那容器你自己拿来玩吧。

————之所以不走寻常路:交给容器处理,容器不需要再交给子类

原因在于:容器拿到滑动事件只需要做滑动操作,而子类则不同,它有点击事件需要判断,一个容器有很多子类,而很多子类只有一个共同的容器,如果把控制权交给容器,那么容器怎么可能能够判断得出不同的子类到底需不需要这个滑动事件呢?所以,既然这么麻烦,那么统统交给子类处理。

交给子类处理,则容器中onInterceptTouchEvent需要做如下操作

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        }
        return true;
    }

而子类(用CustomEdittext为例)的dispatchTouchEvent需要做如下判断

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isContain(event)) {
                    //子类不需要,交给容器自己处理
                    getParent().requestDisallowInterceptTouchEvent(false);
                    setFocusable(false);
                } else {
                    //子类自己做操作
                    setFocusableInTouchMode(true);
                }
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return super.dispatchTouchEvent(event);
    }

在isContain中,我做的是点击的坐标是否在Edittext中,在则拦截,子类处理,不在,则交给父类容器

 private boolean isContain(MotionEvent event) {
        region.set(rect);
        if (region.contains((int) event.getX(), (int) event.getY())) {
            return true;
        }
        return false;
    }

当然交给子类这样也导致了一个问题,就是我如果需要给容器中的子类进行点击事件,则都需要自定义一个View(例如上面的CustomEdittext
继承自Edittext)。

例如我就自定义了三个View,不过还是很简单的,几分钟的事就搞定了(在自定义View中dispatchTouchEvent进行判断)。

具体的可以参考代码。

这里写图片描述

更新说明 2016/8/5

滑动冲突之前我是把控制权交给了子类,这里https://github.com/Y-bao
这位作者提交的pull
request中将事件冲突交给了父类(StereoView)
,我这边通过了pull,我觉得写得挺好的,把点击事件的控制权转移给父类,就不需要自定义View。
如果你还想查看控制权转移给子类的代码(我之前的),可以点击这里

3.4 点击水纹波效果

细心的人会发现,我这里还有个RippleView。
没错这就是点击后有水纹波的效果。
Android本身可以在XML中用ripple实现,不过是Android
5.0以上,个人觉得兼容性不太好,就自己随便写了一个简易的,哈哈,效率不能保证,各位看客看看就好啦。

二、Camera与Matrix旋转效果拆分介绍

Camera坐标系研究Camera的坐标系是左手坐标系。当手机平整的放在桌面上,X轴是手机的水平方向,Y轴是手机的竖直方向,Z轴是垂直于手机向里的那个方向。

澳门新葡萄京官网注册 15左手坐标系

camera位于坐标点,也就是视图的左上角;camera.translate(10,50,-180)的意思是把观察物体右移10,上移50,向-z轴移180(即让物体接近camera,这样物体将会变大);原图:

public class CameraTestView extends View{ private Camera camera; private Matrix matrix; private Paint paint; public CameraTestView(Context context, AttributeSet attrs) { super(context, attrs); camera = new Camera(); matrix = new Matrix(); setBackgroundColor(Color.parseColor("#3f51b5")); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Style.FILL); paint.setColor(Color.parseColor("#ff4081")); } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(60, 60, 60, paint); }}

<RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" tools:context=".MainActivity" > <com.example.matrixcamera.CameraTestView android:layout_width="200dp" android:layout_height="200dp" /></RelativeLayout>

澳门新葡萄京官网注册 16原图

 @Override protected void onDraw(Canvas canvas) { matrix.reset(); camera.save(); camera.translate(10, 50, -180); camera.getMatrix; camera.restore(); canvas.concat; canvas.drawCircle(60, 60, 60, paint); }

澳门新葡萄京官网注册 17translate(10,
50, -180)

camera.rotateX的意思是绕X轴顺时针旋转60度。举例来说,如果物体中间线和X轴重合的话,绕X轴顺时针旋转60度就是指物体上半部分向里翻转,下半部分向外翻转;

@Override protected void onDraw(Canvas canvas) { Options option = new Options(); option.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.drawable.aa,option); option.inSampleSize = calculateInSampleSize(option, getWidth()/2, getHeight; option.inJustDecodeBounds = false; canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.aa,option), matrix, paint); } private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } 

澳门新葡萄京官网注册 18未旋转图片

@Override protected void onDraw(Canvas canvas) { matrix.reset(); camera.save(); camera.rotateX; camera.getMatrix; camera.restore(); matrix.preTranslate(-getWidth()/2, -getHeight; matrix.postTranslate(getWidth()/2, getHeight; Options option = new Options(); option.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.drawable.aa,option); option.inSampleSize = calculateInSampleSize(option, getWidth()/2, getHeight; option.inJustDecodeBounds = false; canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.aa,option), matrix, paint); }

澳门新葡萄京官网注册 19绕X轴顺时针旋转60度

camera.rotateY的意思是绕Y轴顺时针旋转60度。举例来说,如果物体中间线和Y轴重合的话,绕Y轴顺时针旋转60度就是指物体左半部分向外翻转,右半部分向里翻转;

@Override protected void onDraw(Canvas canvas) { ... camera.rotateY; ... }

澳门新葡萄京官网注册 20绕Y轴顺时针旋转60度

camera.rotateZ的意思是绕Z轴逆时针旋转60度。举例来说,如果物体中间线和Z轴重合的话,绕Z轴顺时针旋转60度就是物体上半部分向左翻转,下半部分向右翻转。

@Override protected void onDraw(Canvas canvas) { ... camera.rotateZ; ... }

澳门新葡萄京官网注册 21绕Z轴逆时针旋转60度

注意:下面两行代码的意思是先将旋转中心移动到点,因为Matrix总是用0,0点作为旋转点,旋转之后将视图放回原来的位置。

matrix.preTranslate(-getWidth()/2,
-getHeight;matrix.postTranslate(getWidth()/2, getHeight;

API DEMOS中的例子,大家可以看下效果加深理解:

/** * An animation that rotates the view on the Y axis between two specified angles. * This animation also adds a translation on the Z axis  to improve the effect. */public class Rotate3dAnimation extends Animation { private final float mFromDegrees; private final float mToDegrees; private final float mCenterX; private final float mCenterY; private final float mDepthZ; private final boolean mReverse; private Camera mCamera; /** * Creates a new 3D rotation on the Y axis. The rotation is defined by its * start angle and its end angle. Both angles are in degrees. The rotation * is performed around a center point on the 2D space, definied by a pair * of X and Y coordinates, called centerX and centerY. When the animation * starts, a translation on the Z axis  is performed. The length * of the translation can be specified, as well as whether the translation * should be reversed in time. * * @param fromDegrees the start angle of the 3D rotation * @param toDegrees the end angle of the 3D rotation * @param centerX the X center of the 3D rotation * @param centerY the Y center of the 3D rotation * @param reverse true if the translation should be reversed, false otherwise */ public Rotate3dAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mCenterX = centerX; mCenterY = centerY; mDepthZ = depthZ; mReverse = reverse; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mCamera = new Camera(); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final float centerX = mCenterX; final float centerY = mCenterY; final Camera camera = mCamera; final Matrix matrix = t.getMatrix(); camera.save(); if  { camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); } else { camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); } camera.rotateY; camera.getMatrix; camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); }}

iv.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { int width = getWindowManager().getDefaultDisplay().getWidth(); int height = getWindowManager().getDefaultDisplay().getHeight(); Rotate3dAnimation animation = new Rotate3dAnimation(0, 360, width/2, height/2,0, true); animation.setInterpolator(new AccelerateDecelerateInterpolator; animation.setDuration; animation.setFillAfter; iv.startAnimation(animation); } });

4.应用

4.1 定义的方法

使用方法也和其他的没有什么区别,我这里自定义了几个方法,我这里说明下。

自定义的方法

setStartScreen(int startScreen) :设置第一页展示的页面 @param
startScreen (0,getChildCount-1)

setResistance(float resistance) : 设置滑动阻力 @param resistance
(0,…)

setInterpolator(Interpolator mInterpolator)
设置滚动时interpolator插补器

setAngle(float mAngle):设置滚动时两个item的夹角度数 [0f,180f]

setCan3D(boolean can3D) : 是否开启3D效果

setItem(int itemId) : 跳转到指定的item @param itemId
[0,getChildCount-1]

toPre() : 上一页

toNext() : 下一页

定义的回调接口

这里写图片描述

4.2 使用方法

直接在布局中

这里写图片描述

在代码中

这里写图片描述

4.3 缺陷说明

目前容器的item数量需要大于等于3,小于3个滑动时会些问题。设置的最开始展示的item位置不能是第一个或者最后一个,这么做是为了保证第1个或者最后一个被隐藏,从而保证最开始向上滑动或者向下滑动时的正常。

三、Camera与Matrix实现立体3D切换效果

最后我们要实现的效果(录得图像有点渣。。。):

澳门新葡萄京官网注册 223D切换

OK,有了前面的铺垫,我们开始实现下这个效果吧。

我们分三步来实现下:1、首先,初始化控件,进行测量和布局。这里我们整个容器继承自ViewGroup,来看看吧,初始化Camera和Matrix,因为涉及到滚动,我们用个辅助工具Scroller:

 private void init() { mCamera = new Camera(); mMatrix = new Matrix(); if (mScroller == null){ mScroller = new Scroller(getContext(),new LinearInterpolator; } }

测量控件自身以及子控件:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(measureWidth,measureHeight); MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( measureWidth- (params.leftMargin + params.rightMargin), MeasureSpec.EXACTLY); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( measureHeight - (params.topMargin + params.bottomMargin), MeasureSpec.EXACTLY); measureChildren(childWidthMeasureSpec,childHeightMeasureSpec); mHeight = getMeasuredHeight(); scrollTo(0,mStartScreen * mHeight); }

布局子控件:

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams(); int childTop = 0; for (int i = 0; i <getChildCount { View child = getChildAt; if (child.getVisibility() != GONE){ if { childTop+=params.topMargin; } child.layout(params.leftMargin, childTop, child.getMeasuredWidth() + params.leftMargin, childTop + child.getMeasuredHeight; childTop = childTop + child.getMeasuredHeight(); } } }

到这里,我们初始化的过程就完成了,各个子控件从上到下依次排列,而整个控件大小是一定的,界面上也就只显示一个子控件,在整个控件滚动的时候形成界面切换效果。2、重写dispatchDraw方法,实现3D界面切换效果在dispatchDraw方法中,重新对各个子控件用Camera和Matrix进行矩阵转换,以此在滚动中实现立体效果,这也是我们今天要了解的重点,根据我们之前了解的,我们将Camera沿着X轴进行一定的角度旋转,两个控件在滚动过程中就会形成一个夹角,从而出现立体效果,当然,一定要注意的是要将控件中心点移至0,0点,否则会看不到效果:

 @Override protected void dispatchDraw(Canvas canvas) { for (int i = 0;i<getChildCount{ drawScreen(canvas,i,getDrawingTime; } }private void drawScreen(Canvas canvas, int screen, long drawingTime) { // 得到当前子View的高度 final int height = getHeight(); final int scrollHeight = screen * height; final int scrollY = this.getScrollY(); // 偏移量不足的时 if (scrollHeight > scrollY + height || scrollHeight + height < scrollY) { return; } final View child = getChildAt; final int faceIndex = screen; final float currentDegree = getScrollY() * (angle / getMeasuredHeight; final float faceDegree = currentDegree - faceIndex * angle; if (faceDegree > 90 || faceDegree < -90) { return; } final float centerY = (scrollHeight < scrollY) ? scrollHeight + height : scrollHeight; final float centerX = getWidth() / 2; final Camera camera = mCamera; final Matrix matrix = mMatrix; canvas.save(); camera.save(); camera.rotateX(faceDegree); camera.getMatrix; camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); canvas.concat; drawChild(canvas, child, drawingTime); canvas.restore(); }

3、重写onInterceptTouchEvent和onTouchEvent方法实现手指滑动界面切换

在onTouchEvent方法中,根据手指移动的距离,调用ScrollBy()方法进行持续的滚动,在手指抬起的时候,判断当前的速率,如果大于一定值或超过子控件的1/2时,转换当前状态进行界面切换,否则回滚回当前页面。这里在onInterceptTouchEvent简单的拦截了当前事件,而如果我们需要子控件处理事件时还需要进行处理。

@Override public boolean onTouchEvent(MotionEvent event) { if (mTracker == null){ mTracker = VelocityTracker.obtain(); } mTracker.addMovement; float y = event.getY(); switch (event.getAction{ case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished{ mScroller.setFinalY(mScroller.getCurrY; mScroller.abortAnimation(); scrollTo(0,getScrollY; } mDownY = y; break; case MotionEvent.ACTION_MOVE: int disY =  (mDownY - y); mDownY = y; if (mScroller.isFinished{ recycleMove; } break; case MotionEvent.ACTION_UP: mTracker.computeCurrentVelocity; float velocitY = mTracker.getYVelocity(); //滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为STATE_PRE if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){ STATE = STATE_PRE; }else if(velocitY < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){ //滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为STATE_NEXT STATE = STATE_NEXT; }else{ STATE = STATE_NORMAL; } //根据STATE进行相应的变化 changeByState(); if (mTracker != null){ mTracker.recycle(); mTracker = null; } break; } //返回true,消耗点击事件 return true; }

5.下载

如果觉得对你有帮助,欢迎 star,fork,如果对于我感兴趣,欢迎follow 我

下载地址
:https://github.com/ImmortalZ/StereoView

参考文章:

http://blog.csdn.net/dawanganban/article/details/38421221

http://blog.csdn.net/rav009/article/details/7763223

四、最后,附上源码

public class Custom3DView extends ViewGroup{ private Camera mCamera; private Matrix mMatrix; private int mStartScreen = 1;//开始时的item位置 private float mDownY = 0; private static final int standerSpeed = 2000; private int mCurScreen = 1;//当前item的位置 private int mHeight = 0;//控件的高 private VelocityTracker mTracker; private Scroller mScroller; // 旋转的角度,可以进行修改来观察效果 private float angle = 90; //三种状态 private static final int STATE_PRE = 0; private static final int STATE_NEXT = 1; private static final int STATE_NORMAL = 2; private int STATE = -1; private float resistance = 1.6f;//滑动阻力 public Custom3DView(Context context) { this(context,null); } public Custom3DView(Context context, AttributeSet attrs) { this(context, attrs,0); } public Custom3DView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mCamera = new Camera(); mMatrix = new Matrix(); if (mScroller == null){ mScroller = new Scroller(getContext(),new LinearInterpolator; } } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams; } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(measureWidth,measureHeight); MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( measureWidth- (params.leftMargin + params.rightMargin), MeasureSpec.EXACTLY); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( measureHeight - (params.topMargin + params.bottomMargin), MeasureSpec.EXACTLY); measureChildren(childWidthMeasureSpec,childHeightMeasureSpec); mHeight = getMeasuredHeight(); scrollTo(0,mStartScreen * mHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams(); int childTop = 0; for (int i = 0; i <getChildCount { View child = getChildAt; if (child.getVisibility() != GONE){ if { childTop+=params.topMargin; } child.layout(params.leftMargin, childTop, child.getMeasuredWidth() + params.leftMargin, childTop + child.getMeasuredHeight; childTop = childTop + child.getMeasuredHeight(); } } } @Override protected void dispatchDraw(Canvas canvas) { for (int i = 0;i<getChildCount{ drawScreen(canvas,i,getDrawingTime; } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction{ case MotionEvent.ACTION_DOWN: return false; } return true; } @Override public boolean onTouchEvent(MotionEvent event) { if (mTracker == null){ mTracker = VelocityTracker.obtain(); } mTracker.addMovement; float y = event.getY(); switch (event.getAction{ case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished{ mScroller.setFinalY(mScroller.getCurrY; mScroller.abortAnimation(); scrollTo(0,getScrollY; } mDownY = y; break; case MotionEvent.ACTION_MOVE: int disY =  (mDownY - y); mDownY = y; if (mScroller.isFinished{ recycleMove; } break; case MotionEvent.ACTION_UP: mTracker.computeCurrentVelocity; float velocitY = mTracker.getYVelocity(); //滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为STATE_PRE if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){ STATE = STATE_PRE; }else if(velocitY < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){ //滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为STATE_NEXT STATE = STATE_NEXT; }else{ STATE = STATE_NORMAL; } //根据STATE进行相应的变化 changeByState(); if (mTracker != null){ mTracker.recycle(); mTracker = null; } break; } //返回true,消耗点击事件 return true; } private void changeByState() { switch  { case STATE_NORMAL: toNormalAction(); break; case STATE_PRE: toPrePager(); break; case STATE_NEXT: toNextPager(); break; } invalidate(); } /** * 当前页 */ private void toNormalAction() { int startY; int delta; int duration; STATE = STATE_NORMAL; startY = getScrollY(); delta = mHeight * mStartScreen - getScrollY(); duration = (Math.abs * 4; mScroller.startScroll(0, startY, 0, delta, duration); } /** * 上一页 */ private void toPrePager() { int startY; int delta; int duration; STATE = STATE_PRE; //增加新的页面 setPre(); //mScroller开始的坐标 startY = getScrollY() + mHeight; setScrollY; //mScroller移动的距离 delta = -(startY - mStartScreen * mHeight); duration = (Math.abs * 2; mScroller.startScroll(0, startY, 0, delta, duration); } /** * 下一页 */ private void toNextPager() { int startY; int delta; int duration; STATE = STATE_NEXT; setNext(); startY = getScrollY() - mHeight; setScrollY; delta = mHeight * mStartScreen - startY; duration = (Math.abs * 2; mScroller.startScroll(0, startY, 0, delta, duration); } private void setNext(){ mCurScreen = (mCurScreen + 1) % getChildCount(); int childCount = getChildCount(); View view = getChildAt; removeViewAt; addView(view, childCount - 1); } private void setPre(){ mCurScreen = ((mCurScreen - 1) + getChildCount % getChildCount(); int childCount = getChildCount(); View view = getChildAt(childCount - 1); removeViewAt(childCount - 1); addView; } private void recycleMove(int delta) { delta = delta % mHeight; delta =  (delta / resistance); if (Math.abs > mHeight / 4) { return; } if (getScrollY() <= 0 && mCurScreen <= 0 && delta<=0){ return; } if (mHeight*mCurScreen <= getScrollY() && mCurScreen == getChildCount()-1 && delta>= 0){ return; } scrollBy; if (getScrollY() < 8 && mCurScreen != 0) { setPre(); scrollBy(0, mHeight); } else if (getScrollY() > (getChildCount * mHeight - 8) { setNext(); scrollBy(0, -mHeight); } } @Override public void computeScroll() { if (mScroller.computeScrollOffset { scrollTo(mScroller.getCurrX(), mScroller.getCurrY; postInvalidate(); } } /** * 画单个页面 * @param canvas * @param screen * @param drawingTime */ private void drawScreen(Canvas canvas, int screen, long drawingTime) { // 得到当前子View的高度 final int height = getHeight(); final int scrollHeight = screen * height; final int scrollY = this.getScrollY(); // 偏移量不足的时 if (scrollHeight > scrollY + height || scrollHeight + height < scrollY) { return; } final View child = getChildAt; final int faceIndex = screen; final float currentDegree = getScrollY() * (angle / getMeasuredHeight; final float faceDegree = currentDegree - faceIndex * angle; if (faceDegree > 90 || faceDegree < -90) { return; } final float centerY = (scrollHeight < scrollY) ? scrollHeight + height : scrollHeight; final float centerX = getWidth() / 2; final Camera camera = mCamera; final Matrix matrix = mMatrix; canvas.save(); camera.save(); camera.rotateX(faceDegree); camera.getMatrix; camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); canvas.concat; drawChild(canvas, child, drawingTime); canvas.restore(); }}

 <com.example.matrixcamera.Custom3DView android:layout_height="250dp" android:layout_width="250dp" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_centerInParent="true" > <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/aa" /> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/bb" /> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/cc" /> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/dd" /> </com.example.matrixcamera.Custom3DView>

发表评论

电子邮件地址不会被公开。 必填项已用*标注