1518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jpage.title=Animating a Scroll Gesture 2518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jparent.title=Using Touch Gestures 3518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jparent.link=index.html 4518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 5518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jtrainingnavtop=true 6518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jnext.title=Handling Multi-Touch Gestures 7518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jnext.link=multi.html 8518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 9518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j@jd:body 10518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 11518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<div id="tb-wrapper"> 12518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<div id="tb"> 13518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 14518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<!-- table of contents --> 15518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<h2>This lesson teaches you to</h2> 16518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<ol> 17e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick <li><a href="#term">Understand Scrolling Terminology</a></li> 18518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li><a href="#scroll">Implement Touch-Based Scrolling</a></li> 19518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j</ol> 20518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 21518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<!-- other docs (NOT javadocs) --> 22518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<h2>You should also read</h2> 23518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 24518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<ul> 25518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide 26518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j </li> 27518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> 28518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> 29518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> 30518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> 31518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j</ul> 32518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 33e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<h2>Try it out</h2> 34e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 35e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<div class="download-box"> 36e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick <a href="{@docRoot}shareables/training/InteractiveChart.zip" 37e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickclass="button">Download the sample</a> 38e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick <p class="filename">InteractiveChart.zip</p> 39e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick</div> 40518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 41518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j</div> 42518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j</div> 43518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 44518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<p>In Android, scrolling is typically achieved by using the 45518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j{@link android.widget.ScrollView} 46518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jclass. Any standard layout that might extend beyond the bounds of its container should be 47518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jnested in a {@link android.widget.ScrollView} to provide a scrollable view that's 48518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jmanaged by the framework. Implementing a custom scroller should only be 49518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jnecessary for special scenarios. This lesson describes such a scenario: displaying 50518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -ja scrolling effect in response to touch gestures using <em>scrollers</em>. 51518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 52518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 53518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<p>You can use scrollers ({@link android.widget.Scroller} or {@link 54518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jandroid.widget.OverScroller}) to collect the data you need to produce a 55e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickscrolling animation in response to a touch event. They are similar, but 56e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.widget.OverScroller} 57e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickincludes methods for indicating to users that they've reached the content edges 58e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickafter a pan or fling gesture. The {@code InteractiveChart} sample 5976dfc02135abae5337b48fe99a1b8c0a7d95e33akmccormickuses the {@link android.widget.EdgeEffect} class 60e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick(actually the {@link android.support.v4.widget.EdgeEffectCompat} class) 61e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickto display a "glow" effect when users reach the content edges.</p> 62e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 63e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p class="note"><strong>Note:</strong> We recommend that you 64e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickuse {@link android.widget.OverScroller} rather than {@link 65e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickandroid.widget.Scroller} for scrolling animations. 66e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.widget.OverScroller} provides the best backward 67e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickcompatibility with older devices. 68e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<br /> 69e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickAlso note that you generally only need to use scrollers 70e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickwhen implementing scrolling yourself. {@link android.widget.ScrollView} and 71e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.widget.HorizontalScrollView} do all of this for you if you nest your 72e23f97ba6ee5214679fff61e33deda075cfd741dkmccormicklayout within them. 73e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick</p> 74e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 75518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 76518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j<p>A scroller is used to animate scrolling over time, using platform-standard 77518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jscrolling physics (friction, velocity, etc.). The scroller itself doesn't 78518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jactually draw anything. Scrollers track scroll offsets for you over time, but 79518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jthey don't automatically apply those positions to your view. It's your 80518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jresponsibility to get and apply new coordinates at a rate that will make the 81518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jscrolling animation look smooth.</p> 82518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 83518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 84518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 85e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<h2 id="term">Understand Scrolling Terminology</h2> 86518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 87e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p> 88e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 89e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' 90e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickof content you're looking at). When scrolling is in both the x and y axes, it's called 91e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates 92e23f97ba6ee5214679fff61e33deda075cfd741dkmccormicktwo different types of scrolling, dragging and flinging:</p> 93e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<ul> 94e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her 95e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickfinger across the touch screen. Simple dragging is often implemented by overriding 96e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 97e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 98d7abd97f64fd35d333655b5b71c0844a1e7a4d97Scott Main<a href="scale.html">Dragging and Scaling</a>.</li> 99518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 100e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 101e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickdrags and lifts her finger quickly. After the user lifts her finger, you generally 102e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickwant to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. 103e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickFlinging can be implemented by overriding 104e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.OnGestureListener#onFling onFling()} 105e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickin {@link android.view.GestureDetector.OnGestureListener}, and by using 106e23f97ba6ee5214679fff61e33deda075cfd741dkmccormicka scroller object. This is the use 107e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickcase that is the topic of this lesson.</li> 108e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick</ul> 109518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 110e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>It's common to use scroller objects 111e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickin conjunction with a fling gesture, but they 112518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jcan be used in pretty much any context where you want the UI to display 113e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickscrolling in response to a touch event. For example, you could override 114e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.View#onTouchEvent onTouchEvent()} to process touch 115e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickevents directly, and produce a scrolling effect or a "snapping to page" animation 116e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickin response to those touch events.</p> 117e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 118e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 119e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<h2 id="#scroll">Implement Touch-Based Scrolling</h2> 120e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 121e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>This section describes how to use a scroller. 122e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickThe snippet shown below comes from the {@code InteractiveChart} sample 123e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprovided with this class. 124e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickIt uses a 125e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector}, and overrides the 126e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.SimpleOnGestureListener} method 127e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. 128e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickIt uses {@link android.widget.OverScroller} to track the fling gesture. 129e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickIf the user reaches the content edges 130e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickafter the fling gesture, the app displays a "glow" effect. 131e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick</p> 132518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 133e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a 134e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickchart that you can zoom, pan, scroll, and so on. In the following snippet, 135e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@code mContentRect} represents the rectangle coordinates within the view that the chart 136e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickwill be drawn into. At any given time, a subset of the total chart domain and range are drawn 137e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickinto this rectangular area. 138e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@code mCurrentViewport} represents the portion of the chart that is currently 139e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickvisible in the screen. Because pixel offsets are generally treated as integers, 140e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the 141e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickgraph domain and range are decimal/float values, {@code mCurrentViewport} is of 142e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickthe type {@link android.graphics.RectF}.</p> 143518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 144e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>The first part of the snippet shows the implementation of 145e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p> 146e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 147e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<pre>// The current viewport. This rectangle represents the currently visible 148e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// chart domain and range. The viewport is the part of the app that the 149e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// user manipulates via touch gestures. 150e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate RectF mCurrentViewport = 151e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); 152e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 153e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// The current destination rectangle (in pixel coordinates) into which the 154e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// chart data should be drawn. 155e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate Rect mContentRect; 156e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 157e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate OverScroller mScroller; 158e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate RectF mScrollerStartViewport; 159e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick... 160e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate final GestureDetector.SimpleOnGestureListener mGestureListener 161518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j = new GestureDetector.SimpleOnGestureListener() { 162518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j @Override 163518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j public boolean onDown(MotionEvent e) { 164e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Initiates the decay phase of any active edge effects. 165e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick releaseEdgeEffects(); 166e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.set(mCurrentViewport); 167e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Aborts any active scroll animations and invalidates. 168518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j mScroller.forceFinished(true); 169e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); 170518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j return true; 171518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j } 172e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick ... 173518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j @Override 174518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j public boolean onFling(MotionEvent e1, MotionEvent e2, 175518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j float velocityX, float velocityY) { 176e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick fling((int) -velocityX, (int) -velocityY); 177518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j return true; 178518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j } 179518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j}; 180518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 181e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate void fling(int velocityX, int velocityY) { 182e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Initiates the decay phase of any active edge effects. 183e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick releaseEdgeEffects(); 184e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Flings use math in pixels (as opposed to math based on the viewport). 185e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick Point surfaceSize = computeScrollSurfaceSize(); 186e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.set(mCurrentViewport); 187e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - 188e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick AXIS_X_MIN) / ( 189e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick AXIS_X_MAX - AXIS_X_MIN)); 190e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - 191e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.bottom) / ( 192e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick AXIS_Y_MAX - AXIS_Y_MIN)); 193e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Before flinging, aborts the current animation. 194e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScroller.forceFinished(true); 195e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Begins the animation 196e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScroller.fling( 197e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Current scroll position 198e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick startX, 199e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick startY, 200e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick velocityX, 201e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick velocityY, 202e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick /* 203e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * Minimum and maximum scroll positions. The minimum scroll 204e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * position is generally zero and the maximum scroll position 205e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * is generally the content size less the screen size. So if the 206e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * content width is 1000 pixels and the screen width is 200 207e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * pixels, the maximum scroll offset should be 800 pixels. 208e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick */ 209e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 0, surfaceSize.x - mContentRect.width(), 210e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 0, surfaceSize.y - mContentRect.height(), 211e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // The edges of the content. This comes into play when using 212e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // the EdgeEffect class to draw "glow" overlays. 213e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mContentRect.width() / 2, 214e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mContentRect.height() / 2); 215e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // Invalidates to trigger computeScroll() 216e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick ViewCompat.postInvalidateOnAnimation(this); 217e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick}</pre> 218e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 219e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls 220e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, 221e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickit triggers 222e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.View#computeScroll computeScroll()} to update the values for x and y. 223e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickThis is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p> 224e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 225e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>Most views pass the scroller object's x and y position directly to 226e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.view.View#scrollTo scrollTo()}. 227e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickThe following implementation of {@link android.view.View#computeScroll computeScroll()} 228e23f97ba6ee5214679fff61e33deda075cfd741dkmccormicktakes a different approach—it calls 229e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current 230e23f97ba6ee5214679fff61e33deda075cfd741dkmccormicklocation of x and y. When the criteria for displaying an overscroll "glow" edge effect are met 231e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), 232e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickthe code sets up the overscroll glow effect and calls 233e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} 234e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickto trigger an invalidate on the view:</p> 235e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 236e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<pre>// Edge effect / overscroll tracking objects. 237e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate EdgeEffectCompat mEdgeEffectTop; 238e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate EdgeEffectCompat mEdgeEffectBottom; 239e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate EdgeEffectCompat mEdgeEffectLeft; 240e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate EdgeEffectCompat mEdgeEffectRight; 241e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 242e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate boolean mEdgeEffectTopActive; 243e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate boolean mEdgeEffectBottomActive; 244e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate boolean mEdgeEffectLeftActive; 245e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate boolean mEdgeEffectRightActive; 246e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 247518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j@Override 248518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -jpublic void computeScroll() { 249518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j super.computeScroll(); 250518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 251e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick boolean needsInvalidate = false; 252e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 253e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // The scroller isn't finished, meaning a fling or programmatic pan 254e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick // operation is currently active. 255518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j if (mScroller.computeScrollOffset()) { 256e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick Point surfaceSize = computeScrollSurfaceSize(); 257518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j int currX = mScroller.getCurrX(); 258518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j int currY = mScroller.getCurrY(); 259518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 260e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN 261e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick || mCurrentViewport.right < AXIS_X_MAX); 262e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN 263e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick || mCurrentViewport.bottom < AXIS_Y_MAX); 264518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 265e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick /* 266e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * If you are zoomed in and currX or currY is 267e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * outside of bounds and you're not already 268e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * showing overscroll, then render the overscroll 269e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick * glow edge effect. 270e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick */ 271e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick if (canScrollX 272e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && currX < 0 273e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && mEdgeEffectLeft.isFinished() 274e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && !mEdgeEffectLeftActive) { 275e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectLeft.onAbsorb((int) 276e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick OverScrollerCompat.getCurrVelocity(mScroller)); 277e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectLeftActive = true; 278e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick needsInvalidate = true; 279e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick } else if (canScrollX 280e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && currX > (surfaceSize.x - mContentRect.width()) 281e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && mEdgeEffectRight.isFinished() 282e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && !mEdgeEffectRightActive) { 283e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectRight.onAbsorb((int) 284e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick OverScrollerCompat.getCurrVelocity(mScroller)); 285e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectRightActive = true; 286e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick needsInvalidate = true; 287e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick } 288518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 289e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick if (canScrollY 290e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && currY < 0 291e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && mEdgeEffectTop.isFinished() 292e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && !mEdgeEffectTopActive) { 293e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectTop.onAbsorb((int) 294e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick OverScrollerCompat.getCurrVelocity(mScroller)); 295e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectTopActive = true; 296e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick needsInvalidate = true; 297e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick } else if (canScrollY 298e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && currY > (surfaceSize.y - mContentRect.height()) 299e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && mEdgeEffectBottom.isFinished() 300e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick && !mEdgeEffectBottomActive) { 301e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectBottom.onAbsorb((int) 302e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick OverScrollerCompat.getCurrVelocity(mScroller)); 303e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mEdgeEffectBottomActive = true; 304e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick needsInvalidate = true; 305e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick } 306e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick ... 307e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick }</pre> 308e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 309e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>Here is the section of the code that performs the actual zoom:</p> 310e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 311e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<pre>// Custom object that is functionally similar to Scroller 312e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickZoomer mZoomer; 313e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickprivate PointF mZoomFocalPoint = new PointF(); 314e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick... 315e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 316e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// If a zoom is in progress (either programmatically or via double 317e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick// touch), performs the zoom. 318e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickif (mZoomer.computeZoom()) { 319e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick float newWidth = (1f - mZoomer.getCurrZoom()) * 320e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.width(); 321e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick float newHeight = (1f - mZoomer.getCurrZoom()) * 322e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.height(); 323e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick float pointWithinViewportX = (mZoomFocalPoint.x - 324e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.left) 325e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick / mScrollerStartViewport.width(); 326e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick float pointWithinViewportY = (mZoomFocalPoint.y - 327e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mScrollerStartViewport.top) 328e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick / mScrollerStartViewport.height(); 329e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mCurrentViewport.set( 330e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mZoomFocalPoint.x - newWidth * pointWithinViewportX, 331e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mZoomFocalPoint.y - newHeight * pointWithinViewportY, 332e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), 333e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)); 334e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick constrainViewport(); 335e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick needsInvalidate = true; 336e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick} 337e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickif (needsInvalidate) { 338e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick ViewCompat.postInvalidateOnAnimation(this); 339e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick} 340e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick</pre> 341e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 342e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It 343e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickcomputes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, 344e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickthis is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, 345e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickthe returned size will be twice as large horizontally and vertically.</p> 346e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 347e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<pre>private Point computeScrollSurfaceSize() { 348e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick return new Point( 349e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) 350e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick / mCurrentViewport.width()), 351e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) 352e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick / mCurrentViewport.height())); 353518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j}</pre> 354518edbfa97dbdc366f1e03c62ae275c388ec20ef&& repo sync -j 355e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<p>For another example of scroller usage, see the 356e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 357e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, 358e23f97ba6ee5214679fff61e33deda075cfd741dkmccormickand uses scrolling to implement the "snapping to page" animation.</p> 359e23f97ba6ee5214679fff61e33deda075cfd741dkmccormick 360