1. 程式人生 > >Android中利用Camera與Matrix實現3D效果詳解

Android中利用Camera與Matrix實現3D效果詳解

Camera

本文行文目錄:
一、Camera與Matrix初步認識
二、Camera與Matrix旋轉效果拆分介紹
三、Camera與Matrix實現立體3D切換效果

一、Camera與Matrix初步認識

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

android.graphics.Camera
android.hardware.Camera

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

A camera instance can be used to compute 3D transformations and generate a matrix that can be applied, for instance, on a

Canvas.
一個照相機例項可以被用於計算3D變換,生成一個可以被使用的Matrix矩陣,一個例項,用在畫布上。

Camera內部機制實際上還是opengl,不過大大簡化了使用。有了感性的認識之後,我們再來看下它的常用API定義:

Camera() 建立一個沒有任何轉換效果的新的Camera例項
applyToCanvas(Canvas canvas) 根據當前的變換計算出相應的矩陣,然後應用到制定的畫布上
getLocationX() 獲取Camera的x座標
getLocationY() 獲取Camera的y座標
getLocationZ() 獲取Camera的z座標
getMatrix(Matrix matrix) 獲取轉換效果後的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提供的一個矩陣工具類,是一個3x3的矩陣,一般要實現2D的旋轉(繞z軸旋轉)、縮放、平移、傾斜用這個作用於畫布,這四種操作的內部實現過程都是通過matrix.setValues(…)來設定矩陣的值來達到變換的效果。

setTranslate(float dx,float dy):控制Matrix進行平移
setSkew(float kx,float ky,float px,float py):控制Matrix以px,py為軸心進行傾斜,kx,ky為X,Y方向上的傾斜距離
setRotate(float degress):控制Matrix進行旋轉,degress控制旋轉的角度
setRorate(float degress,float px,float py):設定以px,py為軸心進行旋轉,degress控制旋轉角度
setScale(float sx,float sy):設定Matrix進行縮放,sx,sy控制X,Y方向上的縮放比例
setScale(float sx,float sy,float px,float py):設定Matrix以px,py為軸心進行縮放,sx,sy控制X,Y方向上的縮放比例

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

post是後乘,當前的矩陣乘以引數給出的矩陣。可以連續多次使用post,來完成所需的整個變換。
pre是前乘,引數給出的矩陣乘以當前的矩陣。所以操作是在當前矩陣的最前面發生的。

好了,上面基本方法先簡單瞭解下,現在讓我們看看能做出什麼效果,之後回頭再重新看看會有更深的理解。

二、Camera與Matrix旋轉效果拆分介紹

Camera座標系研究
Camera的座標系是左手座標系。當手機平整的放在桌面上,X軸是手機的水平方向,Y軸是手機的豎直方向,Z軸是垂直於手機向裡的那個方向。

左手座標系

camera位於座標點(0,0),也就是檢視的左上角;
camera.translate(10,50,-180)的意思是把觀察物體右移(+x)10,上移(+z)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軸順時針旋轉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()/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;  
    } 

未旋轉圖片

@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軸順時針旋轉60度
camera.rotateY(60)的意思是繞Y軸順時針旋轉60度。舉例來說,如果物體中間線和Y軸重合的話,繞Y軸順時針旋轉60度就是指物體左半部分向外翻轉,右半部分向裡翻轉;

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

繞Y軸順時針旋轉60度

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

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

繞Z軸逆時針旋轉60度
注意:下面兩行程式碼的意思是先將旋轉中心移動到(0,0)點,因為Matrix總是用0,0點作為旋轉點,旋轉之後將檢視放回原來的位置。

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實現立體3D切換效果

最後我們要實現的效果(錄得影象有點渣。。。):

3D切換

OK,有了前面的鋪墊,我們開始實現下這個效果吧(實現邏輯有參考【從零開始打造一個Android 3D立體旋轉容器】的實現,在此表示感謝~)。

