Android MotionEvent理解

Java层MotionEvent对应C++层MotionEvent

  Java层MotionEvent类下面的mNativePtr指向C++层MotionEvent
  C++层MotionEvent成员变量如下

class MotionEvent : public InputEvent {
…………
protected:
    int32_t mAction; //事件的行为,例如Down,Move,Up,如果是ACTION_POINTER_DOWN事件,则包含着一个移位的pointer index
    int32_t mActionButton; //用于行为是ACTION_BUTTON_PRESS或ACTION_BUTTON_RELEASE,表示哪个按键是被按下或者释放。如果不是这两个行为事件,该值未定义
    int32_t mFlags; // 移动事件的flags,
    int32_t mEdgeFlags; // 对于触摸事件,用来作为显示屏的边缘
    int32_t mMetaState; // meta key被按下的bit位
    int32_t mButtonState; // 表示被按下的按键的状态,例如鼠标或触控笔。因为被按下的按键都是使用bit位来表示的,所以返回的就是某个按键被按下了。
    MotionClassification mClassification;
    float mXScale;// X坐标值的缩放
    float mYScale;//  Y坐标值的缩放
    float mXOffset; //  X坐标值的偏移
    float mYOffset; //  Y坐标值的偏移
    float mXPrecision; //  X坐标值的精确度
    float mYPrecision; //  Y坐标值的偏移
    float mRawXCursorPosition;// 鼠标事件中鼠标在X坐标的位置值
    float mRawYCursorPosition;// 鼠标事件中鼠标在Y坐标的位置值
    nsecs_t mDownTime; // 事件按下的时间
    Vector<PointerProperties> mPointerProperties;
    Vector<nsecs_t> mSampleEventTimes;
    Vector<PointerCoords> mSamplePointerCoords;
}

  其中的成员变量mPointerProperties、mSampleEventTimes、mSamplePointerCoords都是向量类型,里面分别存放什么数据呢?
  手指快速滑动中,MotionEvent类中可以批次存放多个Sample数据。如果只有一个手指滑动,一个Sample数据只有一个数据,如果多个手指参与滑动,一个Sample数据里面的数据量就是手指的数量。在代码中一个手指对应一个Pointer。多个Sample数据分成历史Sample数据和当前数据。
  成员变量mSamplePointerCoords会存储这批历史数据和当前数据,如果存在历史数据,会在前面存放历史数据,最后面存放当前数据。前面的历史数据数量=历史Sample数量×Pointer数量(触摸事件则为手指数量),其中历史Sample数量=mSampleEventTimes.size() - 1, Pointer数量=mPointerProperties.size()。其中mSamplePointerCoords中的数据存放位置如下图
mSamplePointerCoords中的数据存放  mSampleEventTimes中存放的是每个sample对应的时间。

action的获取

final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

  int action = ev.getAction();
  进入Motion里面查看实现

    public final int getAction() {
        return nativeGetAction(mNativePtr);
    }

  C++中对应的方法android_view_MotionEvent_nativeGetAction,看下实现

static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    return event->getAction();
}
class MotionEvent : public InputEvent {
public:
    …………
    inline int32_t getAction() const { return mAction; }
}

  可见,通过取到成员变量mAction的值与MotionEvent.ACTION_MASK做&操作,MotionEvent.ACTION_MASK的值是0xff,所以action的值是存在成员变量mAction的低8位。

pointer index的获取

final int actionIndex = ev.getActionIndex();
    public final int getActionIndex() {
        return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
                >> ACTION_POINTER_INDEX_SHIFT;
    }

  也是通过nativeGetAction方法先获得action的值,然后与ACTION_POINTER_INDEX_MASK做&操作,ACTION_POINTER_INDEX_MASK的值为0xff00,可以知道poniter index存在action的第二个字节内,取到值之后向右移动ACTION_POINTER_INDEX_SHIFT位,ACTION_POINTER_INDEX_SHIFT的值为8,所以就取到了第二个字节的值了。
