Android中利用Camera與Matrix實現(xiàn)3D效果詳解

Camera

本文行文目錄:
一、Camera與Matrix初步認識
二励翼、Camera與Matrix旋轉(zhuǎn)效果拆分介紹
三、Camera與Matrix實現(xiàn)立體3D切換效果

【本文簡書地址:http://www.reibang.com/p/34e0fe5f9e31

一、Camera與Matrix初步認識

android中一共有兩個Camera,分別為:

android.graphics.Camera
android.hardware.Camera

今天我們要說的是第一個Camera,第二個主要應用在相機開發(fā)中状土。
首先看下這個類的官方介紹:

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內(nèi)部機制實際上還是opengl训桶,不過大大簡化了使用彼乌。有了感性的認識之后泻肯,我們再來看下它的常用API定義:

Camera() 創(chuàng)建一個沒有任何轉(zhuǎn)換效果的新的Camera實例
applyToCanvas(Canvas canvas) 根據(jù)當前的變換計算出相應的矩陣,然后應用到制定的畫布上
getLocationX() 獲取Camera的x坐標
getLocationY() 獲取Camera的y坐標
getLocationZ() 獲取Camera的z坐標
getMatrix(Matrixmatrix) 獲取轉(zhuǎn)換效果后的Matrix對象
restore() 恢復保存的狀態(tài)
rotate(float x, float y, float z) 沿X慰照、Y灶挟、Z坐標進行旋轉(zhuǎn)
rotateX(float deg)
rotateY(float deg)
rotateZ(float deg)
save() 保存狀態(tài)
setLocation(float x, float y, float z)
translate(float x, float y, float z)沿X、Y毒租、Z軸進行平移

不得不說下Matrix稚铣,它是Android提供的一個矩陣工具類,是一個3x3的矩陣墅垮,一般要實現(xiàn)2D的旋轉(zhuǎn)(繞z軸旋轉(zhuǎn))惕医、縮放、平移算色、傾斜用這個作用于畫布抬伺,這四種操作的內(nèi)部實現(xiàn)過程都是通過matrix.setValues(…)來設(shè)置矩陣的值來達到變換的效果。

setTranslate(floatdx,floatdy):控制Matrix進行平移
setSkew(floatkx,floatky,floatpx,floatpy):控制Matrix以px,py為軸心進行傾斜灾梦,kx,ky為X,Y方向上的傾斜距離
setRotate(floatdegress):控制Matrix進行旋轉(zhuǎn)峡钓,degress控制旋轉(zhuǎn)的角度
setRorate(floatdegress,floatpx,floatpy):設(shè)置以px,py為軸心進行旋轉(zhuǎn),degress控制旋轉(zhuǎn)角度
setScale(floatsx,floatsy):設(shè)置Matrix進行縮放若河,sx,sy控制X,Y方向上的縮放比例
setScale(floatsx,floatsy,floatpx,floatpy):設(shè)置Matrix以px,py為軸心進行縮放能岩,sx,sy控制X,Y方向上的縮放比例

API提供了set、post和pre三種操作萧福,下面這個重點看下,之后效果會用到

post是后乘拉鹃,當前的矩陣乘以參數(shù)給出的矩陣■耆蹋可以連續(xù)多次使用post膏燕,來完成所需的整個變換。
pre是前乘悟民,參數(shù)給出的矩陣乘以當前的矩陣煌寇。所以操作是在當前矩陣的最前面發(fā)生的。

好了逾雄,上面基本方法先簡單了解下阀溶,現(xiàn)在讓我們看看能做出什么效果,之后回頭再重新看看會有更深的理解鸦泳。

二银锻、Camera與Matrix旋轉(zhuǎn)效果拆分介紹

Camera坐標系研究
Camera的坐標系是左手坐標系。當手機平整的放在桌面上做鹰,X軸是手機的水平方向击纬,Y軸是手機的豎直方向,Z軸是垂直于手機向里的那個方向钾麸。

左手坐標系

camera位于坐標點(0,0)更振,也就是視圖的左上角炕桨;
camera.translate(10,50,-180)的意思是把觀察物體右移(+x)10,上移(+y)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:android="http://schemas.android.com/apk/res/android"
    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>
