知足是天赋的财富;奢侈是人为的贫穷。——苏格拉底


threeddemoActivity.java代码:

package com.siso.crazyworld;

import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class threeddemoActivity extends AppCompatActivity {

    private Button btnLogin;
    private Button btnSetting;
    private Button btnImage;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threeddemo);
        btnLogin = (Button) findViewById(R.id.btn_login);
        btnSetting = (Button) findViewById(R.id.btn_setting);
        btnImage = (Button) findViewById(R.id.btn_image);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(threeddemoActivity.this, LoginActivity.class);
                startActivity(intent);
            }
        });
        btnSetting.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(threeddemoActivity.this, SettingActivity.class);
                startActivity(intent);
            }
        });
        btnImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(threeddemoActivity.this, ImageActivity.class);
                startActivity(intent);
            }
        });
    }
}

SettingActivity.java代码:

package com.siso.crazyworld;

import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;

import com.siso.crazyworld.custom.StereoView;

public class SettingActivity extends AppCompatActivity {

    private StereoView stereoView1;
    private StereoView stereoView2;
    private SeekBar sbAngle;
    private TextView tvAngle;
    private Button btnNext;
    private Button btnPre;
    private Button btnSelect;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_setting);
        initView();
        initStereoView();
        sbAngle.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                stereoView1.setAngle(i);
                tvAngle.setText("设置3D旋转页面夹角,当前夹角 " + i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
        btnPre.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stereoView1.toPre();
            }
        });
        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stereoView1.toNext();
            }
        });
        btnSelect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stereoView1.setItem(3);
            }
        });
    }

    private void initView() {
        stereoView1 = (StereoView) findViewById(R.id.stereoView1);
        stereoView2 = (StereoView) findViewById(R.id.stereoView2);
        sbAngle = (SeekBar) findViewById(R.id.sb_angle);
        tvAngle = (TextView) findViewById(R.id.tv_angle);
        btnPre = (Button) findViewById(R.id.btn_pre);
        btnNext = (Button) findViewById(R.id.btn_next);
        btnSelect = (Button) findViewById(R.id.btn_select);
    }

    private void initStereoView() {
        stereoView1.setResistance(4f)//设置滑动时阻力
                .setInterpolator(new BounceInterpolator())//设置
                .setStartScreen(2);//设置开始时item,注意不能是开头和结尾
        stereoView2.setCan3D(false);
    }
}

LoginActivity.java代码:

package com.siso.crazyworld;

import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.siso.crazyworld.custom.RippleView;
import com.siso.crazyworld.custom.StereoView;
import com.siso.crazyworld.utils.LogUtil;
import com.siso.crazyworld.utils.ToastUtil;

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText etUsername;
    private EditText etEmail;
    private EditText etPassword;
    private RippleView rvUsername;
    private RippleView rvEmail;
    private RippleView rvPassword;
    private StereoView stereoView;
    private LinearLayout lyWelcome;
    private TextView tvWelcome;
    private int translateY;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
        stereoView.setStartScreen(2);
        stereoView.post(new Runnable() {
            @Override
            public void run() {
                int[] location = new int[2];
                stereoView.getLocationOnScreen(location);
                translateY = location[1];
            }
        });
        stereoView.setiStereoListener(new StereoView.IStereoListener() {
            @Override
            public void toPre(int curScreen) {
                LogUtil.m("跳转到前一页 " + curScreen);
            }

            @Override
            public void toNext(int curScreen) {
                LogUtil.m("跳转到下一页 " + curScreen);
            }
        });
    }

    private void initView() {
        stereoView = (StereoView) findViewById(R.id.stereoView);
        etUsername = (EditText) findViewById(R.id.et_username);
        etEmail = (EditText) findViewById(R.id.et_email);
        etPassword = (EditText) findViewById(R.id.et_password);
        rvUsername = (RippleView) findViewById(R.id.rv_username);
        rvEmail = (RippleView) findViewById(R.id.rv_email);
        rvPassword = (RippleView) findViewById(R.id.rv_password);
        lyWelcome = (LinearLayout) findViewById(R.id.ly_welcome);
        tvWelcome = (TextView) findViewById(R.id.tv_welcome);
        rvUsername.setOnClickListener(this);
        rvEmail.setOnClickListener(this);
        rvPassword.setOnClickListener(this);
        tvWelcome.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.rv_username:
                rvUsername.setiRippleAnimListener(new RippleView.IRippleAnimListener() {
                    @Override
                    public void onComplete(View view) {
                        stereoView.toPre();
                    }
                });
                break;
            case R.id.rv_email:
                rvEmail.setiRippleAnimListener(new RippleView.IRippleAnimListener() {
                    @Override
                    public void onComplete(View view) {
                        stereoView.toPre();
                    }
                });
                break;
            case R.id.rv_password:
                rvPassword.setiRippleAnimListener(new RippleView.IRippleAnimListener() {
                    @Override
                    public void onComplete(View view) {
                        stereoView.toPre();
                    }
                });
                break;
            case R.id.tv_welcome:
                if (TextUtils.isEmpty(etUsername.getText())) {
                    ToastUtil.showInfo(LoginActivity.this, "请输入用户名!");
                    stereoView.setItem(2);
                    return;
                }
                if (TextUtils.isEmpty(etEmail.getText())) {
                    ToastUtil.showInfo(LoginActivity.this, "请输入邮箱!");
                    stereoView.setItem(1);
                    return;
                }
                if (TextUtils.isEmpty(etPassword.getText())) {
                    ToastUtil.showInfo(LoginActivity.this, "请输入密码!");
                    stereoView.setItem(0);
                    return;
                }
                startExitAnim();
                break;
        }
    }

    private void startExitAnim() {
        ObjectAnimator animator = ObjectAnimator.ofFloat(stereoView, "translationY", 0, 100, -translateY);
        animator.setDuration(500).start();
        ToastUtil.showInfo(LoginActivity.this, "登录成功 =.=");
    }
}

