权当一个笔记,再写写或许更明白点
习惯了前端世界的交互模式之后(其实就是 DOM 事件),在这个入门的过程中感觉 React Native 的交互处理就是个不适应。
如果要做 React Native 的交互,首先至少要知道这样几个东西:
- Gesture Responder System E文 中文
- TouchableHighlight E文 中文
- TouchableOpacity E文 中文
- TouchableWithoutFeedback E文 中文
- PanResponder E文 中文
不过干读这几个文档的话,基本就是一头雾水……
还是一点一点来看罢:
普通行为
TouchableHighlight、TouchableOpacity、TouchableWithoutFeedback 这几个很好弄,官方贴心的直接封装了最基础的 Touch 行为,在任何需要点击的 View 外面直接包上这样的标签就行了。
TouchableHighlight 在点击时表现为高亮
TouchableOpacity 在点击时表现为透明
TouchableWithoutFeedback 在点击时无反馈
这几个效果都是封装好了的,无需开发者操心。
Sample Code
然而这些只适用于按钮系……
支持事件:
- onPress
- onPressIn
- onPressOut
- onLongPress
支持参数:
- delayLongPress {number} 单位ms
- delayPressIn {number} 单位ms
- delayPressOut {number} 单位ms
再特殊点的行为,例如划过,就不用想用这几个货直接实现了。
Gesture Responder System
中文翻译叫:手势应答系统。
主要就是搞手势识别处理的,那其实也就是复杂点的触摸:例如一边摸一遍动啊,摸着还动出花样画个 L 啥的的那种。
事件
决定是否成为处理器
冒泡的:
- onStartShouldSetResponder
touchStart
/mouseDown
行为发生,是否当前的元素成为处理器 - onMoveShouldSetResponder
touchMove
/mouseMove
行为发生,是否当前行为成为处理器
不冒泡/未来不冒泡的:
- onScrollShouldSetResponder 滚动行为发生了,是否当前的元素成为处理器
- onSelectionChangeShouldSetResponder 选择行为发生了,是否当前元素成为处理器
是否接管成为处理器(因为冒泡是从最深处开始,可以在父级的元素使用此类方法接管):
- onStartShouldSetResponderCapture
touchStart
/mouseDown
行为发生,是否当前的元素代替最深层的子元素成为处理器 - onMoveShouldSetResponderCapture
touchStart
/mouseDown
行为发生,是否当前的元素代替最深层的子元素成为处理器
开始处理了
- onResponderStart 当前处理开始
- onResponderGrant 现在正在响应触摸事件
- onResponderMove 用户正移动他们的手指
- onResponderEnd 当前处理结束
- onResponderRelease 在触摸最后被引发,即
touchUp
跟拦截相关的(当前应答器的身份)
- onResponderReject 当前视图的应答器不是“我”了,并且还不释放让我来当。
- onResponderTerminationRequest 其他的东西想成为应答器。应该释放应答吗?返回 true 就是允许释放
- onResponderTerminate 应答器已经转交给别人担当了。可能在调用onResponderTerminationRequest 之后被其他视图获取,也可能是被操作系统在没有请求的情况下获取了(发生在 iOS 的 control center/notification center)
以上都是在ResponderEventPlugin.js里面实现的,我们直接使用视图 View 配置
行为生命周期
这个图画的我头晕啊……
几个特性
冒泡
之前说到,有两个东西是冒泡的:
- onScrollShouldSetResponder
- onSelectionChangeShouldSetResponder
然则默认是触发最深的那个元素,也就是子级元素,如果父级要拦截作为处理器,则需要处理:
- onStartShouldSetResponderCapture
- onMoveShouldSetResponderCapture
这两个事件的触发顺序是从父级开始的,所以如果父级设置了返回 true,则会执行父级的处理。
但是如果任一返回了 false,则依然使用子级元素作为处理器。
不过如果父级的 onStartShouldSetResponder
如果返回 false,干脆不会触发父级的验证,onStartShouldSetResponderCapture
返回 true 也没用,Move 也是同理。
拦截
- onResponderReject 当前视图的应答器不是“我”了,并且还不释放让我来当。
- onResponderTerminationRequest 其他的东西想成为应答器。应该释放应答吗?返回 true 就是允许释放
- onResponderTerminate 应答器已经转交给别人担当了。可能在调用onResponderTerminationRequest 之后被其他视图获取,也可能是被操作系统在没有请求的情况下获取了(发生在 iOS 的 control center/notification center)
话说这个还没搞明白怎么用……
简单用法
直接写属性,作为 prop:
也可以使用…运算符:
也可以使用 PanResponder
(这个会在实际处理的事件前加个 Pan,输出时又会去掉,而且会增加一个参数 gestureState
):
|
|
特殊行为
划走切换的效果
可以参考:http://www.terlici.com/2015/04/06/simle-slide-menu-react-native.html
就是使用 PanResponder + Animation做的。
这个回头我自己再搞个出来。
手势解锁
因为不想用 WebView 做,所以这里都是从纯 React Native 的角度去考虑的。
因为生命周期中,TouchIn 是起点,所以如果在外面按住了划过元素,元素是不会有反应的……
单纯的子级接管作为处理器然后释放也是没用的,如果同时设置 Capture,父级的优先级大……
那么是否可以这样呢?父级判断碰撞,然后释放处理权?但是拦截的判断只在最开始触发的时候能搞,所以似乎还是行不通的。而且都碰撞到了,如果能直接处理子元素不是更简便么?
没那么简单,需要看看这三个方法:
子元素的获取
使用 refs:
这样就可以直接通过 this.refs[name]
获取到子元素了。
获取子元素的位置
这是从 React Native 的 Issue 1374 拿到的方法:
所以,元素在屏幕中的范围是:pageX ~ pageX + width, pageY ~ pageY + height
至少是个简单的正方形,如果是其他形状例如圆形,可能还需要计算圆心和半径的大小。
获取当前 touch 的坐标
之前说过 PanResponder 会给事件方法增加一个参数 gestureState
:
一个 gestureState 对象有以下属性:
- stateID:gestureState 的ID-在屏幕上保持至少一个触发动作的时间
- moveX:最近动态触发的最新的屏幕坐标
- x0:应答器横向的屏幕坐标
- y0:应答器纵向的屏幕坐标
- dx:触发开始后累积的横向动作距离
- dy:触发开始后累积的纵向动作距离
- vx:当前手势的横向速度
- vy:当前手势的纵向速度
- numberActiveTouch:屏幕上当前触发的数量
那么 touch 位置的坐标可以这么获得:[x0 + dx, y0 + dy]
当然也可以使用都有的 evt 参数:
changedTouches - Array of all touch events that have changed since the last event
identifier - The ID of the touch
locationX - The X position of the touch, relative to the element
locationY - The Y position of the touch, relative to the element
pageX - The X position of the touch, relative to the screen
pageY - The Y position of the touch, relative to the screen
target - The node id of the element receiving the touch event
timestamp - A time identifier for the touch, useful for velocity calculation
touches - Array of all current touches on the screen
直接用[pageX, pageY] 就行了。
这样就可以进行简单的碰撞计算了,计算位置是否在某个子元素的范围内就行了。
实际上至此手势解锁的几个关键问题已经解决,正在写一个手势解锁的组件:k-react-native-swipe-unlock 玩耍。
To Be Continued.