Home | History | Annotate | Download | only in gestures
      1 page.title=Animating a Scroll Gesture
      2 parent.title=Using Touch Gestures
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 next.title=Handling Multi-Touch Gestures
      7 next.link=multi.html
      8 
      9 @jd:body
     10 
     11 <div id="tb-wrapper">
     12 <div id="tb">
     13 
     14 <!-- table of contents -->
     15 <h2>This lesson teaches you to</h2>
     16 <ol>
     17   <li><a href="#term">Understand Scrolling Terminology</a></li>
     18   <li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
     19 </ol>
     20 
     21 <!-- other docs (NOT javadocs) -->
     22 <h2>You should also read</h2>
     23 
     24 <ul>
     25     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     26     </li>
     27     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
     28     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     29     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     30     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
     31 </ul>
     32 
     33 <h2>Try it out</h2>
     34 
     35 <div class="download-box">
     36   <a href="{@docRoot}shareables/training/InteractiveChart.zip"
     37 class="button">Download the sample</a>
     38  <p class="filename">InteractiveChart.zip</p>
     39 </div>
     40 
     41 </div>
     42 </div>
     43 
     44 <p>In Android, scrolling is typically achieved by using the 
     45 {@link android.widget.ScrollView}
     46 class. Any standard layout that might extend beyond the bounds of its container should be 
     47 nested in a {@link android.widget.ScrollView} to provide a scrollable view that's 
     48 managed by the framework. Implementing a custom scroller should only be 
     49 necessary for special scenarios. This lesson describes such a scenario: displaying 
     50 a scrolling effect in response to touch gestures using <em>scrollers</em>.
     51 
     52 
     53 <p>You can use scrollers ({@link android.widget.Scroller} or {@link
     54 android.widget.OverScroller}) to collect the data you need to produce a
     55 scrolling animation in response to a touch event. They are similar, but
     56 {@link android.widget.OverScroller}
     57 includes methods for indicating to users that they've reached the content edges 
     58 after a pan or fling gesture. The {@code InteractiveChart} sample 
     59 uses the {@link android.widget.EdgeEffect} class
     60 (actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
     61 to display a "glow" effect when users reach the content edges.</p>
     62 
     63 <p class="note"><strong>Note:</strong> We recommend that you
     64 use {@link android.widget.OverScroller} rather than {@link
     65 android.widget.Scroller} for scrolling animations.
     66 {@link android.widget.OverScroller} provides the best backward
     67 compatibility with older devices.
     68 <br />
     69 Also note that you generally only need to use scrollers
     70 when implementing scrolling yourself. {@link android.widget.ScrollView} and
     71 {@link android.widget.HorizontalScrollView} do all of this for you if you nest your 
     72 layout within them.
     73 </p>
     74 
     75 
     76 <p>A scroller is used  to animate scrolling over time, using platform-standard
     77 scrolling physics (friction, velocity, etc.). The scroller itself doesn't
     78 actually draw anything. Scrollers track scroll offsets for you over time, but
     79 they don't  automatically apply those positions to your view. It's your
     80 responsibility to get and apply new coordinates at a rate that will make the
     81 scrolling animation look smooth.</p>
     82 
     83 
     84 
     85 <h2 id="term">Understand Scrolling Terminology</h2>
     86 
     87 <p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
     88 
     89 <p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' 
     90 of content you're looking at). When scrolling is in both the x and y axes, it's called 
     91 <em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates 
     92 two different types of scrolling, dragging and flinging:</p>
     93 <ul>
     94     <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her 
     95 finger across the touch screen. Simple dragging is often implemented by overriding 
     96 {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
     97 {@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 
     98 <a href="dragging.jd">Dragging and Scaling</a>.</li>
     99 
    100     <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 
    101 drags and lifts her finger quickly. After the user lifts her finger, you generally 
    102 want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. 
    103 Flinging can be implemented by overriding 
    104 {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} 
    105 in {@link android.view.GestureDetector.OnGestureListener}, and by using 
    106 a scroller object. This is the use 
    107 case that is the topic of this lesson.</li>
    108 </ul>
    109 
    110 <p>It's common to use scroller objects 
    111 in conjunction with a fling gesture, but they
    112 can be used in pretty much any context where you want the UI to display
    113 scrolling in response to a touch event. For example, you could override  
    114 {@link android.view.View#onTouchEvent onTouchEvent()} to process touch 
    115 events directly, and produce a scrolling effect or a "snapping to page" animation 
    116 in response to those touch events.</p>
    117 
    118 
    119 <h2 id="#scroll">Implement Touch-Based Scrolling</h2> 
    120 
    121 <p>This section describes how to use a scroller.
    122 The snippet shown below comes from the {@code InteractiveChart} sample 
    123 provided with this class.
    124 It uses a 
    125 {@link android.view.GestureDetector}, and overrides the  
    126 {@link android.view.GestureDetector.SimpleOnGestureListener} method 
    127 {@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
    128 It uses {@link android.widget.OverScroller} to track the fling gesture.
    129 If the user reaches the content edges 
    130 after the fling gesture, the app displays a "glow" effect.
    131 </p>
    132 
    133 <p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a 
    134 chart that you can zoom, pan, scroll, and so on. In the following snippet, 
    135 {@code mContentRect} represents the rectangle coordinates within the view that the chart 
    136 will be drawn into. At any given time, a subset of the total chart domain and range are drawn 
    137 into this rectangular area. 
    138 {@code mCurrentViewport} represents the portion of the chart that is currently 
    139 visible in the screen. Because pixel offsets are generally treated as integers, 
    140 {@code mContentRect} is of the type {@link android.graphics.Rect}. Because the 
    141 graph domain and range are decimal/float values, {@code mCurrentViewport} is of 
    142 the type {@link android.graphics.RectF}.</p>
    143 
    144 <p>The first part of the snippet shows the implementation of 
    145 {@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
    146 
    147 <pre>// The current viewport. This rectangle represents the currently visible 
    148 // chart domain and range. The viewport is the part of the app that the
    149 // user manipulates via touch gestures.
    150 private RectF mCurrentViewport = 
    151         new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
    152 
    153 // The current destination rectangle (in pixel coordinates) into which the 
    154 // chart data should be drawn.
    155 private Rect mContentRect;
    156 
    157 private OverScroller mScroller;
    158 private RectF mScrollerStartViewport;
    159 ...
    160 private final GestureDetector.SimpleOnGestureListener mGestureListener
    161         = new GestureDetector.SimpleOnGestureListener() {
    162     &#64;Override
    163     public boolean onDown(MotionEvent e) {
    164         // Initiates the decay phase of any active edge effects.
    165         releaseEdgeEffects();
    166         mScrollerStartViewport.set(mCurrentViewport);
    167         // Aborts any active scroll animations and invalidates.
    168         mScroller.forceFinished(true);
    169         ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
    170         return true;
    171     }
    172     ...
    173     &#64;Override
    174     public boolean onFling(MotionEvent e1, MotionEvent e2, 
    175             float velocityX, float velocityY) {
    176         fling((int) -velocityX, (int) -velocityY);
    177         return true;
    178     }
    179 };
    180 
    181 private void fling(int velocityX, int velocityY) {
    182     // Initiates the decay phase of any active edge effects.
    183     releaseEdgeEffects();
    184     // Flings use math in pixels (as opposed to math based on the viewport).
    185     Point surfaceSize = computeScrollSurfaceSize();
    186     mScrollerStartViewport.set(mCurrentViewport);
    187     int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - 
    188             AXIS_X_MIN) / (
    189             AXIS_X_MAX - AXIS_X_MIN));
    190     int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - 
    191             mScrollerStartViewport.bottom) / (
    192             AXIS_Y_MAX - AXIS_Y_MIN));
    193     // Before flinging, aborts the current animation.
    194     mScroller.forceFinished(true);
    195     // Begins the animation
    196     mScroller.fling(
    197             // Current scroll position
    198             startX,
    199             startY,
    200             velocityX,
    201             velocityY,
    202             /*
    203              * Minimum and maximum scroll positions. The minimum scroll 
    204              * position is generally zero and the maximum scroll position 
    205              * is generally the content size less the screen size. So if the 
    206              * content width is 1000 pixels and the screen width is 200  
    207              * pixels, the maximum scroll offset should be 800 pixels.
    208              */
    209             0, surfaceSize.x - mContentRect.width(),
    210             0, surfaceSize.y - mContentRect.height(),
    211             // The edges of the content. This comes into play when using
    212             // the EdgeEffect class to draw "glow" overlays.
    213             mContentRect.width() / 2,
    214             mContentRect.height() / 2);
    215     // Invalidates to trigger computeScroll()
    216     ViewCompat.postInvalidateOnAnimation(this);
    217 }</pre>
    218 
    219 <p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls 
    220 {@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, 
    221 it triggers 
    222 {@link android.view.View#computeScroll computeScroll()} to update the values for x and y. 
    223 This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
    224 
    225 <p>Most views pass the scroller object's x and y position directly to 
    226 {@link android.view.View#scrollTo scrollTo()}. 
    227 The following implementation of {@link android.view.View#computeScroll computeScroll()} 
    228 takes a different approach&mdash;it calls 
    229 {@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current 
    230 location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met 
    231 (the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), 
    232 the code sets up the overscroll glow effect and calls 
    233 {@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} 
    234 to trigger an invalidate on the view:</p>
    235 
    236 <pre>// Edge effect / overscroll tracking objects.
    237 private EdgeEffectCompat mEdgeEffectTop;
    238 private EdgeEffectCompat mEdgeEffectBottom;
    239 private EdgeEffectCompat mEdgeEffectLeft;
    240 private EdgeEffectCompat mEdgeEffectRight;
    241 
    242 private boolean mEdgeEffectTopActive;
    243 private boolean mEdgeEffectBottomActive;
    244 private boolean mEdgeEffectLeftActive;
    245 private boolean mEdgeEffectRightActive;
    246 
    247 &#64;Override
    248 public void computeScroll() {
    249     super.computeScroll();
    250 
    251     boolean needsInvalidate = false;
    252 
    253     // The scroller isn't finished, meaning a fling or programmatic pan 
    254     // operation is currently active.
    255     if (mScroller.computeScrollOffset()) {
    256         Point surfaceSize = computeScrollSurfaceSize();
    257         int currX = mScroller.getCurrX();
    258         int currY = mScroller.getCurrY();
    259 
    260         boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN
    261                 || mCurrentViewport.right < AXIS_X_MAX);
    262         boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN
    263                 || mCurrentViewport.bottom < AXIS_Y_MAX);
    264 
    265         /*          
    266          * If you are zoomed in and currX or currY is
    267          * outside of bounds and you're not already
    268          * showing overscroll, then render the overscroll
    269          * glow edge effect.
    270          */
    271         if (canScrollX
    272                 && currX < 0
    273                 && mEdgeEffectLeft.isFinished()
    274                 && !mEdgeEffectLeftActive) {
    275             mEdgeEffectLeft.onAbsorb((int) 
    276                     OverScrollerCompat.getCurrVelocity(mScroller));
    277             mEdgeEffectLeftActive = true;
    278             needsInvalidate = true;
    279         } else if (canScrollX
    280                 && currX > (surfaceSize.x - mContentRect.width())
    281                 && mEdgeEffectRight.isFinished()
    282                 && !mEdgeEffectRightActive) {
    283             mEdgeEffectRight.onAbsorb((int) 
    284                     OverScrollerCompat.getCurrVelocity(mScroller));
    285             mEdgeEffectRightActive = true;
    286             needsInvalidate = true;
    287         }
    288 
    289         if (canScrollY
    290                 && currY < 0
    291                 && mEdgeEffectTop.isFinished()
    292                 && !mEdgeEffectTopActive) {
    293             mEdgeEffectTop.onAbsorb((int) 
    294                     OverScrollerCompat.getCurrVelocity(mScroller));
    295             mEdgeEffectTopActive = true;
    296             needsInvalidate = true;
    297         } else if (canScrollY
    298                 && currY > (surfaceSize.y - mContentRect.height())
    299                 && mEdgeEffectBottom.isFinished()
    300                 && !mEdgeEffectBottomActive) {
    301             mEdgeEffectBottom.onAbsorb((int) 
    302                     OverScrollerCompat.getCurrVelocity(mScroller));
    303             mEdgeEffectBottomActive = true;
    304             needsInvalidate = true;
    305         }
    306         ...
    307     }</pre>
    308 
    309 <p>Here is the section of the code that performs the actual zoom:</p>
    310 
    311 <pre>// Custom object that is functionally similar to Scroller
    312 Zoomer mZoomer;
    313 private PointF mZoomFocalPoint = new PointF();
    314 ...
    315 
    316 // If a zoom is in progress (either programmatically or via double
    317 // touch), performs the zoom.
    318 if (mZoomer.computeZoom()) {
    319     float newWidth = (1f - mZoomer.getCurrZoom()) * 
    320             mScrollerStartViewport.width();
    321     float newHeight = (1f - mZoomer.getCurrZoom()) * 
    322             mScrollerStartViewport.height();
    323     float pointWithinViewportX = (mZoomFocalPoint.x - 
    324             mScrollerStartViewport.left)
    325             / mScrollerStartViewport.width();
    326     float pointWithinViewportY = (mZoomFocalPoint.y - 
    327             mScrollerStartViewport.top)
    328             / mScrollerStartViewport.height();
    329     mCurrentViewport.set(
    330             mZoomFocalPoint.x - newWidth * pointWithinViewportX,
    331             mZoomFocalPoint.y - newHeight * pointWithinViewportY,
    332             mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
    333             mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
    334     constrainViewport();
    335     needsInvalidate = true;
    336 }
    337 if (needsInvalidate) {
    338     ViewCompat.postInvalidateOnAnimation(this);
    339 }
    340 </pre>
    341 
    342 <p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It 
    343 computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, 
    344 this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, 
    345 the returned size will be twice as large horizontally and vertically.</p>
    346 
    347 <pre>private Point computeScrollSurfaceSize() {
    348     return new Point(
    349             (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
    350                     / mCurrentViewport.width()),
    351             (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
    352                     / mCurrentViewport.height()));
    353 }</pre>
    354 
    355 <p>For another example of scroller usage, see the 
    356 <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
    357 {@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, 
    358 and uses scrolling to implement the "snapping to page" animation.</p>
    359 
    360