今天在网上看到一篇文章写关于Android实现3D旋转(http://www.ibm.com/developerworks/cn/opensource/os-cn-android-anmt2/index.html?ca=drs-)。出于好奇就写了一个。执行效果例如以下:
以下我们就開始一步步完毕这个效果吧。
实现水平滑动
package com.example.rotation3dview; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug.HierarchyTraceType; import android.view.ViewGroup; import android.widget.ImageView; public class Rote3DView extends ViewGroup{ public Rote3DView(Context context, AttributeSet attrs) { super(context, attrs); initScreens(); } public void initScreens(){ ViewGroup.LayoutParams p = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); for (int i = 0; i < 3; i++) { this.addView(new ImageView(this.getContext()), i, p); } ((ImageView)this.getChildAt(0)).setImageResource(R.drawable.page1); ((ImageView)this.getChildAt(1)).setImageResource(R.drawable.page2); ((ImageView)this.getChildAt(2)).setImageResource(R.drawable.page3); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); for(int i = 0; i< childCount; i++){ final View childView = getChildAt(i); if(childView.getVisibility() != View.GONE){ final int childWidth = childView.getMeasuredWidth(); childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); if(widthMode != MeasureSpec.EXACTLY){ throw new IllegalStateException("仅支持精确尺寸"); } final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if(heightMode != MeasureSpec.EXACTLY){ throw new IllegalStateException("仅支持精确尺寸"); } final int count = getChildCount(); for(int i = 0; i < count; i++){ getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } } private float mDownX; @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = x; break; case MotionEvent.ACTION_MOVE: int disX = (int)(mDownX - x); mDownX = x; scrollBy(disX, 0); break; case MotionEvent.ACTION_UP: break; default: break; } return true; } }
上面的滑动还不太流畅。我们在手势抬起的时候进行推断并处理,代码例如以下:
package com.example.rotation3dview; 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.ViewDebug.HierarchyTraceType; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.Scroller; public class Rote3DView extends ViewGroup{ private int mCurScreen = 1; // 滑动的速度 private static final int SNAP_VELOCITY = 500; private VelocityTracker mVelocityTracker; private int mWidth; private Scroller mScroller; private Camera mCamera; private Matrix mMatrix; // 旋转的角度。能够进行改动来观察效果 private float angle = 90; public Rote3DView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); mCamera = new Camera(); mMatrix = new Matrix(); initScreens(); } public void initScreens(){ ViewGroup.LayoutParams p = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); for (int i = 0; i < 3; i++) { this.addView(new ImageView(this.getContext()), i, p); } ((ImageView)this.getChildAt(0)).setImageResource(R.drawable.page1); ((ImageView)this.getChildAt(1)).setImageResource(R.drawable.page2); ((ImageView)this.getChildAt(2)).setImageResource(R.drawable.page3); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); for(int i = 0; i< childCount; i++){ final View childView = getChildAt(i); if(childView.getVisibility() != View.GONE){ final int childWidth = childView.getMeasuredWidth(); childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); if(widthMode != MeasureSpec.EXACTLY){ throw new IllegalStateException("仅支持精确尺寸"); } final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if(heightMode != MeasureSpec.EXACTLY){ throw new IllegalStateException("仅支持精确尺寸"); } final int count = getChildCount(); for(int i = 0; i < count; i++){ getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } scrollTo(mCurScreen * width, 0); } private float mDownX; @Override public boolean onTouchEvent(MotionEvent event) { if(mVelocityTracker == null){ mVelocityTracker = VelocityTracker.obtain(); } //将当前的触摸事件传递给VelocityTracker对象 mVelocityTracker.addMovement(event); float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } mDownX = x; break; case MotionEvent.ACTION_MOVE: int disX = (int)(mDownX - x); mDownX = x; scrollBy(disX, 0); break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int velocityX = (int) velocityTracker.getXVelocity(); if(velocityX > SNAP_VELOCITY && mCurScreen > 0){ snapToScreen(mCurScreen - 1); }else if(velocityX < -SNAP_VELOCITY && mCurScreen < getChildCount() - 1){ snapToScreen(mCurScreen + 1); }else{ snapToDestination(); } if(mVelocityTracker != null){ mVelocityTracker.recycle(); mVelocityTracker = null; } break; } return true; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } public void snapToDestination(){ setMWidth(); final int destScreen = (getScrollX() + mWidth / 2) / mWidth; snapToScreen(destScreen); } public void snapToScreen(int whichScreen){ whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); setMWidth(); int scrollX = getScrollX(); int startWidth = whichScreen * mWidth; if(scrollX != startWidth){ int delta = 0; int startX = 0; if(whichScreen > mCurScreen){ setPre(); delta = startWidth - scrollX; startX = mWidth - startWidth + scrollX; }else if(whichScreen < mCurScreen){ setNext(); delta = -scrollX; startX = scrollX + mWidth; }else{ startX = scrollX; delta = startWidth - scrollX; } mScroller.startScroll(startX, 0, delta, 0, Math.abs(delta) * 2); invalidate(); } } private void setNext(){ int count = this.getChildCount(); View view = getChildAt(count - 1); removeViewAt(count - 1); addView(view, 0); } private void setPre(){ int count = this.getChildCount(); View view = getChildAt(0); removeViewAt(0); addView(view, count - 1); } private void setMWidth(){ if(mWidth == 0){ mWidth = getWidth(); } } }
实现立体效果
加入例如以下代码:/* * 当进行View滑动时。会导致当前的View无效,该函数的作用是对View进行又一次绘制 调用drawScreen函数 */ @Override protected void dispatchDraw(Canvas canvas) { final long drawingTime = getDrawingTime(); final int count = getChildCount(); for (int i = 0; i < count; i++) { drawScreen(canvas, i, drawingTime); } } public void drawScreen(Canvas canvas, int screen, long drawingTime) { // 得到当前子View的宽度 final int width = getWidth(); final int scrollWidth = screen * width; final int scrollX = this.getScrollX(); // 偏移量不足的时 if (scrollWidth > scrollX + width || scrollWidth + width < scrollX) { return; } final View child = getChildAt(screen); final int faceIndex = screen; final float currentDegree = getScrollX() * (angle / getMeasuredWidth()); final float faceDegree = currentDegree - faceIndex * angle; if (faceDegree > 90 || faceDegree < -90) { return; } final float centerX = (scrollWidth < scrollX) ? scrollWidth + width : scrollWidth; final float centerY = getHeight() / 2; final Camera camera = mCamera; final Matrix matrix = mMatrix; canvas.save(); camera.save(); camera.rotateY(-faceDegree); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); canvas.concat(matrix); drawChild(canvas, child, drawingTime); canvas.restore(); }
项目完整源码下载:https://code.csdn.net/lxq_xsyu/rotation3dview
Git下载地址:git@code.csdn.net:lxq_xsyu/rotation3dview.git