Android多点触摸的实现
来源:互联网 发布:unity3d 太空舱 编辑:程序博客网 时间:2024/04/30 15:32
转自:http://blog.csdn.net/xiyu_1986/article/details/6997078
第一章 摘要
在Linux内核支持的基础上,Android在其 2.0 源码中加入多点触摸功能。由此触摸屏在 Android的 frameworks被完全分为 2 种实现途径:单点触摸屏的单点方式,多点触摸屏的单点和多点方式。
第二章 软件位
在Linux的input.h中,多点触摸功能依赖于以下几个主要的软件位:
………………………..
#define SYN_REPORT0
#define SYN_CONFIG1
#define SYN_MT_REPORT2
………………………...
#define ABS_MT_TOUCH_MAJOR0x30/* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR0x31/* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR0x32/* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR0x33/* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION0x34/* Ellipse orientation */
#define ABS_MT_POSITION_X0x35/* Center X ellipse position */
#define ABS_MT_POSITION_Y0x36/* Center Y ellipse position */
#define ABS_MT_TOOL_TYPE0x37/* Type of touching device */
#define ABS_MT_BLOB_ID0x38/* Group a set of packets as a blob */
…………………………
在Android中对应的软件位定义在RawInputEvent.java中 :
…………………..
public class RawInputEvent {
……………….
public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
………………..
public static final int ABS_MT_TOUCH_MAJOR = 0x30;
public static final int ABS_MT_TOUCH_MINOR = 0x31;
public static final int ABS_MT_WIDTH_MAJOR = 0x32;
public static final int ABS_MT_WIDTH_MINOR = 0x33;
public static final int ABS_MT_ORIENTATION = 0x34;
public static final int ABS_MT_POSITION_X = 0x35;
public static final int ABS_MT_POSITION_Y = 0x36;
public static final int ABS_MT_TOOL_TYPE = 0x37;
public static final int ABS_MT_BLOB_ID = 0x38;
………………….
public static final int SYN_REPORT = 0;
public static final int SYN_CONFIG = 1;
public static final int SYN_MT_REPORT = 2;
………………..
在Android中,多点触摸的实现方法在具体的代码实现中和单点是完全区分开的。在Android代码的 EventHub.cpp 中,单点屏和多点屏由如下代码段来判定:
int EventHub::open_device(const char *deviceName)
{
………………………
if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask)
&& test_bit(ABS_MT_POSITION_X, abs_bitmask)
&& test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;
//LOGI("It is a multi-touch screen!");
}
//single-touch?
else if (test_bit(BTN_TOUCH, key_bitmask)
&& test_bit(ABS_X, abs_bitmask)
&& test_bit(ABS_Y, abs_bitmask)) {
device->classes |= CLASS_TOUCHSCREEN;
//LOGI("It is a single-touch screen!");
}
………………..
}
我们知道,在触摸屏驱动中,通常在probe函数中会调用input_set_abs_params给设备的input_dev结构体初始化,这些input_dev的参数会在 Android 的 EventHub.cpp 中被读取。如上可知,如果我们的触摸屏想被当成多点屏被处理,只需要在驱动中给input_dev额外增加以下几个参数即可:
input_set_abs_params(mcs_data.input, ABS_MT_POSITION_X,pdata->abs_x_min, pdata->abs_x_max,0, 0);
input_set_abs_params(mcs_data.input, ABS_MT_POSITION_Y,pdata->abs_y_min, pdata->abs_y_max,0, 0);
input_set_abs_params(mcs_data.input, ABS_MT_TOUCH_MAJOR, 0, 15,0, 0);
//相当于单点屏的ABX_PRESSURE
input_set_abs_params(mcs_data.input, ABS_MT_WIDTH_MAJOR, 0, 15,0, 0);
//相当于单点屏的ABS_TOOL_WIDTH
注:
为了让我们的驱动代码支持所有的Android版本,无论是多点屏还是单点屏,一般都会保留单点屏的事件,如ABS_TOUCH, ABS_PRESSURE, ABS_X, ABS_Y等。另外,由于在Android2.0前支持多点的frameworks 大多是用 HAT0X,HAT0Y 来实现的,所以一般也会上报这 2 个事件。
第三章 同步方式
由于多点触摸技术需要采集到多个点,然后再一起处理这些点,所以在软件实现中需要保证每一波点的准确性和完整性。因此,Linux内核提供了input_mt_sync(struct input_dev * input)函数。在每波的每个点上报后需要紧跟一句input_mt_sync(),当这波所有点上报后再使用 input_sync()进行同步。例如一波要上报 3 个点:
/*上报点1*/
……………..
input_mt_sync(input);
/*上报点2*/
……………..
input_mt_sync(input);
/*上报点3*/
……………..
input_mt_sync(input);
input_sync(input);
注:即使是仅上报一个点的单点事件,也需要一次input_my_sync。
在Android的KeyInputQueue.java中,系统创建了一个线程,然后把所有的Input事件放入一个队列:
public abstract class KeyInputQueue {
……………………
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
try {
RawInputEvent ev = new RawInputEvent();
while (true) {
InputDevice di;
// block, doesn't release the monitor
readEvent(ev);
if (ev.type == RawInputEvent.EV_DEVICE_ADDED) {
synchronized (mFirst) {
di = newInputDevice(ev.deviceId);
mDevices.put(ev.deviceId, di);
configChanged = true;
}
} else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {
synchronized (mFirst) {
Log.i(TAG, "Device removed: id=0x"
+ Integer.toHexString(ev.deviceId));
di = mDevices.get(ev.deviceId);
if (di != null) {
mDevices.delete(ev.deviceId);
configChanged = true;
} else {
Log.w(TAG, "Bad device id: " + ev.deviceId);
}
}
} else {
di = getInputDevice(ev.deviceId);
// first crack at it
send = preprocessEvent(di, ev);
if (ev.type == RawInputEvent.EV_KEY) {
di.mMetaKeysState = makeMetaState(ev.keycode,
ev.value != 0, di.mMetaKeysState);
mHaveGlobalMetaState = false;
}
}
if (di == null) {
continue;
}
if (configChanged) {
synchronized (mFirst) {
addLocked(di, SystemClock.uptimeMillis(), 0,
RawInputEvent.CLASS_CONFIGURATION_CHANGED,
null);
}
}
if (!send) {
continue;
}
synchronized (mFirst) {
……………………….
if (type == RawInputEvent.EV_KEY &&
(classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
(scancode < RawInputEvent.BTN_FIRST ||
scancode > RawInputEvent.BTN_LAST)) {
/*键盘按键事件 */
…………………….
} else if (ev.type == RawInputEvent.EV_KEY) {
/*下面是EV_KEY事件分支,只支持单点的触摸屏有按键事件,
*而支持多点的触摸屏没有按键事件,只有绝对坐标事件
*/
if (ev.scancode == RawInputEvent.BTN_TOUCH &&
(classes&(RawInputEvent.CLASS_TOUCHSCREEN
|RawInputEvent.CLASS_TOUCHSCREEN_MT))
== RawInputEvent.CLASS_TOUCHSCREEN) {
/*只支持单点的触摸屏的按键事件 */
…………………………………
} else if (ev.scancode == RawInputEvent.BTN_MOUSE &&
(classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
/*鼠标和轨迹球 */
……………………….
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
/*下面才是多点触摸屏上报的事件 */
if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) {
di.mAbs.changed = true;
di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+ MotionEvent.SAMPLE_PRESSURE] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) {
di.mAbs.changed = true;
di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+ MotionEvent.SAMPLE_X] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) {
di.mAbs.changed = true;
di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+ MotionEvent.SAMPLE_Y] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) {
di.mAbs.changed = true;
di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+ MotionEvent.SAMPLE_SIZE] = ev.value;
}
/*上面这段就是多点触摸屏要用到的事件上报部分;
*使用一个数组mNextData来保存,其中di.mAbs.mAddingPointerOffset
*是当前点的偏移量,在每个点中还在MotionEvent中定义了X,Y,PRESSURE
* SIZE等偏移量,多点触摸屏的压力值由绝对坐标事件ABS_MT_TOUCH_MAJOR确定。
*/
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
/*这里是对单点触摸屏上报坐标事件的新的处理方法,同样使用了数组来保存 */
if (ev.scancode == RawInputEvent.ABS_X) {
di.mAbs.changed = true;
di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_Y) {
di.mAbs.changed = true;
di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_PRESSURE) {
di.mAbs.changed = true;
di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value;
di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
+ MotionEvent.SAMPLE_PRESSURE] = ev.value;
} else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) {
di.mAbs.changed = true;
di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value;
di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
+ MotionEvent.SAMPLE_SIZE] = ev.value;
}
…………………………………………….}
/*下面是关键的同步处理方法 */
if (ev.type == RawInputEvent.EV_SYN
&& ev.scancode == RawInputEvent.SYN_MT_REPORT
&& di.mAbs != null) {
/*在这里实现了对SYN_MT_REPORT事件的处理,
*改变了di.mAbs.mAddingPointerOffset的值,从而将
*新增的点的参数保存到下一组偏移量的位置。
*/
…………………….
final int newOffset = (num <= InputDevice.MAX_POINTERS)
? (num * MotionEvent.NUM_SAMPLE_DATA)
: (InputDevice.MAX_POINTERS *
MotionEvent.NUM_SAMPLE_DATA);
di.mAbs.mAddingPointerOffset = newOffset;
di.mAbs.mNextData[newOffset
+ MotionEvent.SAMPLE_PRESSURE] = 0;
}
……………….
} else if (send || (ev.type == RawInputEvent.EV_SYN
&& ev.scancode == RawInputEvent.SYN_REPORT)) {
/*这里实现了对SYN_REPORT事件的处理
*如果是单点触摸屏,即使用di.curTouchVals数组保存的点
*转化为多点触摸屏的mNextData数组保存
*最后是调用InputDevice中的generateAbsMotion处理这个数组。这个函数
*的具体实现方法将在后面补充
*/
…………………………..
ms.finish(); //重置所有点和偏移量
……………………..
}
由于上层的代码仍然使用ABS_X, ABS_Y这些事件,为了使多点触摸屏代码有良好的兼容性,在KeyInputQueue.java的最后,我们将多点事件类型转化为单点事件类型,返回一个新的InputDevice:
private InputDevice newInputDevice(int deviceId) {
int classes = getDeviceClasses(deviceId);
String name = getDeviceName(deviceId);
InputDevice.AbsoluteInfo absX;
InputDevice.AbsoluteInfo absY;
InputDevice.AbsoluteInfo absPressure;
InputDevice.AbsoluteInfo absSize;
if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
absX = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_MT_POSITION_X, "X");
absY = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_MT_POSITION_Y, "Y");
absPressure = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure");
absSize = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size");
} else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
absX = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_X, "X");
absY = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_Y, "Y");
absPressure = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_PRESSURE, "Pressure");
absSize = loadAbsoluteInfo(deviceId,
RawInputEvent.ABS_TOOL_WIDTH, "Size");
} else {
absX = null;
absY = null;
absPressure = null;
absSize = null;
}
return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize);
}
第四章 触摸事件 数组的处理
上面我们曾说到 generateAbsMotion 这个方法,它们在InputDevice 类的内部类 MotionState 中实现,该类被定义为 InputDevice 类的静态成员类 (static class) ,调用它们可以直接使用:
InputDeviceClass.MotionStateClass.generateAbsMotion()。
public class InputDevice {
……………………………
static class MotionState {//下面是这个内部类的几个函数
……………………………….
/* mLastNumPointers为上一个动作在触屏上按键的个数 */
int mLastNumPointers = 0;
final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
/* mNextNumPointers为下一个动作在触屏上按键的个数 */
/*通过对这2个值大小的判断,可以确认新的动作方式*/
int mNextNumPointers = 0;
final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)
+ MotionEvent.NUM_SAMPLE_DATA];
………………………………….
int[] generateAveragedData(int upOrDownPointer, int lastNumPointers,
int nextNumPointers) { //平滑处理
…………………………………….
}
private boolean assignPointer(int nextIndex, boolean allowOverlap) {//指派按键
……………………………………
}
private int updatePointerIdentifiers() {//更新按键ID
………………………………….
}
void removeOldPointer(int index) {
……………………………………
}
MotionEvent generateAbsMotion(InputDevice device, long curTime,
long curTimeNano, Display display, int orientation,
int metaState) {
……………………………………
int upOrDownPointer = updatePointerIdentifiers();
final int numPointers = mLastNumPointers;
………………………………………
/*对行为的判断 */
if (nextNumPointers != lastNumPointers) { //前后在触屏上点个数不同,说明有手指up或down
if (nextNumPointers > lastNumPointers) {
if (lastNumPointers == 0) { //上次触屏上没有按键,新值又大,说明有按键按下
action = MotionEvent.ACTION_DOWN;
mDownTime = curTime;
} else {//有新点按下,分配给新点ID号
action = MotionEvent.ACTION_POINTER_DOWN
| (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
}
} else {//新动作比原来pointer数量少
if (numPointers == 1) { //原来只有1个点按下,所以现在的动作是全部按键up
action = MotionEvent.ACTION_UP;
} else { //原来有多点按下,现在是ACTION_POINTER_UP动作,
action = MotionEvent.ACTION_POINTER_UP
| (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
}
}
currentMove = null;
} else { //前后触屏pointer个数相同,所以是移动动作ACTION_MOVE
action = MotionEvent.ACTION_MOVE;
}
/*后面则是根据屏幕的height和width以及屏幕方向orientation对这些点进行二次处理*/
……………………………………
}
MotionEvent generateRelMotion(InputDevice device, long curTime,
long curTimeNano, int orientation, int metaState) {
/*轨迹球等的处理方式 */
…………………………………………..
}
void finish() { //结束这轮动作
mNextNumPointers = mAddingPointerOffset = 0;
mNextData[MotionEvent.SAMPLE_PRESSURE] = 0;
}
…………………………………….
}
……………………………….
……………………………………
}
第五章 接口
我们平时所看到的用2个手指对图片放大缩小、旋转等手势都是由应用程序编写浏览器实现的。这些应用程序大多会使用Android2.0以上的在 MotionEvent.java 中实现的新的接口。所以,我们还需要给 MotionEvent类补充尽量全的接口。这里可以完全参照 google 新的 android 代码。
第六章 总结
综上,在硬件支持基础上,Android1.6如果要实现多点触摸功能,主要工作可简述为以下几个方面:
1、 驱动中,除了增加多点的事件上报方式,还要完全更改单点的事件上报方式。
2、 Android的Frameworks 层需要修改的文件有: EventHub.cpp , RawInputEvent.java , KeyInputQueue.java , InputDevice.java , MotionEvent.java 。
3、 编写新的支持多点触摸功能的多媒体浏览器。
4、 为了代码简练,android2.0在轨迹球和单点屏事件方式中也全使用了新的变量名,以方便多点屏事件同样能使用这些变量,所以修改时还需要注意许多细节方面。
- Android多点触摸的实现
- Android多点触摸的实现
- Android多点触摸实现
- Android多点触摸的实现 (转)
- Android多点触摸的实现(1)
- Android多点触摸的实现(2)
- Android多点触摸的实现(3)
- Android多点触摸的判断
- android的多点触摸效果
- Android开发中实现多点触摸的方法
- Android开发中实现多点触摸的方法
- Android开发中实现多点触摸的方法
- 解析Android开发中多点触摸的实现方法
- android 多点触摸图片缩放的具体实现方法
- 解析Android 中多点触摸的实现方法
- Android开发中多点触摸的实现方法
- android 多点触摸(实现按下画圆,抬起消失的dome)
- [Android] Android开发中实现多点触摸
- CentOS目录结构详解
- 搜狗笔试
- eclipse导入工程中文乱码问题
- Android版本:使用API进行语音到文本的转换
- Android 多点触摸接口
- Android多点触摸的实现
- 《蔡康永的说话之道》--备忘
- ie7+内嵌XMLHttpRequest对象
- UltraISO v9.5.3.2900
- 有趣的jquery.data
- 一种简便定义javascript命名空间的方法
- 卸载linux时,删除grub的方法
- Android Sensor传感器系统架构初探
- Ldd3 学习笔记2 — simple