Mouse Over Key

需求背景

        全局滑鼠应用于平板应用/浏览网页/玩游戏等。基于Android的物理按键实现鼠标操作.在android视图上自绘类似鼠标的箭头,物理按键点击上下左右和确定的操作,可让鼠标箭头上下左右移动和选中操作。

可行性分析

  1. 物理按键操作:按下、弹起。
  2. 鼠标操作状态:按下、弹起、移动、双击、长按、滑动、滚动。
  3. 按下、弹起、移动(down、move、up)是简单的触摸屏事件,而双击、长按、滑动、滚动需要根据运动的轨迹来做识别的

方案设计  

(0)按键映射表

        基于按键映射表实现鼠标操作,通过组合键控制按键及鼠标切换.

(1)灵敏度:

        Int move_accelerate[] = {0, 2, 2, 4, 4, 6, 8, 10, 12, 14, 16, 18}

        系统灵敏度配置符合用户使用意图,若需加快光标移动速度可减少数组成员,加大偏移.

(2)算法:

        REL_X/Y =1+move_accelerate[accelerate]

(3)系统轮询按键检测周期:

        20ms

(4)按照当前的算法,设备检测到连续按键按下:

        1次偏移1位置

        2次偏移3位置

        3次偏移3位置

        4次偏移5位置

        5次偏移5位置

        6次偏移7位置

