diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip new file mode 100644 index 0000000000000..95248ada943b0 Binary files /dev/null and b/docs/downloads/training/InteractiveChart.zip differ diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd index 06d0e983f5ccc..65ddb1baee3a1 100644 --- a/docs/html/training/gestures/detector.jd +++ b/docs/html/training/gestures/detector.jd @@ -25,12 +25,18 @@ next.link=movement.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd index 0191450f29cce..16ca7b08e3af6 100644 --- a/docs/html/training/gestures/index.jd +++ b/docs/html/training/gestures/index.jd @@ -20,12 +20,18 @@ next.link=detector.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd index f2c49d726c314..fdc1ea4ef4ffd 100644 --- a/docs/html/training/gestures/movement.jd +++ b/docs/html/training/gestures/movement.jd @@ -24,12 +24,18 @@ next.link=scroll.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd index d4c5b1d07df96..6a0df11f4c5c8 100644 --- a/docs/html/training/gestures/multi.jd +++ b/docs/html/training/gestures/multi.jd @@ -25,12 +25,18 @@ next.link=scale.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd index 17e40858588a3..f2e4eb82d1228 100644 --- a/docs/html/training/gestures/scale.jd +++ b/docs/html/training/gestures/scale.jd @@ -15,6 +15,7 @@ next.link=viewgroup.html

    This lesson teaches you to

    1. Drag an Object
    2. +
    3. Drag to Pan
    4. Use Touch to Perform Scaling
    @@ -25,20 +26,25 @@ next.link=viewgroup.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    +

    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 source code -for the examples used in this lesson. +touch events.

    Drag an Object

    @@ -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; } +

    Drag to Pan

    + +

    The previous section showed an example of dragging an object around the screen. Another +common scenario is panning, 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}.

    + +

    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 Animating a Scroll Gesture.

    + +

    Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}: + + +

    // 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;
    +}
    + +

    The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} +scrolls the viewport in response to the touch gesture:

    + +
    +/**
    + * 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);
    +}
    +
    +

    Use Touch to Perform Scaling

    As discussed in Detecting Common Gestures, @@ -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.

    -

    Here is a snippet that gives you the basic idea of how to perform scaling. -Here is the original source code -for the examples.

    + +

    Basic scaling example

    + +

    Here is a snippet that illustrates the basic ingredients involved in scaling.

    private ScaleGestureDetector mScaleDetector;
     private float mScaleFactor = 1.f;
    @@ -238,3 +324,88 @@ private class ScaleListener
             return true;
         }
     }
    + + + + +

    More complex scaling example

    +

    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:

    + +
    @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;
    +    }
    +};
    diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd index 8576948b5a23c..bd1537a65a673 100644 --- a/docs/html/training/gestures/scroll.jd +++ b/docs/html/training/gestures/scroll.jd @@ -14,6 +14,7 @@ next.link=multi.html

    This lesson teaches you to

      +
    1. Understand Scrolling Terminology
    2. Implement Touch-Based Scrolling
    @@ -24,12 +25,18 @@ next.link=multi.html
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    @@ -45,7 +52,26 @@ a scrolling effect in response to touch gestures using scrollers.

    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.

    +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.

    + +

    Note: 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. +
    +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. +

    +

    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.

    -

    Note: 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.

    - -

    Implement Touch-Based Scrolling

    -

    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.

    +

    Understand Scrolling Terminology

    +

    "Scrolling" is a word that can take on different meanings in Android, depending on the context.

    -

    It's common to use scrollers in conjunction with a fling gesture, but they +

    Scrolling 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 +panning. The sample application provided with this class, {@code InteractiveChart}, illustrates +two different types of scrolling, dragging and flinging:

    + + +

    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.

    +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.

    -
    -private OverScroller mScroller = new OverScroller(context);
     
    -private GestureDetector.SimpleOnGestureListener mGestureListener
    +

    Implement Touch-Based Scrolling

    + +

    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. +

    + +

    Note: 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}.

    + +

    The first part of the snippet shows the implementation of +{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:

    + +
    // 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);
    +}
    + +

    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.

    + +

    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:

    + +
    // 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;
    +        }
    +        ...
    +    }
    + +

    Here is the section of the code that performs the actual zoom:

    + +
    // 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);
    +}
    +
    + +

    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.

    + +
    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()));
     }
    -

    For another example of scroller usage, see the source code for the -{@link android.support.v4.view.ViewPager} class.

    +

    For another example of scroller usage, see the +source code 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.

    + diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd index 257a5d816e56f..5b32300b99b06 100644 --- a/docs/html/training/gestures/viewgroup.jd +++ b/docs/html/training/gestures/viewgroup.jd @@ -26,12 +26,19 @@ next.link=
  • Input Events API Guide
  • Sensors Overview
  • -
  • Making Sense of Multitouch blog post
  • Making the View Interactive
  • Design Guide for Gestures
  • Design Guide for Touch Feedback
  • +

    Try it out

    + +
    + Download the sample +

    InteractiveChart.zip

    +
    +