原圖
    @Override
    protected void onDraw(Canvas canvas) {
        matrix.reset();
        camera.save();
        camera.translate(10, 50, -180);
        camera.getMatrix(matrix);
        camera.restore();
        canvas.concat(matrix);
        
        canvas.drawCircle(60, 60, 60, paint);
    }
translate(10, 50, -180)

camera.rotateX(60)的意思是繞X軸順時針旋轉(zhuǎn)60度实撒。舉例來說姊途,如果物體中間線和X軸重合的話,繞X軸順時針旋轉(zhuǎn)60度就是指物體上半部分向里翻轉(zhuǎn)知态,下半部分向外翻轉(zhuǎn)捷兰;

@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()/2);
        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;  
    } 
未旋轉(zhuǎn)圖片
@Override
    protected void onDraw(Canvas canvas) {
        matrix.reset();
        camera.save();
        camera.rotateX(60);
        camera.getMatrix(matrix);
        camera.restore();
        
        matrix.preTranslate(-getWidth()/2, -getHeight()/2);
        matrix.postTranslate(getWidth()/2, getHeight()/2);

        Options option = new Options();
        option.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.aa,option);
        option.inSampleSize = calculateInSampleSize(option, getWidth()/2, getHeight()/2);
        option.inJustDecodeBounds = false;
        canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.aa,option), matrix, paint);
    }
繞X軸順時針旋轉(zhuǎn)60度

camera.rotateY(60)的意思是繞Y軸順時針旋轉(zhuǎn)60度。舉例來說负敏,如果物體中間線和Y軸重合的話贡茅,繞Y軸順時針旋轉(zhuǎn)60度就是指物體左半部分向外翻轉(zhuǎn),右半部分向里翻轉(zhuǎn)其做;

@Override
    protected void onDraw(Canvas canvas) {
         ...
                  camera.rotateY(60);
                ...
    }
繞Y軸順時針旋轉(zhuǎn)60度

camera.rotateZ(60)的意思是繞Z軸逆時針旋轉(zhuǎn)60度顶考。舉例來說,如果物體中間線和Z軸重合的話庶柿,繞Z軸順時針旋轉(zhuǎn)60度就是物體上半部分向左翻轉(zhuǎn)村怪,下半部分向右翻轉(zhuǎn)秽浇。

@Override
    protected void onDraw(Canvas canvas) {
                ...
                camera.rotateZ(60);
                ...
        }
繞Z軸逆時針旋轉(zhuǎn)60度

注意:下面兩行代碼的意思是先將旋轉(zhuǎn)中心移動到(0,0)點浮庐,因為Matrix總是用0,0點作為旋轉(zhuǎn)點,旋轉(zhuǎn)之后將視圖放回原來的位置柬焕。

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

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 (depth) 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 (depth) 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 (mReverse) {
    camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
 } else {
    camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));

 }

 camera.rotateY(degrees);

 camera.getMatrix(matrix);

 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(2000);
                animation.setFillAfter(true);
                iv.startAnimation(animation);
            }
        });

三、Camera與Matrix實現(xiàn)立體3D切換效果

最后我們要實現(xiàn)的效果(錄得圖像有點渣斑举。搅轿。。):

3D切換

OK,有了前面的鋪墊富玷,我們開始實現(xiàn)下這個效果吧璧坟。

我們分三步來實現(xiàn)下:
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() ; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE){
                if (i==0){
                    childTop+=params.topMargin;
                }
                child.layout(params.leftMargin, childTop,
                        child.getMeasuredWidth() + params.leftMargin, childTop + child.getMeasuredHeight());
                childTop = childTop + child.getMeasuredHeight();
            }
        }
    }