代码解析

        Linux按键驱动

        common/drivers/xxx/input/keyboard/gpio_keypad.c.

        //内核模块化平台驱动加载接口module_platform_driver,其属module_init的一种封装.

        module_platform_driver(meson_gpio_kp_driver);

        static struct platform_driver meson_gpio_kp_driver = {

                .probe = meson_gpio_kp_probe,

                ……

                };

        static int meson_gpio_kp_probe(struct platform_device *pdev)

                {

                       ……

                       //通过of接口获取设备树节点scan_period数据

                       ret = of_property_read_u32(pdev->dev.of_node,

                                   "scan_period", &keypad->scan_period);

                        ……

                        //初始化sysfs文件系统class属性信息

                        keypad->kp_class.name = "gpio_keypad";

                        keypad->kp_class.owner = THIS_MODULE;

                        //构建class子目录属性文件组,详细逻辑见sysfs文件系统章节

                        keypad->kp_class.class_groups = meson_gpiokey_groups;

                     //注册class抽象设备模式,系统启动后sysfs文件系统/sys/class目录下有gpio_keypad子目录

                        ret = class_register(&keypad->kp_class);

                        ……

                        //为input_dev结构体申请一块内存

                        input_dev = input_allocate_device();

                        ……

                        //设置按键事件类型及事件代码

                        for (i = 0; i < keypad->key_size; i++) {

                      //input子系统提供设备驱动层API接口,设置可上报的事件类型和事件代码:普通按键类型

                                 input_set_capability(input_dev, keypad->key[i].key_type,

                                   keypad->key[i].code);

                                  dev_info(&pdev->dev, "%s key(%d) type(0x%x) registered.\n",

                                   keypad->key[i].name, keypad->key[i].code,

                                   keypad->key[i].key_type);

                         }

                        //设置鼠标事件类型及事件代码

                        for (i = BTN_MOUSE; i < BTN_SIDE; i++) {

                               //input子系统提供设备驱动层API接口,设置上报的事件类型和事件代码:鼠标确定键

                                input_set_capability(input_dev, EV_KEY, i);

                        }

                      //设置上报鼠标X轴/Y轴/滚轮事件

                        input_set_capability(input_dev, EV_REL, REL_X);

                        input_set_capability(input_dev, EV_REL, REL_Y);

                        input_set_capability(input_dev, EV_REL, REL_WHEEL);

                        ……

                        //初始化input_dev结构体后,调用输入子系统注册函数,注册一个新的输入设备

                        ret = input_register_device(keypad->input_dev);

                        ……

            //通过该内核接口见keypad保存成平台总线设备的私有数据,可调用platform_get_drvdata()获取

                        platform_set_drvdata(pdev, keypad);

                        //初始化内核定时器

                        timer_setup(&keypad->polling_timer,polling_timer_handler, 0);

                        if (keypad->use_irq) {

                        ……

                        } else {

                        //重新注册定时器到内核链表,使得定时器进入重新调度流程内,毫秒转成节拍.

                          mod_timer(&keypad->polling_timer,

                                jiffies + msecs_to_jiffies(keypad->scan_period));

                        }

                }

                static ssize_t keymode_show(struct class *cls, struct class_attribute *attr,

                                  char *buf)

                {

                        return sprintf( buf ,"%d\n" , key_work_mode );

                }

                static ssize_t keymode_store(struct class *cls, struct class_attribute *attr,

                              const char *buf, size_t count) {

                              if ( kstrtoint( buf , 10 , &key_work_mode ))

                                    {

                                          return -EINVAL;

                                    }

                            return count;

                        }

                        static CLASS_ATTR_RW(keymode);

                        static struct attribute *meson_gpiokey_attrs[] = {

                                        &class_attr_table.attr,

                                        &class_attr_keymode.attr,

                                        NULL

                          };

                        ATTRIBUTE_GROUPS(meson_gpiokey);

                        //定时器回调函数

                        static void polling_timer_handler(struct timer_list *t)

                        {

                                ……

                                if (keypad->use_irq) {

                                ……

                        }

                        } else {

                                //通过设备树属性可知,按键驱动采用轮询模式,非中断模式

                              for (i = 0; i < keypad->key_size; i++) {

                                   gpio_val = gpiod_get_value(keypad->key[i].desc);

             //按键模式下,上报按键事件

             if(switch_work_mode(keypad) == KEYPAD_MODE) {

                     if (gpio_val != keypad->key[i].current_status) {

                              keypad->key[i].count++;

                     } else {

                              keypad->key[i].count = 0;

                     }

                     if (gpio_val != keypad->key[i].current_status) {

                              keypad->current_key = &keypad->key[i];

                              report_key_code(keypad, gpio_val);

                     }

              } else {

                                    //根据按键鼠标映射表示上报对应事件

                     if ( keypad->key[i].code == rel_xy_key[0] ||

                              keypad->key[i].code == rel_xy_key[1] ||

                              keypad->key[i].code == rel_xy_key[2] ||

                              keypad->key[i].code == rel_xy_key[3]) {

                              if (gpio_val == GPIO_DOWN_STATUS) {

                                         keypad->key[i].count++;

                              } else {

                                         keypad->key[i].count = 0;

                              }

                               //鼠标模式下,上报REL_X及REL_Y事件

                              if (gpio_val == GPIO_DOWN_STATUS) {

                     keypad->current_key = &keypad->key[i];

                                         report_key_rel_xy_code(keypad, gpio_val);

                              }

                        } else {

                               if (gpio_val != keypad->key[i].current_status) {

                                         keypad->current_key = &keypad->key[i];

                                         report_key_mouse_code(keypad, gpio_val);

                                }

                              }

                        }

                        //按键模式与鼠标模式互切

                        if (gpio_val == GPIO_DOWN_STATUS) {

                                 get_key_work_mode(keypad);

                        }

                }

                mod_timer (&keypad->polling_timer,

                                jiffies + msecs_to_jiffies(keypad->scan_period));

        }

}

static int switch_work_mode(struct gpio_keypad *keypad)

{

        if (old_work_mode != key_work_mode) {

                old_work_mode = key_work_mode;

        }

        return key_work_mode;

}

#define GPIO_DOWN_STATUS              0

#define GPIO_UP_STATUS                  1

#define POINTER_MOVE_ACCELERATE {0, 2, 2, 4, 4, 6, 8, 10, 12, 14, 16, 18}

enum KEY_WORK_MODE {

        KEYPAD_MODE = 0,

        KEYMOUSE_MODE = 1

};

static int key_work_mode = KEYPAD_MODE;

