241 lines
9.2 KiB
Plaintext
241 lines
9.2 KiB
Plaintext
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>
|
||
<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="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>
|
||
|
||
|
||
</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.
|
||
</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);
|
||
|
||
// 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;
|
||
|
||
mPosX += dx;
|
||
mPosY += dy;
|
||
|
||
invalidate();
|
||
}
|
||
// 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>
|
||
|
||
<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>
|
||
|
||
<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>
|
||
|
||
<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>
|