现在总结一下,C++的MotionEvent类的成员变量mAction是一个4个字节的int32,第一个字节内存放action值,第二个字节内存放pointer index值。

  并且这个pointer index只在事件类型是ACTION_POINTER_DOWN和ACTION_POINTER_UP的时候存在成员变量mAction中。

pointer id的获取

    public final int getPointerId(int pointerIndex) {
        return nativeGetPointerId(mNativePtr, pointerIndex);
    }

  对应c++层的方法

static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass clazz,
        jlong nativePtr, jint pointerIndex) {
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    size_t pointerCount = event->getPointerCount();
    if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
        return -1;
    }
    return event->getPointerId(pointerIndex);
}

  找到对应的MotionEvent,然后验证下pointerIndex,再接着调用MotionEvent的getPointerId方法

    inline int32_t getPointerId(size_t pointerIndex) const {
        return mPointerProperties[pointerIndex].id;
    }

  得到的是成员变量向量mPointerProperties对应的pointerIndex下标的id值。mPointerProperties是Vector类型,看下PointerProperties数据结构

/*
 * Pointer property data.
 */
struct PointerProperties {
    // The id of the pointer.
    int32_t id;

    // The pointer tool type.
    int32_t toolType;

    inline void clear() {
        id = -1;
        toolType = 0;
    }

    bool operator==(const PointerProperties& other) const;
    inline bool operator!=(const PointerProperties& other) const {
        return !(*this == other);
    }

    void copyFrom(const PointerProperties& other);
};

  可见,里面存着id和toolType。
  关于pointer id需要知道的是,在一次事件中每个手指的id是不会变的,但是index是会变化的。例如刚开始A手指先按下,A的index=0,id=0,现在B手指也按下,B的index=1,id=1。后面A手指抬起了之后,B的index=0,id=1。所以在多点触控中,需要找到相同手指的属性信息,是通过id来确定手指的。