static int old_work_mode = KEYPAD_MODE;

int switch_key[4] = { KEY_LEFT,KEY_UP,KEY_RIGHT,KEY_ENTER };

int rel_xy_key[4] = { KEY_LEFT,KEY_UP,KEY_RIGHT,KEY_DOWN };

static void report_key_mouse_code(struct gpio_keypad *keypad, int gpio_val)

{

        struct pin_desc *key = keypad->current_key;

        key->current_status = gpio_val;

        switch(key->code) {

                case KEY_ENTER:

                        if(key->current_status){

                                input_event(keypad->input_dev, EV_KEY, BTN_LEFT, 0);

                        } else {

                                input_event(keypad->input_dev, EV_KEY, BTN_LEFT, 1);

                        }

                        input_sync(keypad->input_dev);

                break;

                default:

                        if (key->current_status) {

                         input_event(keypad->input_dev, key->key_type,key->code, 0);

                        } else {

                         input_event(keypad->input_dev, key->key_type,key->code, 1);

                        }

                        input_sync(keypad->input_dev);

                break;

        }

        return;

}

static void report_key_rel_xy_code(struct gpio_keypad *keypad, int gpio_val)

{

    struct pin_desc *key = keypad->current_key;

    int move_accelerate[] = POINTER_MOVE_ACCELERATE;

    int accelerate = key->count;

    if (accelerate > ARRAY_SIZE(move_accelerate) - 1) {

             accelerate = ARRAY_SIZE(move_accelerate) - 1;

    }

    switch (key->code) {

      case KEY_UP:

         input_event(keypad->input_dev, EV_REL,REL_Y, -(1+move_accelerate[accelerate]));

         input_sync(keypad->input_dev);

         dev_info(&keypad->input_dev->dev, "-REL_Y %d count:%d accelerate:%d\n", key->code,key->count,accelerate);

         break;

      case KEY_DOWN:

         input_event(keypad->input_dev, EV_REL,REL_Y, (1+move_accelerate[accelerate]));

         input_sync(keypad->input_dev);

         dev_info(&keypad->input_dev->dev, "REL_Y %d count:%d accelerate:%d\n", key->code,key->count,accelerate);

        break;

       case KEY_LEFT:

         input_event(keypad->input_dev, EV_REL,REL_X, -(1+move_accelerate[accelerate]));

         input_sync(keypad->input_dev);

         dev_info(&keypad->input_dev->dev, "-REL_X %d count:%d accelerate:%d\n", key->code,key->count,accelerate);

        break;

       case KEY_RIGHT:

          input_event(keypad->input_dev, EV_REL,REL_X, (1+move_accelerate[accelerate]));

          input_sync(keypad->input_dev);

          dev_info(&keypad->input_dev->dev, "REL_X %d count:%d accelerate:%d\n", key->code,key->count,accelerate);

         break;

        default:

          break;

         }

         return;

}

static void get_key_work_mode(struct gpio_keypad *keypad)

{

        struct pin_desc *key = keypad->current_key;

        //第一个按下左键,记录标志数字1

        if (key->code == switch_key[0]) {

                switch_flag = 1;

        }

        //第二个按上键,记录标志数字2

        if (key->code == switch_key[1]) {

                if (switch_flag == 1) {

                        switch_flag = 2;

                }

        }

        //第三个按右键,记录标志数字3

        if (key->code == switch_key[2]) {

                if (switch_flag == 2) {

                        switch_flag = 3;

                }

        }

        //第四个按确定键,记录标志数字4

        if (key->code == switch_key[3]) {

                if (switch_flag == 3) {

                        switch_flag = 4;

                }

        }

        if (key->code != switch_key[0] &&

                key->code != switch_key[1] &&

                key->code != switch_key[2] &&

                key->code != switch_key[3]) {

                        switch_flag = 0;

        }

        return;

}

static int switch_work_mode(struct gpio_keypad *keypad)