ImageActivity.java代码:

package com.siso.crazyworld;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class ImageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image);
    }
}

utils文件夹下LogUtil.java和ToastUtil.java

package com.siso.crazyworld.utils;

import android.util.Log;

/**
 */
public class LogUtil {
    /**
     * 默认的tag
     */
    public static final String defaultTag = "dota";
    public static final int VERBOSE = 1;
    public static final int DEBUG = 2;
    public static final int INFO = 3;
    public static final int WARN = 4;
    public static final int ERROR = 5;
    public static final int NOTHING = 6;
    /**
     * 下面这个变量定义日志级别
     */
    public static final int LEVEL = VERBOSE;


    public static void v(String tag, String msg) {
        if (LEVEL <= VERBOSE) {
            Log.v(tag, msg);
        }
    }

    public static void d(String tag, String msg) {
        if (LEVEL <= DEBUG) {
            Log.d(tag, msg);
        }
    }

    public static void i(String tag, String msg) {
        if (LEVEL <= INFO) {
            Log.i(tag, msg);
        }
    }

    public static void w(String tag, String msg) {
        if (LEVEL <= WARN) {
            Log.w(tag, msg);
        }
    }

    public static void e(String tag, String msg) {
        if (LEVEL <= ERROR) {
            Log.e(tag, msg);
        }
    }

    public static void v(String msg) {
        if (LEVEL <= VERBOSE) {
            Log.v(defaultTag, msg);
        }
    }

    public static void d(String msg) {
        if (LEVEL <= DEBUG) {
            Log.d(defaultTag, msg);
        }
    }

    public static void i(String msg) {
        if (LEVEL <= INFO) {
            Log.i(defaultTag, msg);
        }
    }

    public static void w(String msg) {
        if (LEVEL <= WARN) {
            Log.w(defaultTag, msg);
        }
    }

    public static void e(String msg) {
        if (LEVEL <= ERROR) {
            Log.e(defaultTag, msg);
        }
    }

    public static void m(String msg){
        String methodName = new Exception().getStackTrace()[1].getMethodName();
        Log.v(defaultTag,methodName+":    "+msg);
    }
    public static void m(int msg){
        String methodName = new Exception().getStackTrace()[1].getMethodName();
        Log.v(defaultTag,methodName+":    "+msg+"");
    }

    public static void m(){
        String methodName = new Exception().getStackTrace()[1].getMethodName();
        Log.v(defaultTag,methodName);
    }

    public static void v(int msg) {
        LogUtil.v(msg + "");
    }

    public static void d(int msg) {
        LogUtil.d(msg + "");
    }

    public static void i(int msg) {
        LogUtil.i(msg + "");
    }

    public static void w(int msg) {
        LogUtil.w(msg + "");
    }

    public static void e(int msg) {
        LogUtil.e(msg + "");
    }
}

package com.siso.crazyworld.utils;

import android.content.Context;
import android.widget.Toast;


public class ToastUtil {
    public static void showInfo(Context context, String info) {
        Toast.makeText(context, info, Toast.LENGTH_SHORT).show();
    }
}

