本文行文目錄:
一、Camera與Matrix初步認識
二励翼、Camera與Matrix旋轉(zhuǎn)效果拆分介紹
三、Camera與Matrix實現(xiàn)立體3D切換效果
【本文簡書地址:http://www.reibang.com/p/34e0fe5f9e31】
一、Camera與Matrix初步認識
android中一共有兩個Camera,分別為:
android.graphics.Camera
android.hardware.Camera
今天我們要說的是第一個Camera,第二個主要應用在相機開發(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训桶,不過大大簡化了使用彼乌。有了感性的認識之后泻肯,我們再來看下它的常用API定義:
Camera() 創(chuàng)建一個沒有任何轉(zhuǎn)換效果的新的Camera實例
applyToCanvas(Canvas canvas) 根據(jù)當前的變換計算出相應的矩陣,然后應用到制定的畫布上
getLocationX() 獲取Camera的x坐標
getLocationY() 獲取Camera的y坐標
getLocationZ() 獲取Camera的z坐標
getMatrix(Matrixmatrix) 獲取轉(zhuǎn)換效果后的Matrix對象
restore() 恢復保存的狀態(tài)
rotate(float x, float y, float z) 沿X慰照、Y灶挟、Z坐標進行旋轉(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是后乘拉鹃,當前的矩陣乘以參數(shù)給出的矩陣■耆蹋可以連續(xù)多次使用post膏燕,來完成所需的整個變換。
pre是前乘悟民,參數(shù)給出的矩陣乘以當前的矩陣煌寇。所以操作是在當前矩陣的最前面發(fā)生的。
好了逾雄,上面基本方法先簡單了解下阀溶,現(xiàn)在讓我們看看能做出什么效果,之后回頭再重新看看會有更深的理解鸦泳。
二银锻、Camera與Matrix旋轉(zhuǎn)效果拆分介紹
Camera坐標系研究
Camera的坐標系是左手坐標系。當手機平整的放在桌面上做鹰,X軸是手機的水平方向击纬,Y軸是手機的豎直方向,Z軸是垂直于手機向里的那個方向钾麸。
camera位于坐標點(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)立體效果,當然铛铁,一定要注意的是要將控件中心點移至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方法實現(xiàn)手指滑動界面切換
在onTouchEvent方法中饵逐,根據(jù)手指移動的距離括眠,調(diào)用ScrollBy()方法進行持續(xù)的滾動,在手指抬起的時候倍权,判斷當前的速率掷豺,如果大于一定值或超過子控件的1/2時,轉(zhuǎn)換當前狀態(tài)進行界面切換薄声,否則回滾回當前頁面当船。這里在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();
//滑動的速度大于規(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進行相應的變化
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;
// 旋轉(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進行相應的變化
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();
}
/**
* 當前頁
*/
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開始的坐標
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>