到這里当悔,我們初始化的過程就完成了傅瞻,各個子控件從上到下依次排列踢代,而整個控件大小是一定的,界面上也就只顯示一個子控件嗅骄,在整個控件滾動的時候形成界面切換效果胳挎。
2、重寫dispatchDraw方法掸读,實現(xiàn)3D界面切換效果
在dispatchDraw方法中串远,重新對各個子控件用Camera和Matrix進行矩陣轉(zhuǎn)換,以此在滾動中實現(xiàn)立體效果儿惫,這也是我們今天要了解的重點澡罚,根據(jù)我們之前了解的,我們將Camera沿著X軸進行一定的角度旋轉(zhuǎn)肾请,兩個控件在滾動過程中就會形成一個夾角留搔,從而出現(xiàn)立體效果,當然铛铁,一定要注意的是要將控件中心點移至0,0點隔显,否則會看不到效果:

  @Override
    protected void dispatchDraw(Canvas canvas) {
            for (int i = 0;i<getChildCount();i++){
                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(screen);
        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(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
        canvas.concat(matrix);
        drawChild(canvas, child, drawingTime);
        canvas.restore();
    }

3、重寫onInterceptTouchEvent和onTouchEvent方法實現(xiàn)手指滑動界面切換

在onTouchEvent方法中饵逐,根據(jù)手指移動的距離括眠,調(diào)用ScrollBy()方法進行持續(xù)的滾動,在手指抬起的時候倍权,判斷當前的速率掷豺,如果大于一定值或超過子控件的1/2時,轉(zhuǎn)換當前狀態(tài)進行界面切換薄声,否則回滾回當前頁面当船。這里在onInterceptTouchEvent簡單的攔截了當前事件,而如果我們需要子控件處理事件時還需要進行處理默辨。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mTracker == null){
            mTracker = VelocityTracker.obtain();
        }
        mTracker.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 disY  = (int) (mDownY - y);
                mDownY = y;
                if (mScroller.isFinished()){
                    recycleMove(disY);            
                }            
                break;

            case MotionEvent.ACTION_UP:
                mTracker.computeCurrentVelocity(1000);

                float velocitY = mTracker.getYVelocity();
                //滑動的速度大于規(guī)定的速度德频,或者向上滑動時,上一頁頁面展現(xiàn)出的高度超過1/2缩幸。則設(shè)定狀態(tài)為STATE_PRE
                if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){
                    STATE =  STATE_PRE;
                }else if(velocitY < -standerSpeed  || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){
                     //滑動的速度大于規(guī)定的速度壹置,或者向下滑動時,下一頁頁面展現(xiàn)出的高度超過1/2表谊。則設(shè)定狀態(tài)為STATE_NEXT
                    STATE =  STATE_NEXT;
                }else{
                    STATE =  STATE_NORMAL;
                }
              //根據(jù)STATE進行相應的變化
                changeByState();
                if (mTracker != null){
                    mTracker.recycle();
                    mTracker = null;
                }
                break;
        }
        //返回true,消耗點擊事件
        return true;
    }