custom文件夹下RippleView.java和StereoView.java

package com.siso.crazyworld.custom;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

import com.siso.crazyworld.R;


public class RippleView extends View {
    private Paint maxPaint;
    private Paint minPaint;
    private Paint bgPaint;
    private Drawable bgNormal;
    private Drawable bgPressed;
    private Bitmap bgBitmapNormal;
    private Bitmap bgBitmapPressed;
    private int mWidth;
    private int mHeight;
    private int radius;
    private int maxRadius;
    private int minRadius;
    private RippleAnim rippleAnim;
    private float mInterpolatedTime;
    private Region region;
    private Rect rect;
    private boolean isPressed = false;
    private IRippleAnimListener iRippleAnimListener;

    public RippleView(Context context) {
        this(context, null);
    }

    public RippleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RippleView);
        bgPressed = ta.getDrawable(R.styleable.RippleView_pressed);
        bgNormal = ta.getDrawable(R.styleable.RippleView_normal);
        ta.recycle();
        init();
    }

    private void init() {
        maxPaint = new Paint();
        maxPaint.setColor(getResources().getColor(R.color.bg_gray));
        minPaint = new Paint();
        minPaint.setColor(getResources().getColor(R.color.bg_white));
        bgPaint = new Paint();
        bgPaint.setAntiAlias(true);
        if (bgNormal != null) {
            bgBitmapNormal = ((BitmapDrawable) bgNormal).getBitmap();
        }
        if (bgPressed != null) {
            bgBitmapPressed = ((BitmapDrawable) bgPressed).getBitmap();
        }
        region = new Region();
        rect = new Rect();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        radius = Math.min(mWidth / 2, mHeight / 2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.translate(getPaddingLeft(), 0);
        maxRadius = (int) (mInterpolatedTime * radius * 1.2f);
        if (maxRadius > radius * 0.8f) {
            minRadius = (int) ((mInterpolatedTime - 0.6) * radius * 3);
        }
        if (mInterpolatedTime != 1.0f) {
            canvas.drawCircle(mWidth / 2, mHeight / 2, maxRadius, maxPaint);
            canvas.drawCircle(mWidth / 2, mHeight / 2, minRadius, minPaint);
        } else {
            clearState();
        }
        if (bgBitmapNormal != null && !isPressed) {
            Rect rect = new Rect();
            rect.set(mWidth * 5 / 16, mHeight * 5 / 16, mWidth * 11 / 16, mHeight * 11 / 16);
            canvas.drawBitmap(bgBitmapNormal, null, rect, bgPaint);
        } else if (bgBitmapPressed != null && isPressed) {
            Rect rect = new Rect();
            rect.set(mWidth * 5 / 16, mHeight * 5 / 16, mWidth * 11 / 16, mHeight * 11 / 16);
            canvas.drawBitmap(bgBitmapPressed, null, rect, bgPaint);
        }


    }

    private void clearState() {
        minRadius = 0;
        maxRadius = 0;
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (isContain(event)) {
                    isPressed = true;
                    startRipple();
                } else {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:

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

    private boolean isContain(MotionEvent event) {
        rect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
        region.set(rect);
        if (region.contains((int) event.getX(), (int) event.getY())) {
            return true;
        }
        return false;
    }

    private void startRipple() {
        if (rippleAnim == null) {
            rippleAnim = new RippleAnim();
        }
        rippleAnim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                if (iRippleAnimListener != null) {
                    iRippleAnimListener.onComplete(RippleView.this);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        rippleAnim.cancel();
        rippleAnim.setDuration(600);
        startAnimation(rippleAnim);
    }

    private class RippleAnim extends Animation {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            mInterpolatedTime = interpolatedTime;
            invalidate();
        }
    }

    public interface IRippleAnimListener{
        void onComplete(View view);
    }

    public void setiRippleAnimListener(IRippleAnimListener iRippleAnimListener) {
        this.iRippleAnimListener = iRippleAnimListener;
    }


}

package com.siso.crazyworld.custom;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.Scroller;

import com.siso.crazyworld.utils.LogUtil;



public class StereoView extends ViewGroup {

    //可对外进行设置的参数
    private int mStartScreen = 1;//开始时的item位置
    private float resistance = 1.8f;//滑动阻力
    private Scroller mScroller;
    private float mAngle = 90;//两个item间的夹角
    private boolean isCan3D = true;//是否开启3D效果

    private Context mContext;
    private int mTouchSlop;
    private VelocityTracker mVelocityTracker;
    private Camera mCamera;
    private Matrix mMatrix;
    private int mWidth;//容器的宽度
    private int mHeight;//容器的高度
    private static final int standerSpeed = 2000;
    private static final int flingSpeed = 800;
    private int addCount;//记录手离开屏幕后,需要新增的页面次数
    private int alreadyAdd = 0;//对滑动多页时的已经新增页面次数的记录
    private boolean isAdding = false;//fling时正在添加新页面,在绘制时不需要开启camera绘制效果,否则页面会有闪动
    private int mCurScreen = 1;//记录当前item
    private IStereoListener iStereoListener;
    private float mDownX, mDownY, mTempY;
    private boolean isSliding = false;

    private State mState = State.Normal;

    public StereoView(Context context) {
        this(context, null);
    }

    public StereoView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StereoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init(mContext);
    }

    /**
     * 初始化数据
     */
    private void init(Context context) {
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        mCamera = new Camera();
        mMatrix = new Matrix();
        if (mScroller == null) {
            mScroller = new Scroller(context);
        }
    }


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


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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float x = ev.getX();
        float y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isSliding = false;
                mDownX = x;
                mTempY = mDownY = y;
                if (!mScroller.isFinished()) {
                    //当上一次滑动没有结束时,再次点击,强制滑动在点击位置结束
                    mScroller.setFinalY(mScroller.getCurrY());
                    mScroller.abortAnimation();
                    scrollTo(0, getScrollY());
                    isSliding = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isSliding) {
                    isSliding = isCanSliding(ev);
                }
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return isSliding;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_MOVE:
                if (isSliding) {
                    int realDelta = (int) (mDownY - y);
                    mDownY = y;
                    if (mScroller.isFinished()) {
                        //因为要循环滚动
                        recycleMove(realDelta);
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (isSliding) {
                    isSliding = false;
                    mVelocityTracker.computeCurrentVelocity(1000);
                    float yVelocity = mVelocityTracker.getYVelocity();
                    //滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为State.ToPre
                    if (yVelocity > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)) {
                        mState = State.ToPre;
                    } else if (yVelocity < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)) {
                        //滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为State.ToNext
                        mState = State.ToNext;
                    } else {
                        mState = State.Normal;
                    }
                    //根据mState进行相应的变化
                    changeByState(yVelocity);
                }
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    public boolean isCanSliding(MotionEvent ev) {
        float moveX;
        float moveY;
        moveX = ev.getX();
        mTempY = moveY = ev.getY();
        if (Math.abs(moveY - mDownX) > mTouchSlop && (Math.abs(moveY - mDownY) > (Math.abs(moveX - mDownX)))) {
            return true;
        }
        return false;
    }

    private void changeByState(float yVelocity) {
        alreadyAdd = 0;//重置滑动多页时的计数
        if (getScrollY() != mHeight) {
            switch (mState) {
                case Normal:
                    toNormalAction();
                    break;
                case ToPre:
                    toPreAction(yVelocity);
                    break;
                case ToNext:
                    toNextAction(yVelocity);
                    break;
            }
            invalidate();
        }
    }

    /**
     * mState = State.Normal 时进行的动作
     */
    private void toNormalAction() {
        int startY;
        int delta;
        int duration;
        mState = State.Normal;
        addCount = 0;
        startY = getScrollY();
        delta = mHeight * mStartScreen - getScrollY();
        duration = (Math.abs(delta)) * 4;
        mScroller.startScroll(0, startY, 0, delta, duration);
    }

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

    /**
     * mState = State.ToNext 时进行的动作
     *
     * @param yVelocity 竖直方向的速度
     */
    private void toNextAction(float yVelocity) {
        int startY;
        int delta;
        int duration;
        mState = State.ToNext;
        addNext();
        int flingSpeedCount = (Math.abs(yVelocity) - standerSpeed) > 0 ? (int) (Math.abs(yVelocity) - standerSpeed) : 0;
        addCount = flingSpeedCount / flingSpeed + 1;
        startY = getScrollY() - mHeight;
        setScrollY(startY);
        delta = mHeight * mStartScreen - startY + (addCount - 1) * mHeight;
        LogUtil.m("多后一页startY " + startY + " yVelocity " + yVelocity + " delta " + delta + "  getScrollY() " + getScrollY() + " addCount " + addCount);
        duration = (Math.abs(delta)) * 3;
        mScroller.startScroll(0, startY, 0, delta, duration);
        addCount--;
    }


    @Override
    public void computeScroll() {
        //滑动没有结束时,进行的操作
        if (mScroller.computeScrollOffset()) {
            if (mState == State.ToPre) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY() + mHeight * alreadyAdd);
                if (getScrollY() < (mHeight + 2) && addCount > 0) {
                    isAdding = true;
                    addPre();
                    alreadyAdd++;
                    addCount--;
                }
            } else if (mState == State.ToNext) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY() - mHeight * alreadyAdd);
                if (getScrollY() > (mHeight) && addCount > 0) {
                    isAdding = true;
                    addNext();
                    addCount--;
                    alreadyAdd++;
                }
            } else {
                //mState == State.Normal状态
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            }
            postInvalidate();
        }
        //滑动结束时相关用于计数变量复位
        if (mScroller.isFinished()) {
            alreadyAdd = 0;
            addCount = 0;
        }
    }

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

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

    private void recycleMove(int delta) {
        delta = delta % mHeight;
        delta = (int) (delta / resistance);
        if (Math.abs(delta) > mHeight / 4) {
            return;
        }
        scrollBy(0, delta);
        if (getScrollY() < 5 && mStartScreen != 0) {
            addPre();
            scrollBy(0, mHeight);
        } else if (getScrollY() > (getChildCount() - 1) * mHeight - 5) {
            addNext();
            scrollBy(0, -mHeight);
        }

    }

    public enum State {
        Normal, ToPre, ToNext
    }

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

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

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

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

    }


    /**
     * 设置第一页展示的页面
     *
     * @param startScreen (0,getChildCount-1)
     * @return
     */
    public StereoView setStartScreen(int startScreen) {
        if (startScreen <= 0 || startScreen >= (getChildCount() - 1)) {
            throw new IndexOutOfBoundsException("请输入规定范围内startScreen位置号");

        }
        this.mStartScreen = startScreen;
        this.mCurScreen = startScreen;
        return this;
    }

    /**
     * 设置移动阻力
     *
     * @param resistance (0,...)
     * @return
     */
    public StereoView setResistance(float resistance) {
        this.resistance = resistance;
        return this;
    }

    /**
     * 设置滚动时interpolator插补器
     *
     * @param mInterpolator
     * @return
     */
    public StereoView setInterpolator(Interpolator mInterpolator) {
        mScroller = new Scroller(mContext, mInterpolator);
        return this;
    }

    /**
     * 设置滚动时两个item的夹角度数
     *
     * @param mAngle [0f,180f]
     * @return
     */
    public StereoView setAngle(float mAngle) {
        this.mAngle = 180f - mAngle;
        return this;
    }

    /**
     * 是否开启3D效果
     *
     * @param can3D
     * @return
     */
    public StereoView setCan3D(boolean can3D) {
        isCan3D = can3D;
        return this;
    }

    /**
     * 跳转到指定的item
     *
     * @param itemId [0,getChildCount-1]
     * @return
     */
    public StereoView setItem(int itemId) {

        LogUtil.m("之前curScreen " + mCurScreen);
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            LogUtil.m("强制完成");
        }
        if (itemId < 0 || itemId > (getChildCount() - 1)) {
            throw new IndexOutOfBoundsException("请输入规定范围内item位置号");

        }
        if (itemId > mCurScreen) {
            //setScrollY(mStartScreen * mHeight);
            toNextAction(-standerSpeed - flingSpeed * (itemId - mCurScreen - 1));
        } else if (itemId < mCurScreen) {
            //setScrollY(mStartScreen * mHeight);
            toPreAction(standerSpeed + (mCurScreen - itemId - 1) * flingSpeed);
        }
        LogUtil.m("之后curScreen " + mCurScreen + " getScrollY " + getScrollY());
        return this;
    }

    /**
     * 上一页
     *
     * @return
     */
    public StereoView toPre() {
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            LogUtil.m("强制完成");
        }
        toPreAction(standerSpeed);
        return this;
    }

    /**
     * 下一页
     *
     * @return
     */
    public StereoView toNext() {
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            LogUtil.m("强制完成");
        }
        toNextAction(-standerSpeed);
        return this;
    }


    public interface IStereoListener {
        //上滑一页时回调
        void toPre(int curScreen);

        //下滑一页时回调
        void toNext(int curScreen);
    }

    public void setiStereoListener(IStereoListener iStereoListener) {
        this.iStereoListener = iStereoListener;
    }
}

