2012-11-30 16:28:27 -08:00
|
|
|
|
page.title=Dragging and Scaling
|
|
|
|
|
parent.title=Using Touch Gestures
|
|
|
|
|
parent.link=index.html
|
|
|
|
|
|
|
|
|
|
trainingnavtop=true
|
|
|
|
|
next.title=Managing Touch Events in a ViewGroup
|
|
|
|
|
next.link=viewgroup.html
|
|
|
|
|
|
|
|
|
|
@jd:body
|
|
|
|
|
|
|
|
|
|
<div id="tb-wrapper">
|
|
|
|
|
<div id="tb">
|
|
|
|
|
|
|
|
|
|
<!-- table of contents -->
|
|
|
|
|
<h2>This lesson teaches you to</h2>
|
|
|
|
|
<ol>
|
|
|
|
|
<li><a href="#drag">Drag an Object</a></li>
|
2013-03-12 14:46:07 -07:00
|
|
|
|
<li><a href="#pan">Drag to Pan</a></li>
|
2012-11-30 16:28:27 -08:00
|
|
|
|
<li><a href="#scale">Use Touch to Perform Scaling</a></li>
|
|
|
|
|
</ol>
|
|
|
|
|
|
|
|
|
|
<!-- other docs (NOT javadocs) -->
|
|
|
|
|
<h2>You should also read</h2>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
<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="{@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>
|
|
|
|
|
|
2013-03-12 14:46:07 -07:00
|
|
|
|
<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>
|
2012-11-30 16:28:27 -08:00
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2013-03-12 14:46:07 -07:00
|
|
|
|
|
2012-11-30 16:28:27 -08:00
|
|
|
|
<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
|
2013-03-12 14:46:07 -07:00
|
|
|
|
touch events.
|
2012-11-30 16:28:27 -08:00
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<h2 id="drag">Drag an Object</h2>
|
|
|
|
|
|
|
|
|
|
<p class="note">If you are targeting Android 3.0 or higher, you can use the built-in drag-and-drop event
|
|
|
|
|
listeners with {@link android.view.View.OnDragListener}, as described in
|
|
|
|
|
<a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a>.
|
|
|
|
|
|
|
|
|
|
<p>A common operation for a touch gesture is to use it to drag an object across
|
|
|
|
|
the screen. The following snippet lets the user drag an on-screen image. Note
|
|
|
|
|
the following:</p>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
|
|
|
|
<li>In a drag (or scroll) operation, the app has to keep track of the original pointer
|
|
|
|
|
(finger), even if additional fingers get placed on the screen. For example,
|
|
|
|
|
imagine that while dragging the image around, the user places a second finger on
|
|
|
|
|
the touch screen and lifts the first finger. If your app is just tracking
|
|
|
|
|
individual pointers, it will regard the second pointer as the default and move
|
|
|
|
|
the image to that location.</li>
|
|
|
|
|
|
|
|
|
|
<li>To prevent this from happening, your app needs to distinguish between the
|
|
|
|
|
original pointer and any follow-on pointers. To do this, it tracks the
|
|
|
|
|
{@link android.view.MotionEvent#ACTION_POINTER_DOWN} and
|
|
|
|
|
{@link android.view.MotionEvent#ACTION_POINTER_UP} events described in
|
|
|
|
|
<a href="multi.html">Handling Multi-Touch Gestures</a>.
|
|
|
|
|
{@link android.view.MotionEvent#ACTION_POINTER_DOWN} and
|
|
|
|
|
{@link android.view.MotionEvent#ACTION_POINTER_UP} are
|
|
|
|
|
passed to the {@link android.view.View#onTouchEvent onTouchEvent()} callback
|
|
|
|
|
whenever a secondary pointer goes down or up. </li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li>In the {@link android.view.MotionEvent#ACTION_POINTER_UP} case, the example
|
|
|
|
|
extracts this index and ensures that the active pointer ID is not referring to a
|
|
|
|
|
pointer that is no longer touching the screen. If it is, the app selects a
|
|
|
|
|
different pointer to be active and saves its current X and Y position. Since
|
|
|
|
|
this saved position is used in the {@link android.view.MotionEvent#ACTION_MOVE}
|
|
|
|
|
case to calculate the distance to move the onscreen object, the app will always
|
|
|
|
|
calculate the distance to move using data from the correct pointer.</li>
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<p>The following snippet enables a user to drag an object around on the screen. It records the initial
|
|
|
|
|
position of the active pointer, calculates the distance the pointer traveled, and moves the object to the
|
|
|
|
|
new position. It correctly manages the possibility of additional pointers, as described
|
|
|
|
|
above.</p>
|
|
|
|
|
|
|
|
|
|
<p>Notice that the snippet uses the {@link android.view.MotionEvent#getActionMasked getActionMasked()} method.
|
|
|
|
|
You should always use this method (or better yet, the compatability version
|
|
|
|
|
{@link android.support.v4.view.MotionEventCompat#getActionMasked MotionEventCompat.getActionMasked()})
|
|
|
|
|
to retrieve the action of a
|
|
|
|
|
{@link android.view.MotionEvent}. Unlike the older
|
|
|
|
|
{@link android.view.MotionEvent#getAction getAction()}
|
|
|
|
|
method, {@link android.support.v4.view.MotionEventCompat#getActionMasked getActionMasked()}
|
|
|
|
|
is designed to work with multiple pointers. It returns the masked action
|
|
|
|
|
being performed, without including the pointer index bits.</p>
|
|
|
|
|
|
|
|
|
|
<pre>// The ‘active pointer’ is the one currently moving our object.
|
|
|
|
|
private int mActivePointerId = INVALID_POINTER_ID;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
|
|
// Let the ScaleGestureDetector inspect all events.
|
|
|
|
|
mScaleDetector.onTouchEvent(ev);
|
|
|
|
|
|
|
|
|
|
final int action = MotionEventCompat.getActionMasked(ev);
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
case MotionEvent.ACTION_DOWN: {
|
|
|
|
|
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
|
|
|
|
|
final float x = MotionEventCompat.getX(ev, pointerIndex);
|
|
|
|
|
final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
|
|
|
|
|
|
|
// Remember where we started (for dragging)
|
|
|
|
|
mLastTouchX = x;
|
|
|
|
|
mLastTouchY = y;
|
|
|
|
|
// Save the ID of this pointer (for dragging)
|
|
|
|
|
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case MotionEvent.ACTION_MOVE: {
|
|
|
|
|
// Find the index of the active pointer and fetch its position
|
|
|
|
|
final int pointerIndex =
|
|
|
|
|
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
|
|
|
|
|
|
|
|
|
|
final float x = MotionEventCompat.getX(ev, pointerIndex);
|
|
|
|
|
final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
|
|
|
2013-03-12 14:46:07 -07:00
|
|
|
|
// Calculate the distance moved
|
|
|
|
|
final float dx = x - mLastTouchX;
|
|
|
|
|
final float dy = y - mLastTouchY;
|
2012-11-30 16:28:27 -08:00
|
|
|
|
|
2013-03-12 14:46:07 -07:00
|
|
|
|
mPosX += dx;
|
|
|
|
|
mPosY += dy;
|
|
|
|
|
|
|
|
|
|
invalidate();
|
2012-11-30 16:28:27 -08:00
|
|
|
|
|
|
|
|
|
// Remember this touch position for the next move event
|
|
|
|
|
mLastTouchX = x;
|
|
|
|
|
mLastTouchY = y;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case MotionEvent.ACTION_UP: {
|
|
|
|
|
mActivePointerId = INVALID_POINTER_ID;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case MotionEvent.ACTION_CANCEL: {
|
|
|
|
|
mActivePointerId = INVALID_POINTER_ID;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case MotionEvent.ACTION_POINTER_UP: {
|
|
|
|
|
|
|
|
|
|
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
|
|
|
|
|
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
|
|
|
|
|
|
|
|
|
|
if (pointerId == mActivePointerId) {
|
|
|
|
|
// This was our active pointer going up. Choose a new
|
|
|
|
|
// active pointer and adjust accordingly.
|
|
|
|
|
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
|
|
|
|
mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex);
|
|
|
|
|
mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);
|
|
|
|
|
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}</pre>
|
|
|
|
|
|
2013-03-12 14:46:07 -07:00
|
|
|
|
<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>
|
|
|
|
|
|
2012-11-30 16:28:27 -08:00
|
|
|
|
<h2 id="scale">Use Touch to Perform Scaling</h2>
|
|
|
|
|
|
|
|
|
|
<p>As discussed in <a href="detector.html">Detecting Common Gestures</a>,
|
|
|
|
|
{@link android.view.GestureDetector} helps you detect common gestures used by
|
|
|
|
|
Android such as scrolling, flinging, and long press. For scaling, Android
|
|
|
|
|
provides {@link android.view.ScaleGestureDetector}. {@link
|
|
|
|
|
android.view.GestureDetector} and {@link android.view.ScaleGestureDetector} can
|
|
|
|
|
be used together when you want a view to recognize additional gestures.</p>
|
|
|
|
|
|
|
|
|
|
<p>To report detected gesture events, gesture detectors use listener objects
|
|
|
|
|
passed to their constructors. {@link android.view.ScaleGestureDetector} uses
|
|
|
|
|
{@link android.view.ScaleGestureDetector.OnScaleGestureListener}.
|
|
|
|
|
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>
|
|
|
|
|
|
2013-03-12 14:46:07 -07:00
|
|
|
|
|
|
|
|
|
<h3>Basic scaling example</h3>
|
|
|
|
|
|
|
|
|
|
<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p>
|
2012-11-30 16:28:27 -08:00
|
|
|
|
|
|
|
|
|
<pre>private ScaleGestureDetector mScaleDetector;
|
|
|
|
|
private float mScaleFactor = 1.f;
|
|
|
|
|
|
|
|
|
|
public MyCustomView(Context mContext){
|
|
|
|
|
...
|
|
|
|
|
// View code goes here
|
|
|
|
|
...
|
|
|
|
|
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
|
|
// Let the ScaleGestureDetector inspect all events.
|
|
|
|
|
mScaleDetector.onTouchEvent(ev);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDraw(Canvas canvas) {
|
|
|
|
|
super.onDraw(canvas);
|
|
|
|
|
|
|
|
|
|
canvas.save();
|
|
|
|
|
canvas.scale(mScaleFactor, mScaleFactor);
|
|
|
|
|
...
|
|
|
|
|
// onDraw() code goes here
|
|
|
|
|
...
|
|
|
|
|
canvas.restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class ScaleListener
|
|
|
|
|
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onScale(ScaleGestureDetector detector) {
|
|
|
|
|
mScaleFactor *= detector.getScaleFactor();
|
|
|
|
|
|
|
|
|
|
// Don't let the object get too small or too large.
|
|
|
|
|
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
|
|
|
|
|
|
|
|
|
|
invalidate();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}</pre>
|
2013-03-12 14:46:07 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<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>
|