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

Camera

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

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

一、Camera與Matrix初步認(rèn)識

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

android.graphics.Camera
android.hardware.Camera

今天我們要說的是第一個Camera,第二個主要應(yīng)用在相機開發(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,不過大大簡化了使用。有了感性的認(rèn)識之后,我們再來看下它的常用API定義:

Camera() 創(chuàng)建一個沒有任何轉(zhuǎn)換效果的新的Camera實例
applyToCanvas(Canvas canvas) 根據(jù)當(dāng)前的變換計算出相應(yīng)的矩陣,然后應(yīng)用到制定的畫布上
getLocationX() 獲取Camera的x坐標(biāo)
getLocationY() 獲取Camera的y坐標(biāo)
getLocationZ() 獲取Camera的z坐標(biāo)
getMatrix(Matrixmatrix) 獲取轉(zhuǎn)換效果后的Matrix對象
restore() 恢復(fù)保存的狀態(tài)
rotate(float x, float y, float z) 沿X、Y、Z坐標(biāo)進行旋轉(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是后乘,當(dāng)前的矩陣乘以參數(shù)給出的矩陣。可以連續(xù)多次使用post,來完成所需的整個變換。
pre是前乘,參數(shù)給出的矩陣乘以當(dāng)前的矩陣。所以操作是在當(dāng)前矩陣的最前面發(fā)生的。

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

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

Camera坐標(biāo)系研究
Camera的坐標(biāo)系是左手坐標(biāo)系。當(dāng)手機平整的放在桌面上,X軸是手機的水平方向,Y軸是手機的豎直方向,Z軸是垂直于手機向里的那個方向。

左手坐標(biāo)系

camera位于坐標(biāo)點(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)立體效果,當(dāng)然,一定要注意的是要將控件中心點移至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) {
        // 得到當(dāng)前子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ù)的滾動,在手指抬起的時候,判斷當(dāng)前的速率,如果大于一定值或超過子控件的1/2時,轉(zhuǎn)換當(dāng)前狀態(tài)進行界面切換,否則回滾回當(dāng)前頁面。這里在onInterceptTouchEvent簡單的攔截了當(dāng)前事件,而如果我們需要子控件處理事件時還需要進行處理。

@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進行相應(yīng)的變化
                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;//當(dāng)前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進行相應(yīng)的變化
                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();
    }
    /**
     * 當(dāng)前頁
     */
    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開始的坐標(biāo)
        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) {
        // 得到當(dāng)前子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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,703評論 2 380

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

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