activity_threeddemo.xml内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="登录"/>

    <Button
        android:id="@+id/btn_setting"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="调节样式"/>
    <Button
        android:id="@+id/btn_image"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="图片展示"/>
</LinearLayout>

activity_setting.xml内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_blue_deep"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="3D效果"
        android:textColor="@color/font_white"/>

    <com.siso.crazyworld.custom.StereoView
        android:id="@+id/stereoView1"
        android:layout_width="300dp"
        android:layout_height="60dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_password"/>

            <EditText
                android:id="@+id/et_password"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="0"
                android:inputType="textPassword"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_password"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_email"/>

            <EditText
                android:id="@+id/et_email"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="1"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_email"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_user"/>

            <EditText
                android:id="@+id/et_username"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="2"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_username"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_love"/>

            <TextView
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:gravity="center"
                android:hint="3"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_love"/>
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_love"/>

            <TextView
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:gravity="center"
                android:hint="4"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_love"/>
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ly_welcome"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_love"/>

            <TextView
                android:id="@+id/tv_welcome"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:gravity="center"
                android:hint="5"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_love"/>
        </LinearLayout>
    </com.siso.crazyworld.custom.StereoView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="平面效果"
        android:textColor="@color/font_white"/>

    <com.siso.crazyworld.custom.StereoView
        android:id="@+id/stereoView2"
        android:layout_width="300dp"
        android:layout_height="60dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_password"/>

            <EditText
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="0"
                android:inputType="textPassword"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_email"/>

            <EditText
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="1"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_user"/>

            <EditText
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="2"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_love"/>

            <TextView
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:gravity="center"
                android:hint="3"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_love"/>
        </LinearLayout>

    </com.siso.crazyworld.custom.StereoView>

    <TextView
        android:id="@+id/tv_angle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="设置3D旋转页面夹角"
        android:textColor="@color/font_white"/>

    <SeekBar
        android:id="@+id/sb_angle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:thumbOffset="0dip"
        android:max="180"/>
    <Button
        android:text="上一页"
        android:id="@+id/btn_pre"
        android:layout_width="match_parent"
        android:layout_height="60dp"/>
    <Button
        android:text="下一页"
        android:id="@+id/btn_next"
        android:layout_width="match_parent"
        android:layout_height="60dp"/>
    <Button
        android:text="跳转到第4页"
        android:id="@+id/btn_select"
        android:layout_width="match_parent"
        android:layout_height="60dp"/>
