SCMLife.com

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 2611|回复: 1

[推荐] Dojo 手势功能介绍

[复制链接]
发表于 2013-2-17 16:48:01 | 显示全部楼层 |阅读模式
本帖最后由 技术狂人 于 2013-2-17 16:50 编辑
* J( k9 x4 W3 K/ f$ X* K* w$ Y1 S3 u2 F) o4 ?0 v
Dojo 目前提供支持的手势有:
9 X" g- ^) i9 Q# P+ _! F) Q; }
  • Tap:在屏幕上单击。
  • Double tap:在屏幕上双击。
  • Hold:手指轻点屏幕并持续一段时间。
  • Swipe:手指在屏幕上滑动。Dojo Api 对该手势的支持能够提供滑动持续的时间与位移信息。
    ! W8 ^; ^% i/ e8 y4 A' h' O
HTML 中的 Touch 事件
) p9 q( E3 p+ oHTML Touch 系列事件是实现手势的基础。通过对 Touch 事件的监听,我们可以得到手指在屏幕上的滑动轨迹,从而判断出用户的手势动作。因此,要想研究 Dojo 中手势的实现,我们需要先了解 HTML 中 Touch 系列事件的触发顺序和事件对象所包含的数据。7 \% q2 o# O( O8 k& I
触控设备(例如智能手机)上支持触摸事件的浏览器一般都实现了以下几个事件:6 T$ v3 n/ p. j( N( _
  • touchstart:该事件在用户手指落在设备屏幕上时发生。
  • touchmove:该事件当用户手指在屏幕上滑动时发生。
  • touchend:该事件当用户手指离开设备屏幕时发生。
  • touchcancel:该事件当用户手指移出文档范围或接触点数目超过浏览器支持时发生。
    : \$ T. `. u$ r1 ?  o+ C- |- L9 H
在 W3C 关于 Touch 事件的规范中规定了 Touch 事件的数据结构。6 r' B4 J4 k% O2 M

% H* r( `8 b; M: L8 ~( ]3 ~7 {清单 1 Touch 事件接口. _& N9 Z# i* b7 ^; ]4 k' H7 s
interface TouchEvent : UIEvent {
8 k$ L, M3 |" c1 Y    readonly attribute TouchList   touches;
9 h$ W" b' z' w( G9 a8 @9 r  s    readonly attribute TouchList   targetTouches;
1 [- m# O1 O: c1 y' G. I    readonly attribute TouchList   changedTouches; ! x. Q8 q4 F: P8 N# R4 U8 V( a
    readonly attribute boolean     altKey; 4 M! q1 |# {( m/ [5 R
    readonly attribute boolean     metaKey;
2 t0 j  ?# @" Z/ c- Y3 D" U, u    readonly attribute boolean     ctrlKey; ! Q, }( V- q; O8 ~+ @) n
    readonly attribute boolean     shiftKey; $ T9 g5 p8 b- D. X9 n
    readonly attribute EventTarget relatedTarget; 0 P3 P+ X7 [4 N; Y3 C% C. V
};
/ v- m3 D; Y3 W
在该接口中,我们注意到有三个属性的类型都是 TouchList。 其中 touches 中包含所有发生在当前文档节点上的 Touch 信息,targetTouches 包含所有以当前文档节点为 target 的 Touch 信息,因为 Touch 事件是以 Bubble 方式在文档树中传递,所以这两个列表不一定相同。
6 q0 }2 y- `3 Z0 F8 Z$ H) x9 J1 [8 ychangedTouches 属性比较特殊,在不同的事件中其有不同的含义。对于 touchstart 事件,其中存储的是激活的触摸点。对于 touchmove 事件,其中存储的是从上个事件发生后移动的点(即从上个事件以来接触点的轨迹)。2 g! v1 W$ e; _8 [8 I$ T
TocuhList 和其元素 Touch 的数据结构见清单 2。
( d* G( M! L* v: B0 H5 Y, B9 S7 \! e
清单 2 TouchList 接口和 Touch 接口) ~/ Q+ [# a) s9 e
interface TouchList {
8 |6 {4 c# \* R5 i, i- ~# X2 ~    readonly attribute unsigned long length;
: _7 k7 A# P/ f9 k7 I    getter Touch item (unsigned long index);
8 G- }+ ~) i6 t6 e* G    Touch        identifiedTouch (long identifier);
! M) H; P; @' b# f/ J( M };
! }  C/ m- @6 n& ?. K% X$ H9 C6 U% b; O9 u3 V( ?
interface Touch { 6 }. A6 [8 F2 J! v! O7 d0 a
    readonly attribute long        identifier; 9 S: Y, p$ C$ v, R" q1 g$ j) ~/ Z
    readonly attribute EventTarget target;
  z: u- T8 T4 T0 f9 c1 k. K+ v    readonly attribute long        screenX;
5 U: j0 |1 x% r    readonly attribute long        screenY;
2 Q* O9 v' z( ^1 Q& {    readonly attribute long        clientX;
8 ~) Y  H# l+ d( |0 @$ G4 k    readonly attribute long        clientY;
. Z6 `2 y( q9 D) [: m9 Z( h    readonly attribute long        pageX; + f5 W* \' r5 ]! X) _
    readonly attribute long        pageY;
$ S5 [: h9 M# Y( \7 f1 C2 q, P    readonly attribute long        radiusX; 7 d' ]1 S. X9 J, y& S' l
    readonly attribute long        radiusY;
* ~, A8 Q% I) y. G    readonly attribute float       rotationAngle; 6 J- `2 P% r' c1 a5 g' n1 @2 @
    readonly attribute float       force; 6 W8 ]* g9 n1 Q: `
};
, c( O9 ^; x  f( A& v/ i
从清单 2 可以看出,我们可以从 TouchList 结构得到接触点的个数和每个点的坐标以及大小信息。对于不支持多点触摸的浏览器来说,touches 和 targetTouches 中永远只有一个元素。/ e# ?& {% F; Y1 \
对于支持鼠标事件(例如 click 事件)的 DOM 元素,触摸动作不单会触发 Touch 事件还可能触发鼠标事件。具体的触发顺序是先触发 Touch 事件,当手指离开触摸屏幕并发生 touchend 事件之后触发鼠标事件。如果 touchstart 事件和 touchend 事件中间间隔的时间较长或者在 Touch 事件的处理函数中调用了 preventDefault 函数,则鼠标事件不会被触发。
. Z! C2 F: H3 k# {* X9 I& j% g为了屏蔽浏览器对 Touch 事件实现上的差异,Dojo 在包 dojo.touch 中对 Touch 事件做了封装。Dojo 在 dojo.touch 包下定义了 press、move、release 和 cancel 四个事件,并将 touches 中的第一个元素移除,该元素的所有属性都被复制到事件对象上。
' f7 \+ o& a: v0 J. ~
4 a7 {" f( ]- Y4 Q) {

+ E! Q' K7 r8 y& ODojo Gesture 分析
0 m4 @. X5 L. u% _% \) G1 Z* I- lDojo 在包 dojox.gesture 中提供了对手势功能的支持。在本节中,我从对 dojox.gesture 的使用着手为大家分析 Dojo 如何实现的手势功能。
1 O2 j6 U; E! A5 F8 u在 Dojo 源代码 dojox.gesture.test 包中有一个测试页面 test_gesture.html。清单 3 是其部分代码。; l0 y4 N2 }2 y4 g/ v1 p

+ w* p0 P; w0 Y9 q0 |" i* _清单 3.test_gesture.html dojo.ready 部分代码( A( |; G8 }4 p0 h$ c, G( R+ x
var inner = html.byId("inner");
  g: N7 g$ m5 B$ q# {0 G- D0 o2 J on(inner, tap, action);
4 {9 K  b6 C% L on(inner, tap.hold, action);
1 W# ]( s- P/ q* e on(inner, tap.doubletap, action); 0 ?- ^: J+ U& b; v
on(inner, swipe, swipeAction);

' u* `# X! I8 O9 i; W+ ]8 \4 `4 i. `在这段代码中:inner 是 HTML 中的一个 DIV 元素。tap 和 swip 是 Dojo 提供的两种手势对象。我们都知道,on 函数是 Dojo 1.7 中新添加的一种事件机制,其作用是把发生在 DOM 节点上的事件和对应的事件处理程序关联起来。$ F! w- O3 A" i. o" |4 t. j+ @6 @% W
从这段代码可以看出,Dojo 对手势的实现思路是先监测用户的手势动作,如果是符合定义的手势则触发自定义的手势事件。' B) W2 u8 S, v6 p
接下来,我们以 dojox.gesture.tap 对象为例来研究下如何实现一个手势。
* R& r' c# v9 X( q: }& i. Gdojox.gesture.tap 对象的类是从 dojox.gesture.Base 继承而来。我们比较一下两个类的代码可以发现,dojox.gesture.tap 对父类的修改有以下几个方面。
8 M! S5 A$ O8 H& n& h
  • 设置属性 defaultEvent 的值为 tap
  • 设置属性 subEvents 的值为数组 ["hold","doubletap"]. 可以猜测,清单 3 代码中的 tap.hold 和 tap.doubletap 应该和该属性有关。
  • 重写了父类的 press 和 release 方法。% V8 x* q' w* S3 U% h
让我们先来研究下 press 和 release 这两个方法。这两个方法在 dojox.gesture.Base 类中只有声明,和它们一起的还有 move 和 cancel 两个方法。根据 Base 类的注释,这四个方法代表了手势的四个阶段。Tap 的字面意思是拍打、轻击。根据字面意思,tap 手势只有 press 和 release 两个阶段,因此 dojox.gesture.tap 中只实现了 press 和 release 方法。
( N. c0 C+ P% X2 j- u- V9 G9 ~: ^! _
; {  C* s# s! j( v: ?, x* W清单 4.dojox.gesture.tap 部分代码
) m. r+ b& S6 o5 k
press: function(/*Object*/data, /*Event*/e){
% f. u, B8 \5 f* C# M* w- @' g// summary:
* ~6 u  D; R" n( j  [2 n' S//记录tap手势的初始信息并初始化一个Timeout句柄用来检测hold手势
/ N# n& j; u# x7 o7 o  M* i                if(e.touches && e.touches.length >= 2){
! ^+ ?! O% h# C/ t% _                        //tap 手势仅支持单点触摸
& I7 m8 N, u# g9 E( t6 b1 @" `% M9 ?                        delete data.context;# e3 k4 E" L( e. J6 r
                        return;2 r" J) j- ?7 r# H1 ]/ F/ |
                }
1 b. O' I  e1 K) ]) j                var target = e.target;
% d5 j% h4 `; ^; U3 A9 p( A0 l                this._initTap(data, e);2 u( o7 p0 V0 D: n% V  @. V2 i
                data.tapTimeOut = setTimeout(lang.hitch(this, function(){
5 F+ b' F$ d' t9 c3 Q7 L0 U                        if(this._isTap(data, e)){
& {* X1 x0 m* ~" I  N                                this.fire(target, {type: "tap.hold"});! b* `0 S- J  w( P4 I1 J7 ^
                        }
1 D- d4 ^8 s1 P$ p6 C                        delete data.context;
. l( l  {1 ~; v+ G                }), this.holdThreshold);' A" |" U8 @+ M1 n( V& ]# R
        },
+ W8 Q9 B# V% d0 k* k. C' `        release: function(/*Object*/data, /*Event*/e){
0 v4 h, ^; |1 W: }7 g# g. z$ W. l, D2 H! l                // summary:
# g/ J/ p* c% o! S" d7 ]( ]                //在touchend事件发生时检测并触发tap或double tap手势
$ J1 X' Q, e. f' g                if(!data.context){, U) F! Q  h+ U( m* r
                        clearTimeout(data.tapTimeOut);9 R; Y  Z" v, f- O
                        return;
. k" w& @4 O: E                }, ?5 \! K, r0 J4 N6 d
                if(this._isTap(data, e)){
9 n! d8 V& @1 F7 ?* R6 Z                        switch(data.context.c){
- J4 o! {3 o6 Z, ^                        case 1:
- r! ?7 |, E7 n' |                                this.fire(e.target, {type: "tap"});2 I! R/ q' e/ d# [$ b4 O! V
                                break;
. h5 v# h1 G1 _. Q0 \                        case 2:9 c+ `# ]/ g! @; C( u  y: v- e
                                this.fire(e.target, {type: "tap.doubletap"});# }  q. j  V( E' c
                                break;
* a( w; J' O% t$ ^                        }
% i1 n7 N( \% m3 Y2 O: l                }( o+ {! q0 N3 D. F* _  `6 B
                clearTimeout(data.tapTimeOut);
) s* z; s& g# z. G* x2 P        },
$ `, s/ _& c! `, ~        _initTap: function(/*Object*/data, /*Event*/e){( x% \2 J% Y. I9 q; b  ]
                // summary:
7 w6 c/ @. \7 h% B. f                //        更新tap手势的数据
1 d: e9 m% j/ z/ K) ]                if(!data.context){- T# k9 m) L* F$ [/ h
                        data.context = {x: 0, y: 0, t: 0, c: 0};( Z: i3 Y: q+ h9 l& m! T9 Y
                }
( `5 Y% ^3 p% X$ ~5 {# X                var ct = new Date().getTime();9 w/ H6 i9 ]" B: ]* F
                if(ct - data.context.t <= this.doubleTapTimeout){
2 O7 ?# h& ?: I: t6 R+ _9 q                        data.context.c++;& l) p/ V# k9 c. x8 r# c6 l5 R
                }else{
. K( U6 F. E. V                        data.context.c = 1;3 P$ |4 Y# g; {4 g
                        data.context.x = e.screenX;) e7 S* G+ [0 Z8 p6 `
                        data.context.y = e.screenY;
. C! o) @+ K4 H- p3 A& K/ L                }
/ c: ]" p" J1 p" q$ b                data.context.t = ct;
  \; ~* u1 C' a: F        },
6 m7 n9 @1 }9 b: D_isTap: function(/*Object*/data, /*Event*/e){* C' s/ l8 p5 Y2 L2 N6 O
                // summary:
( f7 @' k+ H5 x4 h0 i% t/ R                //                Check whether it's an valid tap
5 p% r% Z5 k2 X+ _6 R  L                var dx = Math.abs(data.context.x - e.screenX);( k4 V* T0 ~" D
                var dy = Math.abs(data.context.y - e.screenY);
3 @+ E; o( Z  s' E/ t2 o2 f4 ?! X" |                return dx <= this.tapRadius && dy <= this.tapRadius;
. l6 ?9 ~1 B$ c/ b  P+ y        }
. ?' m6 E: h5 F$ e- o+ t
我们发现,在 press 和 release 函数中,fire 函数被反复调用。函数 fire 是在 Base 类中定义的,其作用是利用 Dojo 的事件 API dojo.on 在指定 DOM 节点上触发特定事件。% d( i3 l5 E( I5 a) T) W8 Y- a
我们再来看看是怎样检测手势的。在清单 4 中有一个名为 _isTap 的函数。从字面意思来看,我们可以断定其是用来判断当前发生的手势是否是 Tap 手势。通过阅读代码,我们可以看到该函数在 release 函数中被调用,函数内部通过检测 release 事件发生时的坐标和 press 事件的坐标(在 press 函数中通过调用 _initTap 来保存)间的距离来判断是否为 Tap 手势。请注意这几个函数的参数列表中的 data 参数,它是用来保存判断手势需要的数据的。; z5 ^$ r, ^3 E0 A
到此,我们大致了解了怎么样利用 Dojo Gesture 库实现自定义的手势。但是,还有几个关键的问题我们没有搞清楚。
4 ]# j5 l9 y9 a9 q$ f
  • 我们发出的事件有什么特性?
  • 手势的四个阶段是怎样被触发的?
  • 我们实现的手势能够在非触摸设备上使用吗?
    ; s7 o) ~5 F* y/ q5 c
让我们继续来看 Dojo 自带的例子 test_gesture.html. 如果在 inner div 元素上轻敲作出 tap 手势的话,你会发现其父节点 outer div 元素也能接收到 tap 事件。这说明我们发出的 tap 事件是具有 bubble 性质的。5 q' q$ c' L, n' a6 f; v* P
要得到剩下的两个问题的答案,我们还需要继续研究 dojox.gesture._Base 的代码。请看下面的代码:
+ q( m/ j1 u4 L- `8 A# |3 H: C1 z7 V. E4 [
清单 5.dojox.gesture._Base 的 _add 方法
6 d2 F5 u1 q$ c( h+ O
_add: function(/*Dom*/node, /*String*/type, /*function*/listener){
3 L& c! G8 M% P9 J) K                         // summary: ; S) U! V& D% T, y  e2 p& s
                         //         将 touch 几个阶段的事件绑定到手势类的相应处理函数上7 S7 B6 \( N& s( |; i
             var element = this._getGestureElement(node);
( |' ~6 q' v. x$ w8 C% N2 E/ K             if(!element){
  b' c+ g' q7 ^  k                // the first time listening to the node
2 _* J* b5 Y" ?  G) j+ ~' n1 N                 element = { ) S, i8 X+ U5 n- z+ q
                     target: node,
+ j& g- i' V9 b: x4 @                     data: {}, 3 s4 u2 a1 o' u! ?" F2 w' Y
                     handles: {}
- o" F+ A" @% c4 x& Q9 Q2 F                                 };
0 t) O$ b/ T& {' f2 g$ T: o% x! z
3 s6 n3 C4 w* @3 o                 var _press = lang.hitch(this, "_process", element, "press");   u. R) i3 f( X  M: v
                 var _move = lang.hitch(this, "_process", element, "move");
# l+ o0 ?$ ]$ z: c, l/ a2 [$ [# ]                 var _release = lang.hitch(this, "_process", element, "release");
& _6 @" B% S0 g  j. T# O) I                 var _cancel = lang.hitch(this, "_process", element, "cancel");
' p; Q* c4 p; p; _6 y$ M+ |# K) j# j& [( Y' J- J0 S9 o* {% D' m( M6 @( T: j
                 var handles = element.handles; . ]; x$ D" A8 v
                 if(this.touchOnly){
0 n8 g5 \5 |# c/ M' ?" ~                     handles.press = on(node, 'touchstart', _press); " R* b- r6 s4 k! ?5 f  d* S
                     handles.move = on(node, 'touchmove', _move);
+ T3 y! Q5 }3 s# B9 }& I' r$ y                     handles.release = on(node, 'touchend', _release); ) ]5 N% p3 p( s
                     handles.cancel = on(node, 'touchcancel', _cancel);
  I5 b4 V* w+ i" N% ^1 S2 @9 C                 }else{
% b! M3 {4 ]' @9 i9 v                     handles.press = touch.press(node, _press); " E: M0 }3 L' s+ {$ P1 F
                     handles.move = touch.move(node, _move);
1 Q5 y- N4 G" B; Z1 J7 q' k. X3 I; I                     handles.release = touch.release(node, _release); - o$ Z$ v/ S6 x$ k
                     handles.cancel = touch.cancel(node, _cancel); + e1 F3 e5 B0 n' A! I
                                 } 1 Y( E& \) X% H: e% ^( {% t& E
                 this._elements.push(element); , L7 Y% ?+ t7 Z6 y
             }
; H$ ^  x$ I8 d5 k8 C: D             // track num of listeners for the gesture event - type 4 g, e: H" E& t. i. ~
            // so that we can release element if no more gestures being monitored
3 y) d% h5 N$ W0 A2 H+ o            element.handles[type] = !element.handles[type] ? 1 : ++element.handles[type];
) S- e6 u, `3 r4 ^+ z! o
$ D; G# u5 o, `( `5 x                         return on(node, type, listener); //handle ) f7 A9 v& w! `* f8 e2 n9 A" j
                 }
( I, J2 X6 L' A; H3 `: W7 V$ i% Z
从注释上来看,这个方法是为 dom node 添加手势事件的处理句柄。5 S0 ]# C4 k+ l/ a5 {9 D
当事件只支持触摸设备时,该函数将相应的 touch 事件映射到 _Base 类相应方法上。这些方法又会调用事件的 press, move, release 和 cancel 方法从而触发我们的手势事件。2 |. |7 ~, G7 x, i
当事件同时支持触摸和鼠标动作时,该方法通过 dojo.touch 来处理相应触摸 / 鼠标事件的监听。
* H! q0 ^+ D1 x: V
" Y2 ~: t6 R! r. F
) y' I7 s0 A8 y) h+ E/ X
总结
+ c' v' R* c/ `通过上面的论述,我们可以总结出实现自定义手势的步骤
: _, a) Q& r: B! U' F! J
  • 从 dojox.gesture._Base 继承
  • 实现 press, move, release 和 cancel 函数,监听并记录 touch/ 鼠标事件的数据。这些数据可以保存在这几个函数的 data 参数中。
  • 在合适的时机通过 fire 方法发出手势事件
    4 b" y& p( j/ _& ?$ n* i( {+ @

8 W" s) D! O% Z$ Z: c
 楼主| 发表于 2013-2-17 16:51:17 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|无图版|SCMLife.com ( 京ICP备06056490号-1 )

GMT+8, 2018-1-18 00:12 , Processed in 0.071507 second(s), 6 queries , Gzip On, MemCache On.

Powered by SCMLife X3.4 Licensed

© 2001-2017 JoyShare.

快速回复 返回顶部 返回列表