diff options
-rw-r--r-- | docs/downloads/training/InteractiveChart.zip | bin | 0 -> 405960 bytes | |||
-rw-r--r-- | docs/html/training/gestures/detector.jd | 8 | ||||
-rw-r--r-- | docs/html/training/gestures/index.jd | 8 | ||||
-rw-r--r-- | docs/html/training/gestures/movement.jd | 8 | ||||
-rw-r--r-- | docs/html/training/gestures/multi.jd | 8 | ||||
-rw-r--r-- | docs/html/training/gestures/scale.jd | 205 | ||||
-rw-r--r-- | docs/html/training/gestures/scroll.jd | 339 | ||||
-rw-r--r-- | docs/html/training/gestures/viewgroup.jd | 9 |
8 files changed, 496 insertions, 89 deletions
diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip Binary files differnew file mode 100644 index 0000000..95248ad --- /dev/null +++ b/docs/downloads/training/InteractiveChart.zip diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd index 06d0e98..65ddb1b 100644 --- a/docs/html/training/gestures/detector.jd +++ b/docs/html/training/gestures/detector.jd @@ -25,12 +25,18 @@ next.link=movement.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> diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd index 0191450..16ca7b0 100644 --- a/docs/html/training/gestures/index.jd +++ b/docs/html/training/gestures/index.jd @@ -20,12 +20,18 @@ next.link=detector.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> diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd index f2c49d7..fdc1ea4 100644 --- a/docs/html/training/gestures/movement.jd +++ b/docs/html/training/gestures/movement.jd @@ -24,12 +24,18 @@ next.link=scroll.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> diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd index d4c5b1d..6a0df11 100644 --- a/docs/html/training/gestures/multi.jd +++ b/docs/html/training/gestures/multi.jd @@ -25,12 +25,18 @@ next.link=scale.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> diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd index 17e4085..f2e4eb8 100644 --- a/docs/html/training/gestures/scale.jd +++ b/docs/html/training/gestures/scale.jd @@ -15,6 +15,7 @@ next.link=viewgroup.html <h2>This lesson teaches you to</h2> <ol> <li><a href="#drag">Drag an Object</a></li> + <li><a href="#pan">Drag to Pan</a></li> <li><a href="#scale">Use Touch to Perform Scaling</a></li> </ol> @@ -25,20 +26,25 @@ next.link=viewgroup.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> + <p>This lesson describes how to use touch gestures to drag and scale on-screen objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept -touch events. Here is the original <a -href="http://code.google.com/p/android-touchexample/">source code</a> -for the examples used in this lesson. +touch events. </p> <h2 id="drag">Drag an Object</h2> @@ -128,17 +134,15 @@ public boolean onTouchEvent(MotionEvent ev) { final float x = MotionEventCompat.getX(ev, pointerIndex); final float y = MotionEventCompat.getY(ev, pointerIndex); - // Only move if the ScaleGestureDetector isn't processing a gesture. - if (!mScaleDetector.isInProgress()) { - // Calculate the distance moved - final float dx = x - mLastTouchX; - final float dy = y - mLastTouchY; + // Calculate the distance moved + final float dx = x - mLastTouchX; + final float dy = y - mLastTouchY; - mPosX += dx; - mPosY += dy; + mPosX += dx; + mPosY += dy; + + invalidate(); - invalidate(); - } // Remember this touch position for the next move event mLastTouchX = x; mLastTouchY = y; @@ -175,6 +179,88 @@ public boolean onTouchEvent(MotionEvent ev) { return true; }</pre> +<h2 id="pan">Drag to Pan</h2> + +<p>The previous section showed an example of dragging an object around the screen. Another +common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling +in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent} +actions to implement dragging. The snippet in this section takes advantage of the platform's +built-in support for common gestures. It overrides +{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in +{@link android.view.GestureDetector.SimpleOnGestureListener}.</p> + +<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} +is called when a user is dragging his finger to pan the content. +{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when +a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, +or a fling gesture is started (if the finger was moving with some speed just before it was lifted). +For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p> + +<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}: + + +<pre>// The current viewport. This rectangle represents the currently visible +// chart domain and range. +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 final GestureDetector.SimpleOnGestureListener mGestureListener + = new GestureDetector.SimpleOnGestureListener() { +... + +@Override +public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + // Scrolling uses math based on the viewport (as opposed to math using pixels). + + // Pixel offset is the offset in screen pixels, while viewport offset is the + // offset within the current viewport. + float viewportOffsetX = distanceX * mCurrentViewport.width() + / mContentRect.width(); + float viewportOffsetY = -distanceY * mCurrentViewport.height() + / mContentRect.height(); + ... + // Updates the viewport, refreshes the display. + setViewportBottomLeft( + mCurrentViewport.left + viewportOffsetX, + mCurrentViewport.bottom + viewportOffsetY); + ... + return true; +}</pre> + +<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} +scrolls the viewport in response to the touch gesture:</p> + +<pre> +/** + * Sets the current viewport (defined by mCurrentViewport) to the given + * X and Y positions. Note that the Y value represents the topmost pixel position, + * and thus the bottom of the mCurrentViewport rectangle. + */ +private void setViewportBottomLeft(float x, float y) { + /* + * Constrains within the scroll range. The scroll range is simply the viewport + * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the + * extremes were 0 and 10, and the viewport size was 2, the scroll range would + * be 0 to 8. + */ + + float curWidth = mCurrentViewport.width(); + float curHeight = mCurrentViewport.height(); + x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth)); + y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX)); + + mCurrentViewport.set(x, y - curHeight, x + curWidth, y); + + // Invalidates the View to update the display. + ViewCompat.postInvalidateOnAnimation(this); +} +</pre> + <h2 id="scale">Use Touch to Perform Scaling</h2> <p>As discussed in <a href="detector.html">Detecting Common Gestures</a>, @@ -191,10 +277,10 @@ Android provides {@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener} as a helper class that you can extend if you don’t care about all of the reported events.</p> -<p>Here is a snippet that gives you the basic idea of how to perform scaling. -Here is the original <a -href="http://code.google.com/p/android-touchexample/">source code</a> -for the examples.</p> + +<h3>Basic scaling example</h3> + +<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p> <pre>private ScaleGestureDetector mScaleDetector; private float mScaleFactor = 1.f; @@ -238,3 +324,88 @@ private class ScaleListener return true; } }</pre> + + + + +<h3>More complex scaling example</h3> +<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class. +The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers, +using the {@link android.view.ScaleGestureDetector} "span" +({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and +"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p> + +<pre>@Override +private RectF mCurrentViewport = + new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); +private Rect mContentRect; +private ScaleGestureDetector mScaleGestureDetector; +... +public boolean onTouchEvent(MotionEvent event) { + boolean retVal = mScaleGestureDetector.onTouchEvent(event); + retVal = mGestureDetector.onTouchEvent(event) || retVal; + return retVal || super.onTouchEvent(event); +} + +/** + * The scale listener, used for handling multi-finger scale gestures. + */ +private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener + = new ScaleGestureDetector.SimpleOnScaleGestureListener() { + /** + * This is the active focal point in terms of the viewport. Could be a local + * variable but kept here to minimize per-frame allocations. + */ + private PointF viewportFocus = new PointF(); + private float lastSpanX; + private float lastSpanY; + + // Detects that new pointers are going down. + @Override + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + lastSpanX = ScaleGestureDetectorCompat. + getCurrentSpanX(scaleGestureDetector); + lastSpanY = ScaleGestureDetectorCompat. + getCurrentSpanY(scaleGestureDetector); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + + float spanX = ScaleGestureDetectorCompat. + getCurrentSpanX(scaleGestureDetector); + float spanY = ScaleGestureDetectorCompat. + getCurrentSpanY(scaleGestureDetector); + + float newWidth = lastSpanX / spanX * mCurrentViewport.width(); + float newHeight = lastSpanY / spanY * mCurrentViewport.height(); + + float focusX = scaleGestureDetector.getFocusX(); + float focusY = scaleGestureDetector.getFocusY(); + // Makes sure that the chart point is within the chart region. + // See the sample for the implementation of hitTest(). + hitTest(scaleGestureDetector.getFocusX(), + scaleGestureDetector.getFocusY(), + viewportFocus); + + mCurrentViewport.set( + viewportFocus.x + - newWidth * (focusX - mContentRect.left) + / mContentRect.width(), + viewportFocus.y + - newHeight * (mContentRect.bottom - focusY) + / mContentRect.height(), + 0, + 0); + mCurrentViewport.right = mCurrentViewport.left + newWidth; + mCurrentViewport.bottom = mCurrentViewport.top + newHeight; + ... + // Invalidates the View to update the display. + ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); + + lastSpanX = spanX; + lastSpanY = spanY; + return true; + } +};</pre> 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() { @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; } - - @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; - } - + ... @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—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; + @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> + diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd index 257a5d8..5b32300 100644 --- a/docs/html/training/gestures/viewgroup.jd +++ b/docs/html/training/gestures/viewgroup.jd @@ -26,12 +26,19 @@ next.link= <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> |