{

        if (switch_flag == 4) {

             switch_flag = 0;

             key_work_mode = (key_work_mode == KEYPAD_MODE) ? KEYMOUSE_MODE : KEYPAD_MODE;

        }

        return key_work_mode;

}

struct pin_desc {

        int current_status;

        struct gpio_desc *desc;

        int irq_num;

        u32 code;

        u32 key_type;

        const char *name;

        int count;

};

struct gpio_keypad {

        int key_size;

        int use_irq;/* 1:irq mode ; 0:polling mode */

        int scan_period;

        struct pin_desc *key;

        struct pin_desc *current_key;

        struct timer_list polling_timer;

        struct input_dev *input_dev;

        struct class kp_class;

};

Linux设备树

        

./common/arch/arm64/boot/dts/xxx/xxxx.dts

gpio_keypad {

        compatible = "xxx, gpio_keypad";

        status = "okay";

        scan_period = <20>;

        key_num = <13>;

        key_name = "bluetooth", "BUT_B", "BUT_OK", "BUT_RETURN", "BUT_START", "BUT_M", "BUT_X", "BUT_Y", "BUT_A","BUT_VOL+", "BUT_OFF", "BUT_VOL-", "BUT_ESC";

        key_code = <600 108 28 102 139 113 105 106 103 115 116 114 15>;

        key_type = <EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY>;

        key-gpios = <&gpio  GPIOD_2   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_0   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_1   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_3   GPIO_ACTIVE_HIGH

                     &gpio  GPIOH_9   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_7   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_8   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_9   GPIO_ACTIVE_HIGH

                     &gpio  GPIOZ_10  GPIO_ACTIVE_HIGH

                     &gpio  GPIOD_3   GPIO_ACTIVE_HIGH

                     &gpio  GPIOD_5   GPIO_ACTIVE_HIGH

                     &gpio  GPIOD_8   GPIO_ACTIVE_HIGH

                     &gpio  GPIOD_10  GPIO_ACTIVE_HIGH>;

                               detect_mode = <0>;/*0:polling mode, 1:irq mode*/

        };

通识类储备

module_platform_driver
common/include/linux/platform_device.h:

#define module_platform_driver(__platform_driver) \

        module_driver(__platform_driver, platform_driver_register, \

                        platform_driver_unregister)

common/include/linux/device.h:

#define module_driver(__driver, __register, __unregister, ...) \

static int __init __driver##_init(void) \

