本文行文目錄:
一、Camera與Matrix初步認(rèn)識
二、Camera與Matrix旋轉(zhuǎn)效果拆分介紹
三、Camera與Matrix實現(xiàn)立體3D切換效果
【本文簡書地址:http://www.lxweimin.com/p/34e0fe5f9e31】
一、Camera與Matrix初步認(rèn)識
android中一共有兩個Camera,分別為:
android.graphics.Camera
android.hardware.Camera
今天我們要說的是第一個Camera,第二個主要應(yīng)用在相機開發(fā)中。
首先看下這個類的官方介紹:
A camera instance can be used to compute 3D transformations and generate a matrix that can be applied, for instance, on aCanvas.
一個照相機實例可以被用于計算3D變換,生成一個可以被使用的Matrix矩陣,一個實例,用在畫布上。
Camera內(nèi)部機制實際上還是opengl,不過大大簡化了使用。有了感性的認(rèn)識之后,我們再來看下它的常用API定義:
Camera() 創(chuàng)建一個沒有任何轉(zhuǎn)換效果的新的Camera實例
applyToCanvas(Canvas canvas) 根據(jù)當(dāng)前的變換計算出相應(yīng)的矩陣,然后應(yīng)用到制定的畫布上
getLocationX() 獲取Camera的x坐標(biāo)
getLocationY() 獲取Camera的y坐標(biāo)
getLocationZ() 獲取Camera的z坐標(biāo)
getMatrix(Matrixmatrix) 獲取轉(zhuǎn)換效果后的Matrix對象
restore() 恢復(fù)保存的狀態(tài)
rotate(float x, float y, float z) 沿X、Y、Z坐標(biāo)進行旋轉(zhuǎn)
rotateX(float deg)
rotateY(float deg)
rotateZ(float deg)
save() 保存狀態(tài)
setLocation(float x, float y, float z)
translate(float x, float y, float z)沿X、Y、Z軸進行平移
不得不說下Matrix,它是Android提供的一個矩陣工具類,是一個3x3的矩陣,一般要實現(xiàn)2D的旋轉(zhuǎn)(繞z軸旋轉(zhuǎn))、縮放、平移、傾斜用這個作用于畫布,這四種操作的內(nèi)部實現(xiàn)過程都是通過matrix.setValues(…)來設(shè)置矩陣的值來達到變換的效果。
setTranslate(floatdx,floatdy):控制Matrix進行平移
setSkew(floatkx,floatky,floatpx,floatpy):控制Matrix以px,py為軸心進行傾斜,kx,ky為X,Y方向上的傾斜距離
setRotate(floatdegress):控制Matrix進行旋轉(zhuǎn),degress控制旋轉(zhuǎn)的角度
setRorate(floatdegress,floatpx,floatpy):設(shè)置以px,py為軸心進行旋轉(zhuǎn),degress控制旋轉(zhuǎn)角度
setScale(floatsx,floatsy):設(shè)置Matrix進行縮放,sx,sy控制X,Y方向上的縮放比例
setScale(floatsx,floatsy,floatpx,floatpy):設(shè)置Matrix以px,py為軸心進行縮放,sx,sy控制X,Y方向上的縮放比例
API提供了set、post和pre三種操作,下面這個重點看下,之后效果會用到
post是后乘,當(dāng)前的矩陣乘以參數(shù)給出的矩陣。可以連續(xù)多次使用post,來完成所需的整個變換。
pre是前乘,參數(shù)給出的矩陣乘以當(dāng)前的矩陣。所以操作是在當(dāng)前矩陣的最前面發(fā)生的。
好了,上面基本方法先簡單了解下,現(xiàn)在讓我們看看能做出什么效果,之后回頭再重新看看會有更深的理解。
二、Camera與Matrix旋轉(zhuǎn)效果拆分介紹
Camera坐標(biāo)系研究
Camera的坐標(biāo)系是左手坐標(biāo)系。當(dāng)手機平整的放在桌面上,X軸是手機的水平方向,Y軸是手機的豎直方向,Z軸是垂直于手機向里的那個方向。
camera位于坐標(biāo)點(0,0),也就是視圖的左上角;
camera.translate(10,50,-180)的意思是把觀察物體右移(+x)10,上移(+y)50,向-z軸移180(即讓物體接近camera,這樣物體將會變大);
原圖:
public class CameraTestView extends View{
private Camera camera;
private Matrix matrix;
private Paint paint;
public CameraTestView(Context context, AttributeSet attrs) {
super(context, attrs);
camera = new Camera();
matrix = new Matrix();
setBackgroundColor(Color.parseColor("#3f51b5"));
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.FILL);
paint.setColor(Color.parseColor("#ff4081"));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(60, 60, 60, paint);
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
tools:context=".MainActivity" >
<com.example.matrixcamera.CameraTestView
android:layout_width="200dp"
android:layout_height="200dp"
/>
</RelativeLayout>
@Override
protected void onDraw(Canvas canvas) {
matrix.reset();
camera.save();
camera.translate(10, 50, -180);
camera.getMatrix(matrix);
camera.restore();
canvas.concat(matrix);
canvas.drawCircle(60, 60, 60, paint);
}
camera.rotateX(60)的意思是繞X軸順時針旋轉(zhuǎn)60度。舉例來說,如果物體中間線和X軸重合的話,繞X軸順時針旋轉(zhuǎn)60度就是指物體上半部分向里翻轉(zhuǎn),下半部分向外翻轉(zhuǎn);
@Override
protected void onDraw(Canvas canvas) {
Options option = new Options();
option.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.aa,option);
option.inSampleSize = calculateInSampleSize(option, getWidth()/2, getHeight()/2);
option.inJustDecodeBounds = false;
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.aa,option), matrix, paint);
}
private int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
@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軸順時針旋轉(zhuǎn)60度。舉例來說,如果物體中間線和Y軸重合的話,繞Y軸順時針旋轉(zhuǎn)60度就是指物體左半部分向外翻轉(zhuǎn),右半部分向里翻轉(zhuǎn);
@Override
protected void onDraw(Canvas canvas) {
...
camera.rotateY(60);
...
}
camera.rotateZ(60)的意思是繞Z軸逆時針旋轉(zhuǎn)60度。舉例來說,如果物體中間線和Z軸重合的話,繞Z軸順時針旋轉(zhuǎn)60度就是物體上半部分向左翻轉(zhuǎn),下半部分向右翻轉(zhuǎn)。
@Override
protected void onDraw(Canvas canvas) {
...
camera.rotateZ(60);
...
}
注意:下面兩行代碼的意思是先將旋轉(zhuǎn)中心移動到(0,0)點,因為Matrix總是用0,0點作為旋轉(zhuǎn)點,旋轉(zhuǎn)之后將視圖放回原來的位置。
matrix.preTranslate(-getWidth()/2, -getHeight()/2);
matrix.postTranslate(getWidth()/2, getHeight()/2);
API DEMOS中的例子,大家可以看下效果加深理解:
/**
* An animation that rotates the view on the Y axis between two specified angles.
* This animation also adds a translation on the Z axis (depth) to improve the effect.
*/
public class Rotate3dAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
/**
* Creates a new 3D rotation on the Y axis. The rotation is defined by its
* start angle and its end angle. Both angles are in degrees. The rotation
* is performed around a center point on the 2D space, definied by a pair
* of X and Y coordinates, called centerX and centerY. When the animation
* starts, a translation on the Z axis (depth) is performed. The length
* of the translation can be specified, as well as whether the translation
* should be reversed in time.
*
* @param fromDegrees the start angle of the 3D rotation
* @param toDegrees the end angle of the 3D rotation
* @param centerX the X center of the 3D rotation
* @param centerY the Y center of the 3D rotation
* @param reverse true if the translation should be reversed, false otherwise
*/
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse) {
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
iv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
int width = getWindowManager().getDefaultDisplay().getWidth();
int height = getWindowManager().getDefaultDisplay().getHeight();
Rotate3dAnimation animation = new Rotate3dAnimation(0, 360, width/2, height/2,0, true);
animation.setInterpolator(new AccelerateDecelerateInterpolator());
animation.setDuration(2000);
animation.setFillAfter(true);
iv.startAnimation(animation);
}
});
三、Camera與Matrix實現(xiàn)立體3D切換效果
最后我們要實現(xiàn)的效果(錄得圖像有點渣。。。):
OK,有了前面的鋪墊,我們開始實現(xiàn)下這個效果吧。
我們分三步來實現(xiàn)下:
1、首先,初始化控件,進行測量和布局。
這里我們整個容器繼承自ViewGroup,來看看吧,初始化Camera和Matrix,因為涉及到滾動,我們用個輔助工具Scroller:
private void init() {
mCamera = new Camera();
mMatrix = new Matrix();
if (mScroller == null){
mScroller = new Scroller(getContext(),new LinearInterpolator());
}
}
測量控件自身以及子控件:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth,measureHeight);
MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams();
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
measureWidth- (params.leftMargin + params.rightMargin), MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
measureHeight - (params.topMargin + params.bottomMargin), MeasureSpec.EXACTLY);
measureChildren(childWidthMeasureSpec,childHeightMeasureSpec);
mHeight = getMeasuredHeight();
scrollTo(0,mStartScreen * mHeight);
}
布局子控件:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams();
int childTop = 0;
for (int i = 0; i <getChildCount() ; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE){
if (i==0){
childTop+=params.topMargin;
}
child.layout(params.leftMargin, childTop,
child.getMeasuredWidth() + params.leftMargin, childTop + child.getMeasuredHeight());
childTop = childTop + child.getMeasuredHeight();
}
}
}
到這里,我們初始化的過程就完成了,各個子控件從上到下依次排列,而整個控件大小是一定的,界面上也就只顯示一個子控件,在整個控件滾動的時候形成界面切換效果。
2、重寫dispatchDraw方法,實現(xiàn)3D界面切換效果
在dispatchDraw方法中,重新對各個子控件用Camera和Matrix進行矩陣轉(zhuǎn)換,以此在滾動中實現(xiàn)立體效果,這也是我們今天要了解的重點,根據(jù)我們之前了解的,我們將Camera沿著X軸進行一定的角度旋轉(zhuǎn),兩個控件在滾動過程中就會形成一個夾角,從而出現(xiàn)立體效果,當(dāng)然,一定要注意的是要將控件中心點移至0,0點,否則會看不到效果:
@Override
protected void dispatchDraw(Canvas canvas) {
for (int i = 0;i<getChildCount();i++){
drawScreen(canvas,i,getDrawingTime());
}
}
private void drawScreen(Canvas canvas, int screen, long drawingTime) {
// 得到當(dāng)前子View的高度
final int height = getHeight();
final int scrollHeight = screen * height;
final int scrollY = this.getScrollY();
// 偏移量不足的時
if (scrollHeight > scrollY + height || scrollHeight + height < scrollY) {
return;
}
final View child = getChildAt(screen);
final int faceIndex = screen;
final float currentDegree = getScrollY() * (angle / getMeasuredHeight());
final float faceDegree = currentDegree - faceIndex * angle;
if (faceDegree > 90 || faceDegree < -90) {
return;
}
final float centerY = (scrollHeight < scrollY) ? scrollHeight + height
: scrollHeight;
final float centerX = getWidth() / 2;
final Camera camera = mCamera;
final Matrix matrix = mMatrix;
canvas.save();
camera.save();
camera.rotateX(faceDegree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
canvas.concat(matrix);
drawChild(canvas, child, drawingTime);
canvas.restore();
}
3、重寫onInterceptTouchEvent和onTouchEvent方法實現(xiàn)手指滑動界面切換
在onTouchEvent方法中,根據(jù)手指移動的距離,調(diào)用ScrollBy()方法進行持續(xù)的滾動,在手指抬起的時候,判斷當(dāng)前的速率,如果大于一定值或超過子控件的1/2時,轉(zhuǎn)換當(dāng)前狀態(tài)進行界面切換,否則回滾回當(dāng)前頁面。這里在onInterceptTouchEvent簡單的攔截了當(dāng)前事件,而如果我們需要子控件處理事件時還需要進行處理。
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mTracker == null){
mTracker = VelocityTracker.obtain();
}
mTracker.addMovement(event);
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()){
mScroller.setFinalY(mScroller.getCurrY());
mScroller.abortAnimation();
scrollTo(0,getScrollY());
}
mDownY = y;
break;
case MotionEvent.ACTION_MOVE:
int disY = (int) (mDownY - y);
mDownY = y;
if (mScroller.isFinished()){
recycleMove(disY);
}
break;
case MotionEvent.ACTION_UP:
mTracker.computeCurrentVelocity(1000);
float velocitY = mTracker.getYVelocity();
//滑動的速度大于規(guī)定的速度,或者向上滑動時,上一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_PRE
if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){
STATE = STATE_PRE;
}else if(velocitY < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){
//滑動的速度大于規(guī)定的速度,或者向下滑動時,下一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_NEXT
STATE = STATE_NEXT;
}else{
STATE = STATE_NORMAL;
}
//根據(jù)STATE進行相應(yīng)的變化
changeByState();
if (mTracker != null){
mTracker.recycle();
mTracker = null;
}
break;
}
//返回true,消耗點擊事件
return true;
}
四、最后,附上源碼
public class Custom3DView extends ViewGroup{
private Camera mCamera;
private Matrix mMatrix;
private int mStartScreen = 1;//開始時的item位置
private float mDownY = 0;
private static final int standerSpeed = 2000;
private int mCurScreen = 1;//當(dāng)前item的位置
private int mHeight = 0;//控件的高
private VelocityTracker mTracker;
private Scroller mScroller;
// 旋轉(zhuǎn)的角度,可以進行修改來觀察效果
private float angle = 90;
//三種狀態(tài)
private static final int STATE_PRE = 0;
private static final int STATE_NEXT = 1;
private static final int STATE_NORMAL = 2;
private int STATE = -1;
private float resistance = 1.6f;//滑動阻力
public Custom3DView(Context context) {
this(context,null);
}
public Custom3DView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public Custom3DView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mCamera = new Camera();
mMatrix = new Matrix();
if (mScroller == null){
mScroller = new Scroller(getContext(),new LinearInterpolator());
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth,measureHeight);
MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams();
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
measureWidth- (params.leftMargin + params.rightMargin), MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
measureHeight - (params.topMargin + params.bottomMargin), MeasureSpec.EXACTLY);
measureChildren(childWidthMeasureSpec,childHeightMeasureSpec);
mHeight = getMeasuredHeight();
scrollTo(0,mStartScreen * mHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams();
int childTop = 0;
for (int i = 0; i <getChildCount() ; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE){
if (i==0){
childTop+=params.topMargin;
}
child.layout(params.leftMargin, childTop,
child.getMeasuredWidth() + params.leftMargin, childTop + child.getMeasuredHeight());
childTop = childTop + child.getMeasuredHeight();
}
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
for (int i = 0;i<getChildCount();i++){
drawScreen(canvas,i,getDrawingTime());
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
return false;
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mTracker == null){
mTracker = VelocityTracker.obtain();
}
mTracker.addMovement(event);
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()){
mScroller.setFinalY(mScroller.getCurrY());
mScroller.abortAnimation();
scrollTo(0,getScrollY());
}
mDownY = y;
break;
case MotionEvent.ACTION_MOVE:
int disY = (int) (mDownY - y);
mDownY = y;
if (mScroller.isFinished()){
recycleMove(disY);
}
break;
case MotionEvent.ACTION_UP:
mTracker.computeCurrentVelocity(1000);
float velocitY = mTracker.getYVelocity();
//滑動的速度大于規(guī)定的速度,或者向上滑動時,上一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_PRE
if(velocitY > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)){
STATE = STATE_PRE;
}else if(velocitY < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)){
//滑動的速度大于規(guī)定的速度,或者向下滑動時,下一頁頁面展現(xiàn)出的高度超過1/2。則設(shè)定狀態(tài)為STATE_NEXT
STATE = STATE_NEXT;
}else{
STATE = STATE_NORMAL;
}
//根據(jù)STATE進行相應(yīng)的變化
changeByState();
if (mTracker != null){
mTracker.recycle();
mTracker = null;
}
break;
}
//返回true,消耗點擊事件
return true;
}
private void changeByState() {
switch (STATE) {
case STATE_NORMAL:
toNormalAction();
break;
case STATE_PRE:
toPrePager();
break;
case STATE_NEXT:
toNextPager();
break;
}
invalidate();
}
/**
* 當(dāng)前頁
*/
private void toNormalAction() {
int startY;
int delta;
int duration;
STATE = STATE_NORMAL;
startY = getScrollY();
delta = mHeight * mStartScreen - getScrollY();
duration = (Math.abs(delta)) * 4;
mScroller.startScroll(0, startY, 0, delta, duration);
}
/**
* 上一頁
*/
private void toPrePager() {
int startY;
int delta;
int duration;
STATE = STATE_PRE;
//增加新的頁面
setPre();
//mScroller開始的坐標(biāo)
startY = getScrollY() + mHeight;
setScrollY(startY);
//mScroller移動的距離
delta = -(startY - mStartScreen * mHeight);
duration = (Math.abs(delta)) * 2;
mScroller.startScroll(0, startY, 0, delta, duration);
}
/**
* 下一頁
*/
private void toNextPager() {
int startY;
int delta;
int duration;
STATE = STATE_NEXT;
setNext();
startY = getScrollY() - mHeight;
setScrollY(startY);
delta = mHeight * mStartScreen - startY;
duration = (Math.abs(delta)) * 2;
mScroller.startScroll(0, startY, 0, delta, duration);
}
private void setNext(){
mCurScreen = (mCurScreen + 1) % getChildCount();
int childCount = getChildCount();
View view = getChildAt(0);
removeViewAt(0);
addView(view, childCount - 1);
}
private void setPre(){
mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();
int childCount = getChildCount();
View view = getChildAt(childCount - 1);
removeViewAt(childCount - 1);
addView(view, 0);
}
private void recycleMove(int delta) {
delta = delta % mHeight;
delta = (int) (delta / resistance);
if (Math.abs(delta) > mHeight / 4) {
return;
}
if (getScrollY() <= 0 && mCurScreen <= 0 && delta<=0){
return;
}
if (mHeight*mCurScreen <= getScrollY() && mCurScreen == getChildCount()-1 && delta>= 0){
return;
}
scrollBy(0, delta);
if (getScrollY() < 8 && mCurScreen != 0) {
setPre();
scrollBy(0, mHeight);
} else if (getScrollY() > (getChildCount() - 1) * mHeight - 8) {
setNext();
scrollBy(0, -mHeight);
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
/**
* 畫單個頁面
* @param canvas
* @param screen
* @param drawingTime
*/
private void drawScreen(Canvas canvas, int screen, long drawingTime) {
// 得到當(dāng)前子View的高度
final int height = getHeight();
final int scrollHeight = screen * height;
final int scrollY = this.getScrollY();
// 偏移量不足的時
if (scrollHeight > scrollY + height || scrollHeight + height < scrollY) {
return;
}
final View child = getChildAt(screen);
final int faceIndex = screen;
final float currentDegree = getScrollY() * (angle / getMeasuredHeight());
final float faceDegree = currentDegree - faceIndex * angle;
if (faceDegree > 90 || faceDegree < -90) {
return;
}
final float centerY = (scrollHeight < scrollY) ? scrollHeight + height
: scrollHeight;
final float centerX = getWidth() / 2;
final Camera camera = mCamera;
final Matrix matrix = mMatrix;
canvas.save();
camera.save();
camera.rotateX(faceDegree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
canvas.concat(matrix);
drawChild(canvas, child, drawingTime);
canvas.restore();
}
}
<com.example.matrixcamera.Custom3DView
android:layout_height="250dp"
android:layout_width="250dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_centerInParent="true"
>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/aa"
/>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/bb"
/>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/cc"
/>
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/dd"
/>
</com.example.matrixcamera.Custom3DView>