本文行文目录:

一、Camera与Matrix初步认识

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

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

【csdn地址:http://blog.csdn.net/zhangke3016/article/details/52093776

一、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);
    }

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);
    }



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

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

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

@Override
    protected void onDraw(Canvas canvas) {
                ...
                camera.rotateZ(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切换效果

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

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中利用Camera与Matrix实现3D效果详解的更多相关文章

  1. 60.自己定义View练习(五)高仿小米时钟 - 使用Camera和Matrix实现3D效果

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布 本文出自:猴菇先生的博客 http://blog.csdn.net/qq_31715429/article/details/546 ...

  2. Android中Intent传值与Bundle传值的区别详解

    Android中Intent传值与Bundle传值的区别详解 举个例子我现在要从A界面跳转到B界面或者C界面   这样的话 我就需要写2个Intent如果你还要涉及的传值的话 你的Intent就要写两 ...

  3. Android中@id与@+id区别和sharedUserId属性详解

    Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值. id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc.@+id/xyz等. 如果在@后 ...

  4. Android中Activity运行时屏幕方向与显示方式详解

    现在我们的手机一般都内置有方向感应器,手机屏幕会根据所处位置自动进行横竖屏切换(前提是未锁定屏幕方向).但有时我们的应用程序仅限在横屏或者竖屏状态下才可以运行,此时我们需要锁定该程序Activity运 ...

  5. Android中源码Launcher主屏幕程序排列详解【安卓Launcher进化一】

    最近研究Lancher,从短信Mms的框架中过度到Launcher的bug和需求修改中,下面对launcher最简单的主屏幕程序的程序的布局的详 解,给读者一个入门的感觉,android的主屏幕一共分 ...

  6. Linux中利用grep命令如何检索文件内容详解

    前言 Linux系统中搜索.查找文件中的内容,一般最常用的是grep命令,另外还有egrep命令,同时vi命令也支持文件内容检索.下面来一起看看Linux利用grep命令检索文件内容的详细介绍. 方法 ...

  7. Android中APK签名工具之jarsigner和apksigner详解

    一.工具介绍 jarsigner是JDK提供的针对jar包签名的通用工具, 位于JDK/bin/jarsigner.exe apksigner是Google官方提供的针对Android apk签名及验 ...

  8. Android中的Service与进程间通信(IPC)详解

    Service 什么是Service 在后台长期运行的没有界面的组件.其他组件可以启动Service让他在后台运行,或者绑定Service与它进行交互,甚至实现进程间通信(IPC).例如,可以让服务在 ...

  9. Android中的surfaceHolder.lockCanvas(null)返回为null详解

    对于新手学习SurfaceView的时候获取lockCanvas的时候总是返回null的问题很是纠结 canvas = surfaceHolder.lockCanvas(new Rect(0, 0, ...

随机推荐

  1. UDA机器学习基础—交叉验证

    交叉验证的目的是为了有在训练集中有更多的数据点,以获得最佳的学习效果,同时也希望有跟多的测试集数据来获得最佳验证.交叉验证的要点是将训练数据平分到k个容器中,在k折交叉验证中,将运行k次单独的试验,每 ...

  2. WebDNN:Web浏览器上最快的DNN执行框架

    WebDNN:Web浏览器上最快的DNN执行框架 为什么需要WebDNN? 深层神经网络(DNN)在许多应用中受到越来越多的关注. 然而,它需要大量的计算资源,并且有许多巨大的过程来设置基于执行环境的 ...

  3. sqlserver 取日期年份月份

    select convert(varchar(10),datepart(YYYY,a.fssj)) as years,--得到年份convert(varchar(10),datepart(mm,a.f ...

  4. if-case-循环语句

    IF语句 drop procedure if exists p_hello_world; create procedure p_hello_world(in v_id int) begin ) the ...

  5. 在查询语句中使用 NOLOCK 和 READPAST

    对于非银行等严格要求事务的行业,搜索记录中出现或者不出现某条记录,都是在可容忍范围内,所以碰到死锁,应该首先考虑,我们业务逻辑是否能容忍出现或者不出现某些记录,而不是寻求对双方都加锁条件下如何解锁的问 ...

  6. 安卓高级 Android图片缓存之初识Glide

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  7. android ActionBarActivity设置全屏无标题

    新建的Activity继承自ActionBarActivity,设置全屏无标题本来很简单的事,但是没想到app竟然无缘无故的挂,要么就是白屏一片,要么就是黑屏.坑了我一个多小时!!! 原因是Actio ...

  8. Programming In Scala笔记-第六章、函数式对象

    这一章主要是以定义和完善一个有理数类Rational为线索,分析和介绍有关类定义,构造函数,方法重写,变量定义和私有化,以及对操作符的定义等. 一.Rational类定义和构造函数 1.定义一个空类 ...

  9. Thread Pools

    许多程序会动态创建数十个设置上百个线程.举个例子,一个web服务器可能在每一个新到来的请求时创建一个新线程,然后在请求完成后将其终止. 然而,创建一个新线程将会带来一定的耗费:它需要在内核中创建自身必 ...

  10. 从源码安装git

    蛋疼的阿里云,git版本居然才1.9.只能手动安装了. 预装 apt-get update apt-get --yes install libcurl4-gnutls-dev libexpat1-de ...