四钞护、最后,附上源碼

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;
    // 旋轉(zhuǎn)的角度铃肯,可以進行修改來觀察效果
    private float angle = 90;
    //三種狀態(tài)
    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(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }


    @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() ; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE){
                if (i==0){
                    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();i++){
                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(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 disY  = (int) (mDownY - y);
                mDownY = y;
                if (mScroller.isFinished()){
                    recycleMove(disY);            
                }            
                break;

            case MotionEvent.ACTION_UP:
                mTracker.computeCurrentVelocity(1000);

                float velocitY = mTracker.getYVelocity();
                //滑動的速度大于規(guī)定的速度患亿,或者向上滑動時,上一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_PRE
                if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){
                    STATE =  STATE_PRE;
                }else if(velocitY < -standerSpeed  || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){
                     //滑動的速度大于規(guī)定的速度步藕,或者向下滑動時惦界,下一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_NEXT
                    STATE =  STATE_NEXT;
                }else{
                    STATE =  STATE_NORMAL;
                }
              //根據(jù)STATE進行相應的變化
                changeByState();
                if (mTracker != null){
                    mTracker.recycle();
                    mTracker = null;
                }
                break;
        }
        //返回true,消耗點擊事件
        return true;
    }
      private void changeByState() {     
                    switch (STATE) {
                        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(delta)) * 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(startY);
        //mScroller移動的距離
        delta = -(startY - mStartScreen * mHeight);
        duration = (Math.abs(delta)) * 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(startY);
        delta = mHeight * mStartScreen - startY;
        duration = (Math.abs(delta)) * 2;
        mScroller.startScroll(0, startY, 0, delta, duration);
    }
    private void setNext(){
        mCurScreen = (mCurScreen + 1) % getChildCount();

        int childCount = getChildCount();
        View view = getChildAt(0);
        removeViewAt(0);
        addView(view, childCount - 1);
    }

    private void setPre(){
        mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();

        int childCount = getChildCount();
        View view = getChildAt(childCount - 1);
        removeViewAt(childCount - 1);
        addView(view, 0);
    }
    private void recycleMove(int delta) {
        delta = delta % mHeight;
        delta = (int) (delta / resistance);
        if (Math.abs(delta) > mHeight / 4) {
            return;
        }
        if (getScrollY() <= 0 && mCurScreen <= 0  && delta<=0){
            return;
        }
        if (mHeight*mCurScreen <= getScrollY()  && mCurScreen == getChildCount()-1 && delta>= 0){
            return;
        }
        scrollBy(0, delta);

        if (getScrollY() < 8 && mCurScreen != 0) {
            setPre();
            scrollBy(0, mHeight);
        } else if (getScrollY() > (getChildCount() - 1) * 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(screen);
        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(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
        canvas.concat(matrix);
        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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咙冗,一起剝皮案震驚了整個濱河市沾歪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雾消,老刑警劉巖灾搏,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異立润,居然都是意外死亡狂窑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門桑腮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泉哈,“玉大人,你說我怎么就攤上這事破讨〈曰蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵提陶,是天一觀的道長烫沙。 經(jīng)常有香客問我,道長隙笆,這世上最難降的妖魔是什么锌蓄? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮仲器,結(jié)果婚禮上煤率,老公的妹妹穿的比我還像新娘仰冠。我一直安慰自己乏冀,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布洋只。 她就那樣靜靜地躺著辆沦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪识虚。 梳的紋絲不亂的頭發(fā)上肢扯,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音担锤,去河邊找鬼蔚晨。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的铭腕。 我是一名探鬼主播银择,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼累舷!你這毒婦竟也來了浩考?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤被盈,失蹤者是張志新(化名)和其女友劉穎析孽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體只怎,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡袜瞬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了身堡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吞滞。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盾沫,靈堂內(nèi)的尸體忽然破棺而出裁赠,到底是詐尸還是另有隱情,我是刑警寧澤赴精,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布佩捞,位于F島的核電站,受9級特大地震影響蕾哟,放射性物質(zhì)發(fā)生泄漏一忱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一谭确、第九天 我趴在偏房一處隱蔽的房頂上張望帘营。 院中可真熱鬧,春花似錦逐哈、人聲如沸芬迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禀梳。三九已至,卻和暖如春肠骆,著一層夾襖步出監(jiān)牢的瞬間算途,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工蚀腿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘴瓤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像廓脆,于是被迫代替她去往敵國和親畏浆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 前段時間讀到一篇文章狞贱,作者通過自定義View實現(xiàn)了一個高仿小米時鐘刻获,其中的3D效果很是吸引我,于是抽時間學習...
    實例波閱讀 20,754評論 1 43
  • 1瞎嬉、引子 筆者剛開始工作時蝎毡,做的第一個模塊是手機中的launcher,launcher可自由選擇滑屏效果氧枣,甚至還有...
    某昆閱讀 3,824評論 0 6
  • 1 前言 OpenGL渲染3D模型離不開空間幾何的數(shù)學理論知識沐兵,而本篇文章的目的就是對空間幾何進行簡單的介紹,并對...
    RichardJieChen閱讀 6,935評論 1 11
  • CSDN博客 img cquwentao android matrix 最全方法詳解與進階(完整篇) 發(fā)表于201...
    北風知我意閱讀 4,798評論 0 0
  • 今日有幸在群里遇見了一位少年便监,風華正茂意氣風發(fā)扎谎,頗有幾分我年少時的神色。無奈其一進群就指點江山烧董、激昂文字毁靶,多少有些...
    化濁閱讀 607評論 3 1