{ \

        return __register(&(__driver) , ##__VA_ARGS__); \

} \

module_init(__driver##_init); \

static void __exit __driver##_exit(void) \

{ \

        __unregister(&(__driver) , ##__VA_ARGS__); \

} \

module_exit(__driver##_exit);

common/include/linux/module.h

#define module_init(x)  __initcall(x);

common/include/linux/init.h

linux内核对initcall进行等级划分,总共有0~7等级,0等级最高,由start_kernel()首先执行。

#define pure_initcall(fn)               __define_initcall(fn, 0)

#define core_initcall(fn)               __define_initcall(fn, 1)

#define core_initcall_sync(fn)          __define_initcall(fn, 1s)

#define postcore_initcall(fn)           __define_initcall(fn, 2)

#define postcore_initcall_sync(fn)      __define_initcall(fn, 2s)

#define arch_initcall(fn)               __define_initcall(fn, 3)

#define arch_initcall_sync(fn)          __define_initcall(fn, 3s)

#define subsys_initcall(fn)             __define_initcall(fn, 4)

#define subsys_initcall_sync(fn)        __define_initcall(fn, 4s)

#define fs_initcall(fn)                 __define_initcall(fn, 5)

#define fs_initcall_sync(fn)            __define_initcall(fn, 5s)

#define rootfs_initcall(fn)             __define_initcall(fn, rootfs)

#define device_initcall(fn)             __define_initcall(fn, 6)

#define device_initcall_sync(fn)        __define_initcall(fn, 6s)

#define late_initcall(fn)               __define_initcall(fn, 7)

#define late_initcall_sync(fn)          __define_initcall(fn, 7s)#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

#define ___define_initcall(fn, id, __sec) \

        static initcall_t __initcall_##fn##id __used \

                __attribute__((__section__(#__sec ".init"))) = fn;

typedef int (*initcall_t)(void)声明为函数指针类型的变量__initcall_ meson_gpio_kp_driver_6,并将__initcall_ meson_gpio_kp_driver_6赋值给该变量。即

static initcall_t __initcall_ meson_gpio_kp_driver_6 __used \

                __attribute__((__section__(".initcall6.init"))) = meson_gpio_kp_driver;

out/android11-5.4/common/System.map

console:/ # lsmod | grep gpio

gpio_keypad            20480  0

console:/ #

当前gpio_keypad驱动以KO形式插入,可通过在板运行objdump -t查看相关符号表:

objdump -t ./vendor-ramdisk/lib/modules/gpio_keypad.ko

./vendor-ramdisk/lib/modules/gpio_keypad.ko:     file format elf64-little

SYMBOL TABLE:

0000000000000000 l    d  .text  0000000000000000 .text

0000000000000000 l    d  .llvm_addrsig  0000000000000000 .llvm_addrsig

……

0000000000000020 l     O .data  00000000000000c8 meson_gpio_kp_driver

……

0000000000000000 g     F .init.text     0000000000000030 init_module

0000000000000000 g     F .exit.text     0000000000000024 cleanup_module

如此insmod驱动过程中,在系统内部会调用sys_init_module()去找init_module函数的入口地址。init_module与meson_gpio_kp_driver地址偏移是相关联的,详细深究驱动调用流程:sys_init_module->

Kernel/module.c:load_module().->do_init_module()->

Init/main.c:do_one_initcall()->

Drivers/base/driver.c->dirver_register()->

Bus_add_driver()->buf_for_each_device()->__driver_attach()->really_probe()->platform_drv_probe()->user_xxx_probe(). 其中__driver_attach()实现驱动与设备匹配driver_match_device()->drivers/base/platform.c:platform_match()->of_driver_match_device(dev,drv)。

若非KO形式驱动,静态内核映射out/android11-5.4/common/System.map表文件中查询相关信息, 可结合vmlinux.lds.S文件了解其放置section的逻辑。符号表如下:

ffffffc01135a218 D __initcall6_start

ffffffc01135a218 d __initcall_171_427_register_kernel_offset_dumper6

ffffffc01135a220 d __initcall_148_413_cpuinfo_regs_init6

ffffffc01135a228 d __initcall_161_80_register_cpu_hwcaps_dumper6

ffffffc01135a230 d __initcall_182_1203_armv8_pmu_driver_init6

ffffffc01135a238 d __initcall_159_208_arch_init_uprobes6

……

ffffffc01135a880 d __initcall_165_325_ gpio_led_driver_init6

……

__initcall_165_325_ gpio_led_driver_init6函数指针指向gpio_led_driver_init,内核启动后就是按照System.map表文件顺序注册驱动模块。

common/init/main.c

系统启动过程中,调用start_kernel()完成内核系统初始化操作,其中包括加载驱动,第一个循环轮询驱动框架大类别,第二循环对同一个级别不同驱动顺序进行注册。

static void __init do_initcall_level(int level, char *command_line)

{

……

        trace_initcall_level(initcall_level_names[level]);

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)

                do_one_initcall(initcall_from_entry(fn));

}

static void __init do_initcalls(void)

{

……

        for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {

                /* Parser modifies command_line, restore it each time */

                strcpy(command_line, saved_command_line);

                do_initcall_level(level, command_line);

        }

}

static initcall_entry_t *initcall_levels[] __initdata = {

        __initcall0_start,

        __initcall1_start,

        __initcall2_start,

        __initcall3_start,

        __initcall4_start,

        __initcall5_start,

        __initcall6_start,

        __initcall7_start,

        __initcall_end,

};

/* Keep these in sync with initcalls in include/linux/init.h */

static const char *initcall_level_names[] __initdata = {

        "pure",

        "core",

        "postcore",

        "arch",

        "subsys",

        "fs",

        "device",

        "late",

};

Note:

Start_kernel()->arch_call_rest_init()->rest_init():分别启动两个内核线程.

  1. Pid=kernel_thread(kernel_init, NULL, CLONE_FS);这个进程号1即init进程
  2. Pid=kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 这个进程号2即内核守护进程,保证内核正常工作。

1|console:/ # ps -A

USER            PID   PPID     VSZ    RSS WCHAN            ADDR S NAME

root              1      0   46052   8244 ep_poll             0 S init

root              2      0       0      0 kthreadd            0 S [kthreadd]

platform设备模型
common/include/linux/platform_device.h:

        platform_driver结构和device_driver结构类似。

struct platform_driver {

        int (*probe)(struct platform_device *);

        int (*remove)(struct platform_device *);

        void (*shutdown)(struct platform_device *);

        int (*suspend)(struct platform_device *, pm_message_t state);

        int (*resume)(struct platform_device *);

        struct device_driver driver;

        const struct platform_device_id *id_table;

        bool prevent_deferred_probe;

};

drivers/base/platform.c

platform模块的初始化

int __init platform_bus_init(void)

{

        int error;

        early_platform_cleanup();

        error = device_register(&platform_bus);

        if (error) {

                put_device(&platform_bus);

                return error;

        }

        error =  bus_register(&platform_bus_type);

        if (error)

                device_unregister(&platform_bus);

        of_platform_register_reconfig_notifier();

        return error;

}

input子系统
common/include/uapi/linux/major.h

        input子系统设备的主设备号都为13

        #define INPUT_MAJOR             13

        console:/dev/input # ls -als event3

        0 crw-rw---- 1 root input 13,  67 1970-01-01 08:00 event3

Sysfs文件系统
common/include/linux/sysfs.h

        #define __ATTRIBUTE_GROUPS(_name)                               \

        static const struct attribute_group *_name##_groups[] = {       \

                &_name##_group,                                         \

                NULL,                                                   \

        }

common/include/linux/device.h

        通过XXX_ATTR_RW宏定义可在目录/sys/devices,/sys/class,/sys/bus生成对应属性文件。用户态可以使用cat/echo命令对设备属性文件进行读写操作,实现用户态与内核态之间数据交换。

#define BUS_ATTR_RW(_name) \

        struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)

#define CLASS_ATTR_RW(_name) \

        struct class_attribute class_attr_##_name = __ATTR_RW(_name)

#define DEVICE_ATTR_RW(_name) \

        struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

struct device_attribute {

        struct attribute        attr;

        ssize_t (*show)(struct device *dev, struct device_attribute *attr,

                        char *buf);

        ssize_t (*store)(struct device *dev, struct device_attribute *attr,

                         const char *buf, size_t count);

};

定时器
common/include/linux/timer.h

#define timer_setup(timer, callback, flags)                     \

        __init_timer((timer), (callback), (flags))

#define __init_timer(_timer, _fn, _flags)                               \

        init_timer_key((_timer), (_fn), (_flags), NULL, NULL)

struct timer_list {

        /*

         * All fields that change during normal runtime grouped to the

         * same cacheline

         */

              //定时器列表元素

        struct hlist_node       entry;

              //定时器定时时间

        unsigned long           expires;

              //定时器回调函数,时间到执行回调函数

        void                    (*function)(struct timer_list *);

        u32                     flags;

        struct lockdep_map      lockdep_map;

        ANDROID_KABI_RESERVE(1);

        ANDROID_KABI_RESERVE(2);

};

common/kernel/time/timer.c

        内核定时器结构体timer_list,挂载在定时器链表上.

static void do_init_timer(struct timer_list *timer,

                          void (*func)(struct timer_list *),

                          unsigned int flags,

                          const char *name, struct lock_class_key *key)

{

        timer->entry.pprev = NULL;

        timer->function = func;

        timer->flags = flags | raw_smp_processor_id();

        lockdep_init_map(&timer->lockdep_map, name, key, 0);

}

void init_timer_key(struct timer_list *timer,

                    void (*func)(struct timer_list *), unsigned int flags,

                    const char *name, struct lock_class_key *key)

{

        debug_init(timer);

        do_init_timer(timer, func, flags, name, key);

}

common/include/linux/jiffies.h

        假设内核时间频率HZ为100,表示每秒触发100次时钟中断,每10ms触发一次,每次中断jiffies+1,则每秒jiffies增加了100.Linux中用全局变量jiffies表示系统自启动以来的时钟节拍数码。系统运行时间以S为单位计数,就等于jiffies/100.

static inline unsigned long _msecs_to_jiffies(const unsigned int m) {

        return (m + (MSEC_PER_SEC / HZ) - 1) / (MSEC_PER_SEC / HZ);

}

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要在vue2项目中安装hammer.js库:`npm install --save hammerjs` 然后在需要使用双指缩放功能的组件中引入hammer.js和vue2-hammer库: ```javascript import Hammer from 'hammerjs' import VueHammer from 'vue2-hammer' ``` 在组件中注册VueHammer: ```javascript export default { name: 'ImageDisplay', directives: { VueHammer }, ... } ``` 接下来,在mounted钩子函数中初始化hammer.js,并添加双指缩放功能: ```javascript mounted() { // 初始化hammer.js const mc = new Hammer.Manager(this.$el) // 添加pan和pinch事件 mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 })) mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get('pan')]) // 定义初始状态 let posX = 0 let posY = 0 let scale = 1 let lastScale = 1 let lastPosX = 0 let lastPosY = 0 let maxPosX = 0 let maxPosY = 0 let transform = '' // 监听pan事件 mc.on('pan', (e) => { posX = e.deltaX + lastPosX posY = e.deltaY + lastPosY maxPosX = Math.ceil((scale - 1) * this.$el.clientWidth / 2) maxPosY = Math.ceil((scale - 1) * this.$el.clientHeight / 2) if (posX > maxPosX) { posX = maxPosX } if (posX < -maxPosX) { posX = -maxPosX } if (posY > maxPosY) { posY = maxPosY } if (posY < -maxPosY) { posY = -maxPosY } transform = `translate3d(${posX}px, ${posY}px, 0) scale3d(${scale}, ${scale}, 1)` this.$refs[this.list[this.listIndex].id].style.transform = transform }) // 监听pinch事件 mc.on('pinch', (e) => { scale = Math.max(.5, Math.min(lastScale * (e.scale), 5)) transform = `translate3d(${posX}px, ${posY}px, 0) scale3d(${scale}, ${scale}, 1)` this.$refs[this.list[this.listIndex].id].style.transform = transform }) // 监听pan结束事件 mc.on('panend', () => { lastPosX = posX < maxPosX ? posX : maxPosX lastPosY = posY < maxPosY ? posY : maxPosY }) // 监听pinch结束事件 mc.on('pinchend', () => { lastScale = scale }) } ``` 最后,在需要使用双指缩放功能的模板中添加VueHammer指令: ```html <template> <div class="home"> <div class="box" @mouseout="out" @mouseover="over" v-vue-hammer> <div style="width: 100%;height: 100%;"> <img v-for="(item,index) in list" :ref="item.id" v-show="listIndex === index" :key="index" :src="item.url" alt="" /> </div> <p class="left1" @click="changePage(prevIndex)"> < </p> <ul> <li :class="{color:index == listIndex}" v-for="(item,index) in list" @click="changePage(index)" :key="index" /> </ul> <p class="right1" @click="changePage(nextIndex)"> > </p> </div> </div> </template> ``` 这样,双指缩放功能就被添加到了图片展示组件中。需要注意的是,在mounted钩子函数中添加双指缩放功能时,需要根据实际情况修改transform变量的值,以实现正确的图片缩放和拖动效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值