</LinearLayout>

activity_login.xml内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_blue_deep"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">


    <com.siso.crazyworld.custom.StereoView
        android:id="@+id/stereoView"
        android:layout_width="300dp"
        android:layout_height="60dp"
        android:layout_marginTop="50dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_password"/>

            <EditText
                android:id="@+id/et_password"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:hint="Password"
                android:inputType="textPassword"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_password"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_email"/>

            <EditText
                android:id="@+id/et_email"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:singleLine="true"
                android:hint="Email"
                android:textColorHint="@color/font_gray"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_email"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_user"/>

            <EditText
                android:id="@+id/et_username"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:singleLine="true"
                android:background="@null"
                android:hint="UserName"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:id="@+id/rv_username"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_next_gray"
                app:pressed="@drawable/icon_next"/>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/ly_welcome"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/bg_white"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical"
            android:paddingLeft="20dp">

            <ImageView
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:background="@drawable/icon_love"/>

            <TextView
                android:id="@+id/tv_welcome"
                android:layout_width="180dp"
                android:layout_height="50dp"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:singleLine="true"
                android:gravity="center"
                android:hint="You are welcome !"
                android:textColorHint="@color/font_gray"
                android:textSize="18sp"/>

            <com.siso.crazyworld.custom.RippleView
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:paddingLeft="10dp"
                app:normal="@drawable/icon_love"/>
        </LinearLayout>

    </com.siso.crazyworld.custom.StereoView>

</LinearLayout>

activity_image.xml内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_blue_deep"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">


    <com.siso.crazyworld.custom.StereoView
        android:id="@+id/stereoView"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_marginTop="50dp">

        <ImageView
            android:background="@drawable/p10"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <ImageView
            android:background="@drawable/p14"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <ImageView
            android:background="@drawable/p12"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <ImageView
            android:background="@drawable/p11"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </com.siso.crazyworld.custom.StereoView>

</LinearLayout>

运行结果如图:

android立体效果 安卓3d立体主题_3d


android立体效果 安卓3d立体主题_ide_02


android立体效果 安卓3d立体主题_android立体效果_03