我們分三步來實現下:
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方法,實現3D介面切換效果
在dispatchDraw方法中,重新對各個子控制元件用Camera和Matrix進行矩陣轉換,以此在滾動中實現立體效果,這也是我們今天要了解的重點,根據我們之前瞭解的,我們將Camera沿著X軸進行一定的角度旋轉,兩個控制元件在滾動過程中就會形成一個夾角,從而出現立體效果,當然,一定要注意的是要將控制元件中心點移至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方法實現手指滑動介面切換

在onTouchEvent方法中,根據手指移動的距離,呼叫ScrollBy()方法進行持續的滾動,在手指擡起的時候,判斷當前的速率,如果大於一定值或超過子控制元件的1/2時,轉換當前狀態進行介面切換,否則回滾回當前頁面。這裡在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();
                //滑動的速度大於規定的速度,或者向上滑動時,上一頁頁面展現出的高度超過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;
    }

四、最後,附上原始碼

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(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();
                //滑動的速度大於規定的速度,或者向上滑動時,上一頁頁面展現出的高度超過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 (STATE) {
                        case STATE_NORMAL:
                            toNormalPager();
                            break;
                        case STATE_PRE:                         
                            toPrePager();
                            break;
                        case STATE_NEXT:                        
                            toNextPager();
                            break;
                    }
                    invalidate();
    }
    /**
     * 當前頁
     */
    private void toNormalPager() {
        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>

相關推薦

Android利用CameraMatrix實現3D效果

本文行文目錄: 一、Camera與Matrix初步認識 二、Camera與Matrix旋轉效果拆分介紹 三、Camera與Matrix實現立體3D切換效果 一、Camera與Matr

AndroidAPK簽名工具之jarsigner和apksigner

內容 value signature align light 文件簽名 item als release 一.工具介紹 jarsigner是JDK提供的針對jar包簽名的通用工具, 位於JDK/bin/jarsigner.exe apksigner是Google官方提

Android群英傳》學習筆記之Android控制元件架構自定義控制元件

一、Android控制元件架構: 控制元件大致分為兩類:ViewGroup控制元件與View控制元件。View是繪製在螢幕上的使用者能與之互動的一個物件。而ViewGroup則是一個用於存放其他Vi

AndroidMVC架構和MVP架構的實踐 通俗易懂的Demo

前言 相信從事軟體開發的夥計們肯定熟悉或者聽說過專案架構,比如要新開發一個APP或者Web專案,首先考慮的就是專案需要設計什麼樣的架構,MVC還是MVP呢?MVC和MVP具體是怎麼體現的,有哪些優點,哪些缺點呢? 為什麼需要架構設計 假如我們不需要架構設計,那

C++:fstream類seekg()/seekp()tellg()/tellp()的用法

對輸入流操作:seekg()與tellg() 對輸出流操作:seekp()與tellp() 下面以輸入流函式為例介紹用法: seekg()是對輸入檔案定位,它有兩個引數:第一個引數是偏移量,第二個引數是基地址。 對於第一個引數,可以是正負數值,正的表示向後偏移,負的表示向

cocos2dx學習之路----第七篇(座標系統本地座標世界座標的轉換

這一篇我們來談談關於座標系統中本地座標與世界座標的轉換問題。 在上一篇中我們知道了標準的螢幕座標系、本地座標與世界座標的區別,還了解了關於cocos2dx的座標系問題。 其實關於OpenGL的座標,如果我們做2d程式設計,是可以暫時忽略Z軸座標的。但是卻需要記住的是渲染的深

閱讀徐宜生《Android群英傳》的筆記——第3章 Android控制元件架構自定義控制元件(3.6-3.8)

3.6 自定義 View 在自定義 View 時,我們通常會去重寫 onDraw() 方法來繪製 View 的顯示內容。如果該 View 還需要使用 wrap_content 屬性,那麼還必須重寫 onMeasure() 方法。另外,通過自定義 attr

Java的FileInputStreamFileOutputStream的基本使用

什麼是InputStream和OutputStream? InputStream和OutputStream是抽象類,是所有位元組輸入流和輸出流的父類。這裡,我們首先要分清楚兩個概念: InputStream(輸入流):輸入流是用來讀入資料

Androidpx、dp、dip、sp

眾所周知,Android廠商非常多,各種尺寸的Android手機、平板層出不窮。導致了Android生態環境的碎片化現象越來越嚴重。Google公司為了解決解析度過多的問題,在Android 的開發文件中定義了px,dp,dip,sp,方便開發者適配不同解析度的Androi

使用jQuery的外掛jquery.corner.js來實現圓角效果-

jquery.corner.js可以實現各種塊級元素的角效果,以下為演示,詳見jquery_corner.html中的註釋部分,並附百度盤下載 jquery_corner.html程式碼如下: 1 <!DOCTYPE html> 2 <html> 3 <head>

android利用Socket實現手機客戶端PC端進行通訊

伺服器端: import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; impor

Django利用filtersimple_tag為前端自定義函數的實現方法

但是 col filter 成了 應用程序 註冊 number 獲取 except 前言 Django的模板引擎提供了一般性的功能函數,通過前端可以實現多數的代碼邏輯功能,這裏稱之為一般性,是因為它僅支持大多數常見情況下的函數功能,例如if判斷,ifequal對比返回值等

Unity3D之UGUI——利用CameraRawImage元件製作3D遊戲小地圖

對於場景較大的3D場景遊戲,玩家不能夠全域性觀察自己與敵人的位置以及地圖全貌,因此製作小地圖顯示自己與敵人在地圖上的位置是必要的!       現在我就利用一個小的Demo來製作一小地圖,我在製作中查閱了網上資料,製作了一個demo,但是由於只能顯示在我新增在T

Android利用RecyclerView實現瀑布流效果

RecyclerView相比於傳統的ListView,功能更加強大,使用也比較方便,因此Android官方更加推薦使用RecycleView,未來也會有更多的程式逐漸從ListView轉向RecycleView。為此,首先先來了解下RecycleView的用法。當然,最先看

Android利用Picasso實現圖片壓縮指定任意尺寸

之前做專案時,有個需求是指定照片壓縮到任意寬高尺寸上傳給伺服器。當時我自己寫了個圖片壓縮方法,但是不夠完美,小問題不斷(比如OOM之類的)。後來看到了神器Picasso不光能載入網路圖片,還能以任意尺寸載入本地圖片。於是我想,既然Picasso能任意尺寸載入本地圖片,那它肯

Android利用ZipEntry漏洞實現免root寫惡意檔案到應用的沙盒

http://blog.csdn.net/jiangwei0910410003/article/details/52118575   版權宣告:本文為博主原創文章,未經博主允許不得轉載。 目錄(?)[-] 一前言 二漏洞場景分析 三漏洞出現的原因 四

Android的ClassLoaderdex檔案加密實現分析

Android中的ClassLoader BaseDexClassLoader Dex類載入器的基類,包含Dex類載入器之間通用功能的實現。 DexClassLoader A class loader that loads classes from .jar

android利用tablelayout實現表格效果

利用tablelayout佈局,給textview新增邊框的方法實現表格效果 在drawable中新增textview邊框樣式 四周邊框都有 <?xml version="1.0" encoding="utf-8"?> <layer-

js利用正則表示式實現空格換行的互相轉換

1.將換行符轉換成空格: var content = accountInfo.replace(/\r\n/mg,'  '); 2.將空格轉換成換行:var accountInfo = (document.getElementById("accountInfo").value

Android利用StickyListHeaders實現listView的懸浮頭

使用StickyListHeaders第三方框架可以輕鬆的實現listView新增headers,這個功能有點類似於手機的通訊錄。效果還是挺好的,使用起來也非常簡單。 首先看一下效果圖, 因為使用的是第三方的框架,所以需要新增依賴, MainAct