Android2.3触摸屏功能详解
手机设备上常用触摸屏进行用户操作,非常方便快捷,而且正好有个项目上用到这个设备,所以就花时间研究了一下。好了,还是老规则:大体了解概念先,细节线索找代码:
InputReader.cpp 中有针对单点触摸SingleTouchInputMapper及多点触摸MultiTouchInputMapper的处理代码,这两个类都继承自TouchInputMapper,由syncTouch处理最终的触摸屏动作的发送。
1、首先了解一下触摸屏中的几个参数概念:
“接触”一词用来描述一个物体直接碰到另一个物体的表面。
单点触摸的参数解析:
ABS_X,ABS_Y分别对应触摸屏的x,y坐标
ABS_PRESSURE是压力值,一般触摸屏也只是分是否有按下去,按下去的话值会大于多少,没有按的话值小于多少
ABS_TOOL_WIDTH 触摸工具的宽度
多点触摸的参数解析:
ABS_MT_POSITION_X 接触面的形心的X坐标值
ABS_MT_POSITION_Y 接触面的形心的Y坐标值
ABS_MT_TOUCH_MAJOR和ABS_MT_WIDTH_MAJOR 分别被用来提供手指的大小和触摸面积大小
TOUCH 和 WIDTH参数给出了个,想想如果一个手指按在玻璃上,透过玻璃你将看到两个区域,一个是手指与玻璃接触的区域,用 ABS_MT_TOUCH_MAJOR描述,一个是手指本身大小的区域,ABS_MT_WIDTH_MAJOR描述, 手指与玻璃接触的面积要小于手指本身的大小,通过这两个参数,可以换算出手指的压力。也可通过 ABS_MT_PRESSURE参数直接提供手指的压力。
除了 MAJOR这个参数,还可以提供一个 MINOR参数,手指可以被认为是一个椭圆,MAJOR和 MINOR可以认为是这个椭圆的长轴和短轴,椭圆的中心可以被 ORIENTATION这个参数描述。
ABS_MT_PRESSURE
接触工具对接触面的压力大小,可以用来代替上面的四个参数。
ABS_MT_ORIENTATION
描述随圆的转动趋势,这是一个抽相值,O值表示接触面在平行与触摸屏的Y轴,向左是负值,向右是正值,如果完全平行于X轴,则上向返回最大值。如果接触面是圆形,则可以忽略这个参数。如果内核不能获得这个参数有有效值,但可以区分接触面的长短轴,这个功能还是可以被部份支持,在一些设备中, ABS_MT_ORIENTATION 的值只能是 0和1。
ABS_MT_TOOL_TYPE描述接触工具类型(手指,触控笔等 ),很多内核驱动无法区分此参数如手指及笔,如果是这样,该参数可以不用,协议目前支持MT_TOOL_FINGER和MT_TOOL_PEN两种类型。
ABS_MT_BLOB_ID形状集ID,集合几个点以描述一个形状,很多驱动没有形状属性,此参数可以不用。
ABS_MT_TRACKING_ID描述了从接触开始到释放的整个过程的集合,如果设备不支持,此参数可是不用。
计算方法:
一些设备将触摸面作为一个矩形上报,可以通过下面这些公式来计算出协议中所需要的信息。
ABS_MT_TOUCH_MAJOR := max(X, Y)
ABS_MT_TOUCH_MINOR := min(X, Y)
ABS_MT_ORIENTATION := bool(X > Y)
ABS_MT_ORIENTATION的取值范围为0至1,用来标识矩形接触面偏向X轴或Y轴的程度。
触摸轨迹
仅有少数设备可以明触的标识真实的 trackingID,多数情况下 trackingID只能来标识一次触摸动作的过程。
手势
多点触摸指定的应用是创建手势动作, TOUCH和 WIDTH参数经常用来区别手指的压力和手指间的距离,另外 MINOR类的参数可以用来区别设备的接触面的大小(点接触还是面接触),ORIENTATION可以产生旋转事件。
以上的含义从http://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt 翻译而来,可以自行下载
2、代码入手
第一部分:直接写/dev/input/eventX接点实现
单点触摸事件发送序列:
EV_KEY (BTN_TOUCH) 发送是否在按下或弹出(0或者1)
ABS_X
ABS_Y
ABS_PRESSURE
ABS_TOOL_WIDTH
代码演示:
event.type = EV_KEY;
event.code = BTN_TOUCH;
mDown = (istEvent->pointers[0].abs_pressure > 0)?1:0 ;
Event.value = mDown;
write(fd,&event,sizeof(event)) ;
/* 触摸屏键按下坐标定位 */
event.type = EV_ABS;
event.code = ABS_X;
event.value = istEvent->pointers[0].abs_x;
write(fd,&event,sizeof(event)) ;
event.type = EV_ABS;
event.code = ABS_Y;
event.value = istEvent->pointers[0].abs_y;
write(fd,&event,sizeof(event)) ;
/* 触摸屏接触面的压力大小 */
event.type = EV_ABS;
event.code = ABS_PRESSURE;
event.value = istEvent->pointers[0].abs_pressure;
write(fd,&event,sizeof(event)) ;
event.type = EV_ABS;
event.code = ABS_TOOL_WIDTH;
event.value = istEvent->pointers[0].abs_touch_major;
/* 结束完整帧数据,发送同步信号 */
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));
Ok,非常简单,对比inputreader.cpp中的代码,注意process代码:
void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: //一定要注意发送这个event,否则触摸事件不会发送出去,直接丢弃
switch (rawEvent->scanCode) {
case BTN_TOUCH:
mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
mAccumulator.btnTouch = rawEvent->value != 0;
// Don't sync immediately. Wait until the next SYN_REPORT since we might
// not have received valid position information yet. This logic assumes that
// BTN_TOUCH is always followed by SYN_REPORT as part of a complete
// packet.
break;
}
break;
...
}
void SingleTouchInputMapper::sync(nsecs_t when) {
...
if (mDown) { // 这就是上面为何要发送的原因
mCurrentTouch.pointerCount = 1;
mCurrentTouch.pointers[0].id = 0;
mCurrentTouch.pointers[0].x = mX;
mCurrentTouch.pointers[0].y = mY;
mCurrentTouch.pointers[0].pressure = mPressure;
mCurrentTouch.pointers[0].touchMajor = 0;
mCurrentTouch.pointers[0].touchMinor = 0;
mCurrentTouch.pointers[0].toolMajor = mToolWidth;
mCurrentTouch.pointers[0].toolMinor = mToolWidth;
mCurrentTouch.pointers[0].orientation = 0;
mCurrentTouch.idToIndex[0] = 0;
mCurrentTouch.idBits.markBit(0);
}
...
}
多点触摸事件发送序列:
ABS_MT_TOUCH_MAJOR
ABS_MT_PRESSURE
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_MT_REPORT //上报第一个点
ABS_MT_TOUCH_MAJOR
ABS_MT_PRESSURE
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_MT_REPORT //上报第二个点
... //以上顺序组织多点touch event即可
SYN_REPORT //最后发送的完整包动作
代码演示:
for(pointIndex=0;pointIndex<imtEvent->pointnums;pointIndex++)
{
/* 触摸屏键按下坐标定位 */
event.type = EV_ABS;
event.code = ABS_MT_POSITION_X;
event.value = imtEvent->pointers[pointIndex].abs_x;
write(fd,&event,sizeof(event)) ;
event.type = EV_ABS;
event.code = ABS_MT_POSITION_Y;
event.value = imtEvent->pointers[pointIndex].abs_y;
write(fd,&event,sizeof(event)) ;
/* 触摸屏接触面的压力大小 */
event.type = EV_ABS;
event.code = ABS_MT_PRESSURE;
event.value = imtEvent->pointers[pointIndex].abs_pressure;
write(fd,&event,sizeof(event)) ;
/* 触摸屏接触面积大小 */
event.type = EV_ABS;
event.code = ABS_MT_TOUCH_MAJOR;
event.value = imtEvent->pointers[pointIndex].abs_touch_major;
event.type = EV_SYN;
event.code = SYN_MT_REPORT;
event.value = 0;
write(fd,&event,sizeof(event)) ;
}
/* 结束完整帧数据,发送同步信号 */
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd,&event,sizeof(event)) ;
Ok,只要深入研究下inputreader.cpp的代码即可轻松解决这些问题,更扩展的功能扩大及缩小,以及旋转的功能都可以搞定。
对于多点触摸的功能,首先在linux内核的input输入模型需要支持,这个在linux/input.h可以见到,多点触摸功能依赖于以下几个主要的软件位:
#define ABS_MT_TOUCH_MAJOR 0x30/* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31/* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32/* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33/* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34/* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35/* Center X ellipse position */
#define ABS_MT_POSITION_Y 0x36/* Center Y ellipse position */
#define ABS_MT_TOOL_TYPE 0x37/* Type of touching device */
#define ABS_MT_BLOB_ID 0x38/* Group a set of packets as a blob */
第二部分:利用linux input输入模型发送
主要函数介绍:
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
static inline void input_mt_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_MT_REPORT, 0);
}
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
代码示例:
设定初始参数(支持单点触摸及多点触摸):
set_bit(EV_SYN, ts->input_dev->evbit);
set_bit(EV_KEY, ts->input_dev->evbit);
set_bit(EV_ABS, ts->input_dev->evbit);
set_bit(BTN_TOUCH,ts->input_dev->keybit);
max_x = 1280;
max_y = 720;
input_set_abs_params(ts->input_dev, ABS_X, 0, max_x, 0, 0);
input_set_abs_params(ts->input_dev, ABS_Y, 0, max_y, 0, 0);
input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, max_x, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
input_register_device(ts->input_dev);
按下时:
for(i=0;i<finger;i++){
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 1);
input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 100);
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, ts->x[i]);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, ts->y[i]);
input_mt_sync(ts->input_dev);
ts->upsend=0;
}
input_sync(ts->input_dev);
弹起时:
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0);
input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0);
input_mt_sync(ts->input_dev);
input_sync(ts->input_dev);