Android官方开发文档Training系列课程中文版:手势处理之滚动动画及Scroller
原文地址:http://android.xsoftlab.net/training/gestures/scroll.html
在Android中,滑動經(jīng)常由ScrollView類來實現(xiàn)。任何超出容器邊界的布局都應(yīng)該將自己內(nèi)嵌在ScrollView中,以便提供可滾動的視圖效果。自定義滾動只有在特定的場景下才會被用到。這節(jié)課將會描述這樣一種場景:使用scroller顯示一種可滾動的效果。
你可以使用Scroller或者OverScroller來收集一些滑動動畫所需要的數(shù)據(jù)。這兩個類很相似,但是OverScroller包含了一些用于指示已經(jīng)到達(dá)布局邊界的方法。示例InteractiveChart在這里使用了類EdgeEffect(實際上是類EdgeEffectCompat),在用戶到達(dá)布局邊界時會顯示一種”glow“的效果。
Note: 我們推薦使用OverScroller。這個類提供了良好的向后兼容性。還應(yīng)該注意,通常只有在實現(xiàn)滑動自己內(nèi)部的時候,才需要使用到scroller類。如果你將布局嵌入到ScrollView或HorizontalScrollView中的話,那么它們會將滾動的相關(guān)事宜做好。
Scroller用于在時間軸上做動畫滾動效果,它使用了標(biāo)準(zhǔn)的滾動物理學(xué)(摩擦力、速度等等)。Scroller本身不會繪制任何東西。Scroller會隨著時間的變化追蹤移動的偏移量,但是它們不會自動的將這些值應(yīng)用在你的View上。你需要自己獲取這些值并使用,這樣才會使滑動的效果更流暢。
了解滑動術(shù)語
“Scrolling”這個詞在Android中可被翻譯為各種不同的意思,這取決于具體的上下文。
Scrolling是一種viewport(viewport的意思是,你所看到的內(nèi)容的窗口)移動的通用處理過程。當(dāng)scrolling處于x軸及y軸上時,這被稱為“平移(panning)”。示例程序提供了相關(guān)的類:InteractiveChart,演示了滑動的兩種不同類型,dragging(拖動)及flinging(滑動):
- Dragging 該滾動類型在這種情況下發(fā)生:當(dāng)用戶的手指在屏幕上來回滑動時。簡單的Dragging由GestureDetector.OnGestureListener接口的onScroll()方法實現(xiàn)。
- Flinging 該滾動類型在這種情況下發(fā)生:當(dāng)用戶快速在屏幕上滑動并離開屏幕時。在用戶離開了屏幕之后,常理上應(yīng)該保持滾動狀態(tài),并隨之慢慢減速,直到viewport停止移動。Flinging由GestureDetector.OnGestureListener接口的onFling()方法及scroller對象所實現(xiàn)。
將scroller對象與滑動手勢結(jié)合使用是一種共通的方法。你可以重寫onTouchEvent()方法直接處理觸摸事件,并降低滑動的速度來響應(yīng)相應(yīng)的觸摸事件。
實現(xiàn)基礎(chǔ)滾動
這部分章節(jié)將會描述如何使用scroller。下面的代碼段摘自示例應(yīng)用InteractiveChart。它在這里使用了一個GestureDetector對象,重寫了GestureDetector.SimpleOnGestureListener的onFling()方法。它使用了OverScroller來追蹤滑動中的手勢。如果用戶在滑動之后到達(dá)了內(nèi)容的邊緣,那么APP會顯示一個”glow”的效果。
Note: 示例APP InteractiveChart 展示了一個圖表,這個圖標(biāo)可以縮放、平移、滾動等等。在下面的代碼段中,mContentRect代表了矩形的坐標(biāo)點,而繪制圖表的View則居于其中。在給定的時間內(nèi),一個總表的子集將會被繪制到這塊區(qū)域內(nèi)。mCurrentViewport代表了屏幕中當(dāng)前可視的部分圖表。因為像素的偏移量通常被當(dāng)做整型,所以mContentRect的類型是Rect。因為圖形的數(shù)據(jù)范圍是小數(shù)類型,所以mCurrentViewport的類型是RectF。
代碼段的第一部分展示了onFling()方法的實現(xiàn):
// The current viewport. This rectangle represents the currently visible // chart domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private RectF mCurrentViewport = new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); // The current destination rectangle (in pixel coordinates) into which the // chart data should be drawn. private Rect mContentRect; private OverScroller mScroller; private RectF mScrollerStartViewport; ... private final GestureDetector.SimpleOnGestureListener mGestureListener= new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();mScrollerStartViewport.set(mCurrentViewport);// Aborts any active scroll animations and invalidates.mScroller.forceFinished(true);ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);return true;}...@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {fling((int) -velocityX, (int) -velocityY);return true;} }; private void fling(int velocityX, int velocityY) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();// Flings use math in pixels (as opposed to math based on the viewport).Point surfaceSize = computeScrollSurfaceSize();mScrollerStartViewport.set(mCurrentViewport);int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN));int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - mScrollerStartViewport.bottom) / (AXIS_Y_MAX - AXIS_Y_MIN));// Before flinging, aborts the current animation.mScroller.forceFinished(true);// Begins the animationmScroller.fling(// Current scroll positionstartX,startY,velocityX,velocityY,/** Minimum and maximum scroll positions. The minimum scroll * position is generally zero and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset should be 800 pixels.*/0, surfaceSize.x - mContentRect.width(),0, surfaceSize.y - mContentRect.height(),// The edges of the content. This comes into play when using// the EdgeEffect class to draw "glow" overlays.mContentRect.width() / 2,mContentRect.height() / 2);// Invalidates to trigger computeScroll()ViewCompat.postInvalidateOnAnimation(this); }當(dāng)onFling()方法調(diào)用postInvalidateOnAnimation()方法時,它會調(diào)用computeScroll()來更新x及y的值。
大多數(shù)View會將scroller對象的x及y的屬性值直接設(shè)置給方法scrollTo()。而下面的computeScroll()則采用了不同的方法:它調(diào)用computeScrollOffset()來獲取x及y的當(dāng)前坐標(biāo)。當(dāng)顯示的區(qū)域到達(dá)邊界時,這里就會展示一個”glow”的效果,代碼會設(shè)置一個越過邊緣的效果,并會調(diào)用postInvalidateOnAnimation()來使View重新繪制。
// Edge effect / overscroll tracking objects. private EdgeEffectCompat mEdgeEffectTop; private EdgeEffectCompat mEdgeEffectBottom; private EdgeEffectCompat mEdgeEffectLeft; private EdgeEffectCompat mEdgeEffectRight; private boolean mEdgeEffectTopActive; private boolean mEdgeEffectBottomActive; private boolean mEdgeEffectLeftActive; private boolean mEdgeEffectRightActive; @Override public void computeScroll() {super.computeScroll();boolean needsInvalidate = false;// The scroller isn't finished, meaning a fling or programmatic pan // operation is currently active.if (mScroller.computeScrollOffset()) {Point surfaceSize = computeScrollSurfaceSize();int currX = mScroller.getCurrX();int currY = mScroller.getCurrY();boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN|| mCurrentViewport.right < AXIS_X_MAX);boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN|| mCurrentViewport.bottom < AXIS_Y_MAX);/* * If you are zoomed in and currX or currY is* outside of bounds and you're not already* showing overscroll, then render the overscroll* glow edge effect.*/if (canScrollX&& currX < 0&& mEdgeEffectLeft.isFinished()&& !mEdgeEffectLeftActive) {mEdgeEffectLeft.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));mEdgeEffectLeftActive = true;needsInvalidate = true;} else if (canScrollX&& currX > (surfaceSize.x - mContentRect.width())&& mEdgeEffectRight.isFinished()&& !mEdgeEffectRightActive) {mEdgeEffectRight.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));mEdgeEffectRightActive = true;needsInvalidate = true;}if (canScrollY&& currY < 0&& mEdgeEffectTop.isFinished()&& !mEdgeEffectTopActive) {mEdgeEffectTop.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));mEdgeEffectTopActive = true;needsInvalidate = true;} else if (canScrollY&& currY > (surfaceSize.y - mContentRect.height())&& mEdgeEffectBottom.isFinished()&& !mEdgeEffectBottomActive) {mEdgeEffectBottom.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller));mEdgeEffectBottomActive = true;needsInvalidate = true;}...}下面是執(zhí)行實際放大的代碼:
// Custom object that is functionally similar to Scroller Zoomer mZoomer; private PointF mZoomFocalPoint = new PointF(); ... // If a zoom is in progress (either programmatically or via double // touch), performs the zoom. if (mZoomer.computeZoom()) {float newWidth = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.width();float newHeight = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.height();float pointWithinViewportX = (mZoomFocalPoint.x - mScrollerStartViewport.left)/ mScrollerStartViewport.width();float pointWithinViewportY = (mZoomFocalPoint.y - mScrollerStartViewport.top)/ mScrollerStartViewport.height();mCurrentViewport.set(mZoomFocalPoint.x - newWidth * pointWithinViewportX,mZoomFocalPoint.y - newHeight * pointWithinViewportY,mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));constrainViewport();needsInvalidate = true; } if (needsInvalidate) {ViewCompat.postInvalidateOnAnimation(this); }下面是computeScrollSurfaceSize()方法的內(nèi)容。它計算了當(dāng)前可滑動的界面的尺寸,以像素為單位。舉個例子,如果整個圖表區(qū)域是可見的,那么它的值就等于mContentRect。如果圖標(biāo)被放大了200%,那么返回的值就是水平及垂直方向值的兩倍。
private Point computeScrollSurfaceSize() {return new Point((int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)/ mCurrentViewport.width()),(int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)/ mCurrentViewport.height())); }總結(jié)
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:手势处理之滚动动画及Scroller的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 实时事理逻辑知识库(事理图谱)终身学习项
- 下一篇: Android官方开发文档Trainin
