summaryrefslogtreecommitdiffstats
path: root/docs/html/training/gestures/scroll.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/training/gestures/scroll.jd')
-rw-r--r--docs/html/training/gestures/scroll.jd339
1 files changed, 272 insertions, 67 deletions
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 8576948..bd1537a 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -14,6 +14,7 @@ next.link=multi.html
<!-- table of contents -->
<h2>This lesson teaches you to</h2>
<ol>
+ <li><a href="#term">Understand Scrolling Terminology</a></li>
<li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
</ol>
@@ -24,12 +25,18 @@ next.link=multi.html
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
@@ -45,7 +52,26 @@ a scrolling effect in response to touch gestures using <em>scrollers</em>.
<p>You can use scrollers ({@link android.widget.Scroller} or {@link
android.widget.OverScroller}) to collect the data you need to produce a
-scrolling animation in response to a touch event.</p>
+scrolling animation in response to a touch event. They are similar, but
+{@link android.widget.OverScroller}
+includes methods for indicating to users that they've reached the content edges
+after a pan or fling gesture. The {@code InteractiveChart} sample
+uses the the {@link android.widget.EdgeEffect} class
+(actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
+to display a "glow" effect when users reach the content edges.</p>
+
+<p class="note"><strong>Note:</strong> We recommend that you
+use {@link android.widget.OverScroller} rather than {@link
+android.widget.Scroller} for scrolling animations.
+{@link android.widget.OverScroller} provides the best backward
+compatibility with older devices.
+<br />
+Also note that you generally only need to use scrollers
+when implementing scrolling yourself. {@link android.widget.ScrollView} and
+{@link android.widget.HorizontalScrollView} do all of this for you if you nest your
+layout within them.
+</p>
+
<p>A scroller is used to animate scrolling over time, using platform-standard
scrolling physics (friction, velocity, etc.). The scroller itself doesn't
@@ -54,101 +80,280 @@ they don't automatically apply those positions to your view. It's your
responsibility to get and apply new coordinates at a rate that will make the
scrolling animation look smooth.</p>
-<p class="note"><strong>Note:</strong> You generally only need to use scrollers
-when implementing scrolling yourself. {@link android.widget.ScrollView} and
-{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p>
-<h2 id = "scroll">Implement Touch-Based Scrolling</h2>
+<h2 id="term">Understand Scrolling Terminology</h2>
-<p>This snippet illustrates the basics of using a scroller. It uses a
-{@link android.view.GestureDetector}, and overrides the
-{@link android.view.GestureDetector.SimpleOnGestureListener} methods
-{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and
-{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also
-overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}
-to return {@code false} since you don't need to animate a scroll.</p>
+<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
+
+<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window'
+of content you're looking at). When scrolling is in both the x and y axes, it's called
+<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates
+two different types of scrolling, dragging and flinging:</p>
+<ul>
+ <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her
+finger across the touch screen. Simple dragging is often implemented by overriding
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in
+{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see
+<a href="dragging.jd">Dragging and Scaling</a>.</li>
+ <li><strong>Flinging</strong> is the type of scrolling that occurs when a user
+drags and lifts her finger quickly. After the user lifts her finger, you generally
+want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving.
+Flinging can be implemented by overriding
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}
+in {@link android.view.GestureDetector.OnGestureListener}, and by using
+a scroller object. This is the use
+case that is the topic of this lesson.</li>
+</ul>
-<p>It's common to use scrollers in conjunction with a fling gesture, but they
+<p>It's common to use scroller objects
+in conjunction with a fling gesture, but they
can be used in pretty much any context where you want the UI to display
-scrolling in response to a touch event. For example, you could override {@link
-android.view.View#onTouchEvent onTouchEvent()} to process touch events directly,
-and produce a scrolling effect in response to those touch events.</p>
+scrolling in response to a touch event. For example, you could override
+{@link android.view.View#onTouchEvent onTouchEvent()} to process touch
+events directly, and produce a scrolling effect or a "snapping to page" animation
+in response to those touch events.</p>
+
+
+<h2 id="#scroll">Implement Touch-Based Scrolling</h2>
+
+<p>This section describes how to use a scroller.
+The snippet shown below comes from the {@code InteractiveChart} sample
+provided with this class.
+It uses a
+{@link android.view.GestureDetector}, and overrides the
+{@link android.view.GestureDetector.SimpleOnGestureListener} method
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
+It uses {@link android.widget.OverScroller} to track the fling gesture.
+If the user reaches the content edges
+after the fling gesture, the app displays a "glow" effect.
+</p>
-<pre>
-private OverScroller mScroller = new OverScroller(context);
+<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a
+chart that you can zoom, pan, scroll, and so on. In the following snippet,
+{@code mContentRect} represents the rectangle coordinates within the view that the chart
+will be drawn into. At any given time, a subset of the total chart domain and range are drawn
+into this rectangular area.
+{@code mCurrentViewport} represents the portion of the chart that is currently
+visible in the screen. Because pixel offsets are generally treated as integers,
+{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the
+graph domain and range are decimal/float values, {@code mCurrentViewport} is of
+the type {@link android.graphics.RectF}.</p>
-private GestureDetector.SimpleOnGestureListener mGestureListener
+<p>The first part of the snippet shows the implementation of
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
+
+<pre>// 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() {
&#64;Override
public boolean onDown(MotionEvent e) {
- // Abort any active scroll animations and invalidate.
+ // Initiates the decay phase of any active edge effects.
+ releaseEdgeEffects();
+ mScrollerStartViewport.set(mCurrentViewport);
+ // Aborts any active scroll animations and invalidates.
mScroller.forceFinished(true);
- // There is also a compatibility version:
- // ViewCompat.postInvalidateOnAnimation
- postInvalidateOnAnimation();
+ ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
return true;
}
-
- &#64;Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- // You don't use a scroller in onScroll because you don't need to animate
- // a scroll. The scroll occurs instantly in response to touch feedback.
- return false;
- }
-
+ ...
&#64;Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
- // Before flinging, abort the current animation.
- mScroller.forceFinished(true);
- // Begin the scroll animation
- mScroller.fling(
- // Current scroll position
- startX,
- startY,
- // Velocities, negated for natural touch response
- (int) -velocityX,
- (int) -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.
- minX, maxX,
- minY, maxY,
- // The maximum overscroll bounds. This is useful when using
- // the EdgeEffect class to draw overscroll "glow" overlays.
- mContentRect.width() / 2,
- mContentRect.height() / 2);
- // Invalidate to trigger computeScroll()
- postInvalidateOnAnimation();
+ 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 animation
+ mScroller.fling(
+ // Current scroll position
+ startX,
+ 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);
+}</pre>
+
+<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()},
+it triggers
+{@link android.view.View#computeScroll computeScroll()} to update the values for x and y.
+This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
+
+<p>Most views pass the scroller object's x and y position directly to
+{@link android.view.View#scrollTo scrollTo()}.
+The following implementation of {@link android.view.View#computeScroll computeScroll()}
+takes a different approach&mdash;it calls
+{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current
+location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met
+(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll),
+the code sets up the overscroll glow effect and calls
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}
+to trigger an invalidate on the view:</p>
+
+<pre>// 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;
+
&#64;Override
public void computeScroll() {
super.computeScroll();
- // Compute the current scroll offsets. If this returns true, then the
- // scroll has not yet finished.
+ 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();
- // Actually render the scrolled viewport, or actually scroll the
- // view using View.scrollTo.
+ 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 currX or currY are outside the bounds, render the overscroll
- // glow using EdgeEffect.
+ /*
+ * 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;
+ }
- } else {
- // The scroll has finished.
- }
+ 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;
+ }
+ ...
+ }</pre>
+
+<p>Here is the section of the code that performs the actual zoom:</p>
+
+<pre>// 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);
+}
+</pre>
+
+<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It
+computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible,
+this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions,
+the returned size will be twice as large horizontally and vertically.</p>
+
+<pre>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()));
}</pre>
-<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the
-{@link android.support.v4.view.ViewPager} class.</p>
+<p>For another example of scroller usage, see the
+<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the
+{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings,
+and uses scrolling to implement the "snapping to page" animation.</p>
+