事件分解

  将原来的事件分解成含特定id的事件,代码如下:

    /**
     * Splits a motion event such that it includes only a subset of pointer ids.
     * @hide
     */
    @UnsupportedAppUsage
    public final MotionEvent split(int idBits) {
        MotionEvent ev = obtain();
        synchronized (gSharedTempLock) {
            final int oldPointerCount = nativeGetPointerCount(mNativePtr);
            ensureSharedTempPointerCapacity(oldPointerCount);
            final PointerProperties[] pp = gSharedTempPointerProperties;
            final PointerCoords[] pc = gSharedTempPointerCoords;
            final int[] map = gSharedTempPointerIndexMap;

            final int oldAction = nativeGetAction(mNativePtr);
            final int oldActionMasked = oldAction & ACTION_MASK;
            final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
                    >> ACTION_POINTER_INDEX_SHIFT;
            int newActionPointerIndex = -1;
            int newPointerCount = 0;
            for (int i = 0; i < oldPointerCount; i++) {
                nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
                final int idBit = 1 << pp[newPointerCount].id;
                if ((idBit & idBits) != 0) {
                    if (i == oldActionPointerIndex) {
                        newActionPointerIndex = newPointerCount;
                    }
                    map[newPointerCount] = i;
                    newPointerCount += 1;
                }
            }

            if (newPointerCount == 0) {
                throw new IllegalArgumentException("idBits did not match any ids in the event");
            }

            final int newAction;
            if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
                if (newActionPointerIndex < 0) {
                    // An unrelated pointer changed.
                    newAction = ACTION_MOVE;
                } else if (newPointerCount == 1) {
                    // The first/last pointer went down/up.
                    newAction = oldActionMasked == ACTION_POINTER_DOWN
                            ? ACTION_DOWN : ACTION_UP;
                } else {
                    // A secondary pointer went down/up.
                    newAction = oldActionMasked
                            | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
                }
            } else {
                // Simple up/down/cancel/move or other motion action.
                newAction = oldAction;
            }

            final int historySize = nativeGetHistorySize(mNativePtr);
            for (int h = 0; h <= historySize; h++) {
                final int historyPos = h == historySize ? HISTORY_CURRENT : h;

                for (int i = 0; i < newPointerCount; i++) {
                    nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
                }

                final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
                if (h == 0) {
                    ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
                            nativeGetDisplayId(mNativePtr),
                            newAction, nativeGetFlags(mNativePtr),
                            nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
                            nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
                            nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
                            nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                            nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
                            newPointerCount, pp, pc);
                } else {
                    nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
                }
            }
            return ev;
        }
    }

  事件分解发生在多点触控的时候,方法参数idBits是事件的id移位后的值,例如id=1,1<<id就得到2,这个时候idBits的值就是2。
  先看下获取MotionEvent的方法,MotionEvent ev = obtain(),

    static private MotionEvent obtain() {
        final MotionEvent ev;
        synchronized (gRecyclerLock) {
            ev = gRecyclerTop;
            if (ev == null) {
                return new MotionEvent();
            }
            gRecyclerTop = ev.mNext;
            gRecyclerUsed -= 1;
        }
        ev.mNext = null;
        ev.prepareForReuse();
        return ev;
    }

  该方法是从事件缓存中得到一个事件,事件缓存是一个链表,事件缓存最多是MAX_RECYCLED(10)个事件,如果没有缓存会先新建一个。该事件最终就是分解得到的新事件。
  回到split()函数里面,9行代码得到事件的pointer数量,15-18行会得到原事件的action和pointerIndex。
  代码21行开始循环,22行会将新事件的属性存放在pp数组(里面主要包括id和tooltype)中,通过原事件中id与新事件中的id进行匹配,会得到新事件的pointer数量newPointerCount,并且将原事件中新分解事件id对应pointerIndex存在map中,其中有个变量newActionPointerIndex,代表新事件中的pointerIndex。这个字段的赋值是有条件的,在匹配id的情况下,并且原事件中的index与原事件中的当前pointerIndex相等下情况下。这个变量newActionPointerIndex在新分解出来的事件的pointer数量多于1,并且在事件类型是ACTION_POINTER_DOWN或者ACTION_POINTER_UP的情况下,是会设置到C++类中的MotionEvent中的mAction里。从下面的代码分析中也能看出来。
  代码37-54行是为了获取新分解事件的action,在当前事件类型为ACTION_POINTER_DOWN或者ACTION_POINTER_UP的情况下,根据新分解事件的pointer序列和pointer数量来设置action的值。
  1、新分解事件的pointer序列的值小于0,action直接设置成ACTION_MOVE
  2、新分解事件的pointer序列的值不小于0,并且pointer数量=1,这种情况就是新分解的事件只有一个手指在触发触摸事件,pointer index也就是0。根据事件类型分别将action设置成ACTION_DOWN或ACTION_UP
  3、新分解事件的pointer序列的值不小于0,并且pointer数量>1,这种情况就是新分解的事件超过一个手指在触发触摸事件,需要将newActionPointerIndex的值设置在action值里面,oldActionMasked保持不变,还是ACTION_POINTER_DOWN或者ACTION_POINTER_UP。
  57行开始的循环是取出新分解事件对应的历史数据,然后设置到新分解事件中。
  60行开始取出原事件中新分解事件对应的序列的数据,放置到pc数组中。其中map数组中存放的是新分解事件的pointer index,通过map[i],historyPos,用nativeGetPointerCoords方法将原事件中的对应数据设置到pc数组中。65行通过h是否等于0,来执行ev.initialize方法还是nativeAddBatch方法。ev.initialize方法是初始化方法,会将事件的当前属性给初始化。而nativeAddBatch方法,是将历史数据设置到新分解事件中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值