diff options
| author | 2010-08-18 18:14:13 -0700 | |
|---|---|---|
| committer | 2010-08-20 13:39:28 -0700 | |
| commit | 2b342f0a76f3237e97f15dc2f4e8a0b72dd7c023 (patch) | |
| tree | 1fea957e003b84db427deae346ed3558d6a6986e | |
| parent | 8a44bb23c1f12ba58d0cb5732e3e216d48e7ccdb (diff) | |
Allow ViewGroup to split MotionEvents to multiple targets during dispatch.
Use the layout xml attribute splitMotionEvents="true" or the ViewGroup
method setMotionEventSplittingEnabled(true) to enable motion event
splitting. Rules for splitting are as follows:
* Splitting is enabled per ViewGroup. When splitting is enabled any
MotionEvent dispatched to that ViewGroup can potentially be split
into several and dispatched to children independently.
* Each pointer is assigned a target child view when the ACTION_DOWN or
ACTION_POINTER_DOWN event is received. That will be the pointer's
target until it goes up, the target returns false from onTouchEvent,
or the MotionEvents are intercepted.
* Multiple pointers may be assigned to the same target. All pointer
data sent to a target are bundled into a single MotionEvent. Child
views do not need to be aware that splitting has occurred.
Change-Id: I993f838e2f6b455da9812f4742a016dfcd1c4cc9
| -rw-r--r-- | api/current.xml | 35 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 34 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 549 | ||||
| -rwxr-xr-x | core/res/res/values/attrs.xml | 11 | ||||
| -rw-r--r-- | core/res/res/values/public.xml | 1 |
5 files changed, 596 insertions, 34 deletions
diff --git a/api/current.xml b/api/current.xml index bcc80aca7abc..d8264162c522 100644 --- a/api/current.xml +++ b/api/current.xml @@ -8919,6 +8919,17 @@ visibility="public" > </field> +<field name="splitMotionEvents" + type="int" + transient="false" + volatile="false" + value="16843567" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="src" type="int" transient="false" @@ -199360,6 +199371,17 @@ visibility="protected" > </method> +<method name="isMotionEventSplittingEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="measureChild" return="void" abstract="false" @@ -199823,6 +199845,19 @@ <parameter name="animationListener" type="android.view.animation.Animation.AnimationListener"> </parameter> </method> +<method name="setMotionEventSplittingEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="split" type="boolean"> +</parameter> +</method> <method name="setOnHierarchyChangeListener" return="void" abstract="false" diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f31a248906ae..c13bb8c70fde 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,7 +16,6 @@ package android.view; -import android.graphics.Camera; import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; @@ -25,6 +24,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Interpolator; import android.graphics.LinearGradient; @@ -69,7 +69,6 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; -import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -5419,37 +5418,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** - * This method detects whether the given event is inside the view and, if so, - * handles it via the dispatchEvent(MotionEvent) method. - * - * @param ev The event that is being dispatched. - * @param parentX The x location of the event in the parent's coordinates. - * @param parentY The y location of the event in the parent's coordinates. - * @return true if the event was inside this view, false otherwise. - */ - boolean dispatchTouchEvent(MotionEvent ev, float parentX, float parentY) { - float localX = parentX - mLeft; - float localY = parentY - mTop; - if (!hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = localX; - localXY[1] = localY; - getInverseMatrix().mapPoints(localXY); - localX = localXY[0]; - localY = localXY[1]; - } - if (localX >= 0 && localY >= 0 && localX < (mRight - mLeft) && localY < (mBottom - mTop)) { - // It would be safer to clone the event here but we don't for performance. - // There are many subtle interactions in touch event dispatch; change at your own risk. - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - ev.setLocation(localX, localY); - return dispatchTouchEvent(ev); - } - return false; - } - - /** * Utility method to determine whether the given point, in local coordinates, * is inside the view, where the area of the view is expanded by the slop factor. * This method is called while processing touch-move events to determine if the event diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e2f9c1513238..e2e3333fe720 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -43,6 +43,7 @@ import android.view.animation.LayoutAnimationController; import android.view.animation.Transformation; import java.util.ArrayList; +import java.util.Arrays; /** * <p> @@ -108,6 +109,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Target of Motion events private View mMotionTarget; + // Targets of MotionEvents in split mode + private SplitMotionTargets mSplitMotionTargets; + // Layout animation private LayoutAnimationController mLayoutAnimationController; private Animation.AnimationListener mAnimationListener; @@ -242,6 +246,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000; /** + * When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate. + */ + private static final int FLAG_SPLIT_MOTION_EVENTS = 0x100000; + + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -362,6 +371,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_descendantFocusability: setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]); break; + case R.styleable.ViewGroup_splitMotionEvents: + setMotionEventSplittingEnabled(a.getBoolean(attr, false)); + break; } } @@ -843,6 +855,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS) { + return dispatchSplitTouchEvent(ev); + } + final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); @@ -872,7 +888,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - if (child.dispatchTouchEvent(ev, scrolledXFloat, scrolledYFloat)) { + // Single dispatch always picks its target based on the initial down + // event's position - index 0 + if (dispatchTouchEventIfInView(child, ev, 0)) { mMotionTarget = child; return true; } @@ -953,6 +971,276 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * This method detects whether the pointer location at <code>pointerIndex</code> within + * <code>ev</code> is inside the specified view. If so, the transformed event is dispatched to + * <code>child</code>. + * + * @param child View to hit test against + * @param ev MotionEvent to test + * @param pointerIndex Index of the pointer within <code>ev</code> to test + * @return <code>false</code> if the hit test failed, or the result of + * <code>child.dispatchTouchEvent</code> + */ + private boolean dispatchTouchEventIfInView(View child, MotionEvent ev, int pointerIndex) { + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); + final float scrolledX = x + mScrollX; + final float scrolledY = y + mScrollY; + float localX = scrolledX - child.mLeft; + float localY = scrolledY - child.mTop; + if (!child.hasIdentityMatrix() && mAttachInfo != null) { + // non-identity matrix: transform the point into the view's coordinates + final float[] localXY = mAttachInfo.mTmpTransformLocation; + localXY[0] = localX; + localXY[1] = localY; + child.getInverseMatrix().mapPoints(localXY); + localX = localXY[0]; + localY = localXY[1]; + } + if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) && + localY < (child.mBottom - child.mTop)) { + // It would be safer to clone the event here but we don't for performance. + // There are many subtle interactions in touch event dispatch; change at your own risk. + child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + ev.offsetLocation(localX - x, localY - y); + return child.dispatchTouchEvent(ev); + } + return false; + } + + private boolean dispatchSplitTouchEvent(MotionEvent ev) { + final SplitMotionTargets targets = mSplitMotionTargets; + final int action = ev.getAction(); + final int maskedAction = ev.getActionMasked(); + float xf = ev.getX(); + float yf = ev.getY(); + float scrolledXFloat = xf + mScrollX; + float scrolledYFloat = yf + mScrollY; + + boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; + + if (maskedAction == MotionEvent.ACTION_DOWN || + maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + final int actionIndex = ev.getActionIndex(); + final int actionId = ev.getPointerId(actionIndex); + + // Clear out any current target for this ID. + // XXX: We should probably send an ACTION_UP to the current + // target if present. + targets.removeById(actionId); + + // If we're disallowing intercept or if we're allowing and we didn't + // intercept + if (disallowIntercept || !onInterceptTouchEvent(ev)) { + // reset this event's action (just to protect ourselves) + ev.setAction(action); + // We know we want to dispatch the event down, try to find a child + // who can handle it, start with the front-most child. + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = count - 1; i >= 0; i--) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + || child.getAnimation() != null) { + final MotionEvent childEvent = targets.filterMotionEventForChild(ev, child); + if (childEvent != null) { + try { + final int childActionIndex = childEvent.findPointerIndex(actionId); + if (dispatchTouchEventIfInView(child, childEvent, + childActionIndex)) { + targets.add(actionId, child); + + return true; + } + } finally { + childEvent.recycle(); + } + } + } + } + + // Didn't find a new target. Do we have a "primary" target to send to? + final View primaryTarget = targets.getPrimaryTarget(); + if (primaryTarget != null) { + final MotionEvent childEvent = + targets.filterMotionEventForChild(ev, primaryTarget); + if (childEvent != null) { + try { + // Calculate the offset point into the target's local coordinates + float xc = scrolledXFloat - (float) primaryTarget.mLeft; + float yc = scrolledYFloat - (float) primaryTarget.mTop; + if (!primaryTarget.hasIdentityMatrix() && mAttachInfo != null) { + // non-identity matrix: transform the point into the view's + // coordinates + final float[] localXY = mAttachInfo.mTmpTransformLocation; + localXY[0] = xc; + localXY[1] = yc; + primaryTarget.getInverseMatrix().mapPoints(localXY); + xc = localXY[0]; + yc = localXY[1]; + } + childEvent.setLocation(xc, yc); + if (primaryTarget.dispatchTouchEvent(childEvent)) { + targets.add(actionId, primaryTarget); + return true; + } + } finally { + childEvent.recycle(); + } + } + } + } + } + + boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || + (action == MotionEvent.ACTION_CANCEL); + + if (isUpOrCancel) { + // Note, we've already copied the previous state to our local + // variable, so this takes effect on the next event + mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + } + + if (targets.isEmpty()) { + // We don't have any targets, this means we're handling the + // event as a regular view. + ev.setLocation(xf, yf); + if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { + ev.setAction(MotionEvent.ACTION_CANCEL); + mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + } + return super.dispatchTouchEvent(ev); + } + + // if we have targets, see if we're allowed to and want to intercept their + // events + int uniqueTargetCount = targets.getUniqueTargetCount(); + if (!disallowIntercept && onInterceptTouchEvent(ev)) { + mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + + for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) { + final View target = targets.getUniqueTargetAt(uniqueIndex); + + // Calculate the offset point into the target's local coordinates + float xc = scrolledXFloat - (float) target.mLeft; + float yc = scrolledYFloat - (float) target.mTop; + if (!target.hasIdentityMatrix() && mAttachInfo != null) { + // non-identity matrix: transform the point into the view's coordinates + final float[] localXY = mAttachInfo.mTmpTransformLocation; + localXY[0] = xc; + localXY[1] = yc; + target.getInverseMatrix().mapPoints(localXY); + xc = localXY[0]; + yc = localXY[1]; + } + + ev.setAction(MotionEvent.ACTION_CANCEL); + ev.setLocation(xc, yc); + if (!target.dispatchTouchEvent(ev)) { + // target didn't handle ACTION_CANCEL. not much we can do + // but they should have. + } + } + targets.clear(); + // Don't dispatch this event to our own view, because we already + // saw it when intercepting; we just want to give the following + // event to the normal onTouchEvent(). + return true; + } + + boolean handled = false; + for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) { + final View target = targets.getUniqueTargetAt(uniqueIndex); + + final MotionEvent targetEvent = targets.filterMotionEventForChild(ev, target); + if (targetEvent == null) { + continue; + } + + try { + // Calculate the offset point into the target's local coordinates + xf = targetEvent.getX(); + yf = targetEvent.getY(); + scrolledXFloat = xf + mScrollX; + scrolledYFloat = yf + mScrollY; + float xc = scrolledXFloat - (float) target.mLeft; + float yc = scrolledYFloat - (float) target.mTop; + if (!target.hasIdentityMatrix() && mAttachInfo != null) { + // non-identity matrix: transform the point into the view's coordinates + final float[] localXY = mAttachInfo.mTmpTransformLocation; + localXY[0] = xc; + localXY[1] = yc; + target.getInverseMatrix().mapPoints(localXY); + xc = localXY[0]; + yc = localXY[1]; + } + + // finally offset the event to the target's coordinate system and + // dispatch the event. + targetEvent.setLocation(xc, yc); + + if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { + targetEvent.setAction(MotionEvent.ACTION_CANCEL); + target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + targets.removeView(target); + uniqueIndex--; + uniqueTargetCount--; + } + + handled |= target.dispatchTouchEvent(targetEvent); + } finally { + targetEvent.recycle(); + } + } + + if (maskedAction == MotionEvent.ACTION_POINTER_UP) { + final int removeId = ev.getPointerId(ev.getActionIndex()); + targets.removeById(removeId); + } + + if (isUpOrCancel) { + targets.clear(); + } + + return handled; + } + + /** + * Enable or disable the splitting of MotionEvents to multiple children during touch event + * dispatch. This behavior is disabled by default. + * + * <p>When this option is enabled MotionEvents may be split and dispatched to different child + * views depending on where each pointer initially went down. This allows for user interactions + * such as scrolling two panes of content independently, chording of buttons, and performing + * independent gestures on different pieces of content. + * + * @param split <code>true</code> to allow MotionEvents to be split and dispatched to multiple + * child views. <code>false</code> to only allow one child view to be the target of + * any MotionEvent received by this ViewGroup. + */ + public void setMotionEventSplittingEnabled(boolean split) { + // TODO Applications really shouldn't change this setting mid-touch event, + // but perhaps this should handle that case and send ACTION_CANCELs to any child views + // with gestures in progress when this is changed. + if (split) { + if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == 0) { + mSplitMotionTargets = new SplitMotionTargets(); + } + mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; + } else { + mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS; + mSplitMotionTargets = null; + } + } + + /** + * @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children. + */ + public boolean isMotionEventSplittingEnabled() { + return (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS; + } + + /** * {@inheritDoc} */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -3812,4 +4100,263 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager bottomMargin = bottom; } } + + private static class SplitMotionTargets { + private SparseArray<View> mTargets; + private View[] mUniqueTargets; + private int mUniqueTargetCount; + private long mDownTime; + private MotionEvent.PointerCoords[] mPointerCoords; + private int[] mPointerIds; + + private static final int INITIAL_UNIQUE_MOTION_TARGETS_SIZE = 5; + private static final int INITIAL_BUCKET_SIZE = 5; + + public SplitMotionTargets() { + mTargets = new SparseArray<View>(); + mUniqueTargets = new View[INITIAL_UNIQUE_MOTION_TARGETS_SIZE]; + mPointerIds = new int[INITIAL_BUCKET_SIZE]; + mPointerCoords = new MotionEvent.PointerCoords[INITIAL_BUCKET_SIZE]; + for (int i = 0; i < INITIAL_BUCKET_SIZE; i++) { + mPointerCoords[i] = new MotionEvent.PointerCoords(); + } + } + + public void clear() { + mTargets.clear(); + Arrays.fill(mUniqueTargets, null); + mUniqueTargetCount = 0; + } + + public void add(int pointerId, View target) { + mTargets.put(pointerId, target); + + final int uniqueCount = mUniqueTargetCount; + boolean addUnique = true; + for (int i = 0; i < uniqueCount; i++) { + if (mUniqueTargets[i] == target) { + addUnique = false; + } + } + if (addUnique) { + if (mUniqueTargets == null) { + mUniqueTargets = new View[INITIAL_UNIQUE_MOTION_TARGETS_SIZE]; + } + if (mUniqueTargets.length == uniqueCount) { + View[] newTargets = + new View[uniqueCount + INITIAL_UNIQUE_MOTION_TARGETS_SIZE]; + System.arraycopy(mUniqueTargets, 0, newTargets, 0, uniqueCount); + mUniqueTargets = newTargets; + } + mUniqueTargets[uniqueCount] = target; + mUniqueTargetCount++; + } + } + + public int getIdCount() { + return mTargets.size(); + } + + public int getUniqueTargetCount() { + return mUniqueTargetCount; + } + + public View getUniqueTargetAt(int index) { + return mUniqueTargets[index]; + } + + public View get(int id) { + return mTargets.get(id); + } + + public int indexOfId(int id) { + return mTargets.indexOfKey(id); + } + + public int indexOfTarget(View target) { + return mTargets.indexOfValue(target); + } + + public int idAt(int index) { + return mTargets.keyAt(index); + } + + public View targetAt(int index) { + return mTargets.valueAt(index); + } + + public View getPrimaryTarget() { + if (!isEmpty()) { + return mUniqueTargets[0]; + } + return null; + } + + public boolean hasTarget(View target) { + final View[] unique = mUniqueTargets; + final int uniqueCount = mUniqueTargetCount; + for (int i = 0; i < uniqueCount; i++) { + if (unique[i] == target) { + return true; + } + } + return false; + } + + public boolean isEmpty() { + return mUniqueTargetCount == 0; + } + + public void removeById(int id) { + final int index = mTargets.indexOfKey(id); + removeAt(index); + } + + public void removeView(View view) { + int i = 0; + while (i < mTargets.size()) { + if (mTargets.valueAt(i) == view) { + mTargets.removeAt(i); + } else { + i++; + } + } + removeUnique(view); + } + + public void removeAt(int index) { + if (index < 0 || index >= mTargets.size()) { + return; + } + + final View removeView = mTargets.valueAt(index); + mTargets.removeAt(index); + if (mTargets.indexOfValue(removeView) < 0) { + removeUnique(removeView); + } + } + + private void removeUnique(View removeView) { + View[] unique = mUniqueTargets; + int uniqueCount = mUniqueTargetCount; + for (int i = 0; i < uniqueCount; i++) { + if (unique[i] == removeView) { + unique[i] = unique[--uniqueCount]; + unique[uniqueCount] = null; + break; + } + } + + mUniqueTargetCount = uniqueCount; + } + + /** + * Return a new (obtain()ed) MotionEvent containing only data for pointers that should + * be dispatched to child. Don't forget to recycle it! + */ + public MotionEvent filterMotionEventForChild(MotionEvent ev, View child) { + int action = ev.getAction(); + final int maskedAction = action & MotionEvent.ACTION_MASK; + + // Only send pointer up events if this child was the target. Drop it otherwise. + if (maskedAction == MotionEvent.ACTION_POINTER_UP && + get(ev.getPointerId(ev.getActionIndex())) != child) { + return null; + } + + int pointerCount = 0; + final int idCount = getIdCount(); + for (int i = 0; i < idCount; i++) { + if (targetAt(i) == child) { + pointerCount++; + } + } + + int actionId = -1; + boolean needsNewIndex = false; // True if we should fill in the action's masked index + + // If we have a down event, it wasn't counted above. + if (maskedAction == MotionEvent.ACTION_DOWN) { + pointerCount++; + actionId = ev.getPointerId(0); + mDownTime = ev.getDownTime(); + } else if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + pointerCount++; + + actionId = ev.getPointerId(ev.getActionIndex()); + + if (indexOfTarget(child) < 0) { + // The new action should be ACTION_DOWN if this child isn't currently getting + // any events. + action = MotionEvent.ACTION_DOWN; + } else { + // Fill in the index portion of the action later. + needsNewIndex = true; + } + } else if (maskedAction == MotionEvent.ACTION_POINTER_UP) { + actionId = ev.getPointerId(ev.getActionIndex()); + if (pointerCount == 1) { + // The new action should be ACTION_UP if there's only one pointer left for + // this target. + action = MotionEvent.ACTION_UP; + } else { + // Fill in the index portion of the action later. + needsNewIndex = true; + } + } + + if (pointerCount == 0) { + return null; + } + + // Fill the buckets with pointer data! + final int eventPointerCount = ev.getPointerCount(); + int bucketIndex = 0; + int newActionIndex = -1; + for (int evp = 0; evp < eventPointerCount; evp++) { + final int id = ev.getPointerId(evp); + + // Add this pointer to the bucket if it is new or targeted at child + if (id == actionId || get(id) == child) { + // Expand scratch arrays if needed + if (mPointerCoords.length <= bucketIndex) { + int[] pointerIds = new int[pointerCount]; + MotionEvent.PointerCoords[] pointerCoords = + new MotionEvent.PointerCoords[pointerCount]; + for (int i = mPointerCoords.length; i < pointerCoords.length; i++) { + pointerCoords[i] = new MotionEvent.PointerCoords(); + } + + System.arraycopy(mPointerCoords, 0, + pointerCoords, 0, mPointerCoords.length); + System.arraycopy(mPointerIds, 0, pointerIds, 0, mPointerIds.length); + + mPointerCoords = pointerCoords; + mPointerIds = pointerIds; + } + + mPointerIds[bucketIndex] = id; + ev.getPointerCoords(evp, mPointerCoords[bucketIndex]); + + if (needsNewIndex && id == actionId) { + newActionIndex = bucketIndex; + } + + bucketIndex++; + } + } + + // Encode the new action index if we have one + if (newActionIndex >= 0) { + action = (action & MotionEvent.ACTION_MASK) | + (newActionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); + } + + MotionEvent result = MotionEvent.obtain(mDownTime, ev.getEventTime(), + action, pointerCount, mPointerIds, mPointerCoords, ev.getMetaState(), + ev.getXPrecision(), ev.getYPrecision(), ev.getDeviceId(), ev.getEdgeFlags(), + ev.getSource()); + return result; + } + } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c65631de4b2e..e611b2bf2b33 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1453,6 +1453,17 @@ <enum name="blocksDescendants" value="2" /> </attr> + <!-- Sets whether this ViewGroup should split MotionEvents + to separate child views during touch event dispatch. + If false (default), touch events will be dispatched to + the child view where the first pointer went down until + the last pointer goes up. + If true, touch events may be dispatched to multiple children. + MotionEvents for each pointer will be dispatched to the child + view where the initial ACTION_DOWN event happened. + See {@link android.view.ViewGroup#setSplitMotionEvents(boolean)} + for more information. --> + <attr name="splitMotionEvents" format="boolean" /> </declare-styleable> <!-- A {@link android.view.ViewStub} lets you lazily include other XML layouts diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c89b4c090b3b..ea5c1586d784 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1324,6 +1324,7 @@ <public type="attr" name="imeSubtypeLocale" /> <public type="attr" name="imeSubtypeMode" /> <public type="attr" name="imeSubtypeExtraValue" /> + <public type="attr" name="splitMotionEvents" /> <public type="anim" name="animator_fade_in" /> <public type="anim" name="animator_fade_out" /> |