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    &#64;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    &#64;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&mdash;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&#64;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