am 61f13cd8: am ae657a05: am e23f97ba: Doc change: updates to Gestures training class.

* commit '61f13cd877405f8bb72c2ad33c717ba3f02acf74':
  Doc change: updates to Gestures training class.
This commit is contained in:
kmccormick
2013-03-12 15:32:46 -07:00
committed by Android Git Automerger
8 changed files with 497 additions and 90 deletions

Binary file not shown.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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() {
...
&#64;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 dont 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>&#64;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.
&#64;Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
lastSpanX = ScaleGestureDetectorCompat.
getCurrentSpanX(scaleGestureDetector);
lastSpanY = ScaleGestureDetectorCompat.
getCurrentSpanY(scaleGestureDetector);
return true;
}
&#64;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>

View File

@@ -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>
<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>
<h2 id="term">Understand Scrolling Terminology</h2>
<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
<p>It's common to use scrollers in conjunction with a fling gesture, but they
<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 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>
<pre>
private OverScroller mScroller = new OverScroller(context);
private GestureDetector.SimpleOnGestureListener mGestureListener
<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>
<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>
<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>

View File

@@ -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>