响应者的生命周期
生命周期方法列表
View.props.onStartShouldSetResponderCapture: (evt) => true
View.props.onMoveShouldSetResponderCapture: (evt) => true
View.props.onStartShouldSetResponder: (evt) => true
View.props.onMoveShouldSetResponder: (evt) => true
View.props.onResponderGrant: (evt) => {}
View.props.onResponderReject: (evt) => {}
View.props.onResponderMove: (evt) => {}
View.props.onResponderRelease: (evt) => {}
View.props.onResponderTerminationRequest: (evt) => true
View.props.onResponderTerminate: (evt) => {}
evt是一个合成事件,它包含以下结构:
- nativeEvent
- changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
- identifier - 触摸点的 ID
- locationX - 触摸点相对于当前元素的横坐标
- locationY - 触摸点相对于当前元素的纵坐标
- pageX - 触摸点相对于根元素的横坐标
- pageY - 触摸点相对于根元素的纵坐标
- target - 触摸点所在的元素 ID
- timestamp - 触摸事件的时间戳,可用于移动速度的计算
- touches - 当前屏幕上的所有触摸点的集合
生命周期方法详解
捕获 ShouldSet 事件处理
View.props.onStartShouldSetResponderCapture: (evt) => true
View.props.onMoveShouldSetResponderCapture: (evt) => true
“是否愿意成为响应者”系列方法:onStartShouldSetResponder与onMoveShouldSetResponder是以冒泡的形式调用的,即嵌套最深的节点最先调用。这意味着当多个 View 同时在*ShouldSetResponder中返回 true 时,最底层的 View 将优先“夺权”。在多数情况下这并没有什么问题,因为这样可以确保所有控件和按钮是可用的。
但是有些时候,某个父 View 会希望能先成为响应者。我们可以利用“捕获期”来解决这一需求。响应系统在从最底层的组件开始冒泡之前,会首先执行一个“捕获期”,在此期间会触发on*ShouldSetResponderCapture系列事件。因此,如果某个父 View 想要在触摸操作开始时阻止子组件成为响应者,那就应该处理onStartShouldSetResponderCapture事件并返回 true 值。
是否愿意成为响应者
-
View.props.onStartShouldSetResponder: (evt) => true
在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者? -
View.props.onMoveShouldSetResponder: (evt) => true
如果 View 不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?
尝试成为响应者
-
View.props.onResponderGrant: (evt) => {}
View 现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里 -
View.props.onResponderReject: (evt) => {}
响应者现在“另有其人”而且暂时不会“放权”,请另作安排。
开始响应触摸事件
-
View.props.onResponderMove: (evt) => {}
用户正在屏幕上移动手指时(没有停下也没有离开屏幕)。 -
View.props.onResponderRelease: (evt) => {}
触摸操作结束时触发,比如"touchUp"(手指抬起离开屏幕)。 -
View.props.onResponderTerminationRequest: (evt) => true
有其他组件请求接替响应者,当前的 View 是否“放权”?返回 true 的话则释放响应者权力。 -
View.props.onResponderTerminate: (evt) => {}
响应者权力已经交出。这可能是由于其他 View 通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如 iOS 上的控制中心或是通知中心)。
封装手势
可以认为是对手势响应生命周期方法的模板封装,与gesture responder system 比起来,封装手势
方法的抽象程度更高,使用起来也更加方便
PanResponder
响应事件
事件方法列表
- onMoveShouldSetPanResponder: (evt, gestureState) => {...}
- onMoveShouldSetPanResponderCapture: (evt, gestureState) => {...}
- onStartShouldSetPanResponder: (evt, gestureState) => {...}
- onStartShouldSetPanResponderCapture: (evt, gestureState) => {...}
- onPanResponderReject: (evt, gestureState) => {...}
- onPanResponderGrant: (evt, gestureState) => {...}
- onPanResponderStart: (evt, gestureState) => {...}
- onPanResponderEnd: (evt, gestureState) => {...}
- onPanResponderRelease: (evt, gestureState) => {...} //用户手指离开屏幕时,调用该方法
- onPanResponderMove: (evt, gestureState) => {...} //用户滑动手指时,调用该方法
- onPanResponderTerminate: (evt, gestureState) => {...}
- onPanResponderTerminationRequest: (evt, gestureState) => {...}
- onShouldBlockNativeResponder: (evt, gestureState) => {...}
说明:
-
相比gesture responder syste的响应事件方法多了一个gestureState对象参数。
例如:
onPanResponderMove: (event, gestureState) => {}
-
一个gestureState对象有如下的字段:
- stateID - 触摸状态的 ID。在屏幕上有至少一个触摸点的情况下,这个 ID 会一直有效。
- moveX - 最近一次移动时的屏幕横坐标
- moveY - 最近一次移动时的屏幕纵坐标
- x0 - 当响应器产生时的屏幕坐标
- y0 - 当响应器产生时的屏幕坐标
- dx - 从触摸操作开始时的累计横向路程
- dy - 从触摸操作开始时的累计纵向路程
- vx - 当前的横向移动速度
- vy - 当前的纵向移动速度
- numberActiveTouches - 当前在屏幕上的有效触摸点的数量
-
两个参数的作用
- evt
- 获取触摸的位置在被响应的 View 中的相对坐标
- evt.nativeEvent.locationX 和 evt.nativeEvent.locationY(这个方法很实用)
- gestureState
- dx/dy:手势进行到现在的横向/纵向相对位移
- vx/vy:此刻的横向/纵向速度
- numberActiveTouches:responder上的触摸的个数
- evt
基本用法
componentWillMount: function() {
this._panResponder = PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
// gestureState.{x,y} 现在会被设置为0
},
onPanResponderMove: (evt, gestureState) => {
// 最近一次的移动距离为gestureState.move{X,Y}
// 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
},
onPanResponderTerminate: (evt, gestureState) => {
// 另一个组件已经成为了新的响应者,所以当前手势将被取消。
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
// 默认返回true。目前暂时只支持android。
return true;
},
});
},
render: function() {
return (
<View {...this._panResponder.panHandlers} />
);
},
示例:
简单的拖拽示例
import {
View,
StyleSheet,
PanResponder
} from 'react-native';
class QQAndGameHome extends PureComponent {
static contextTypes = {
router: PropTypes.object,
store: PropTypes.object
}
constructor(props) {
super(props);
this.state = {
top: JDDevice.getRpx(100),
left: JDDevice.getRpx(100),
bg: 'gray',
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: () => {
this._top = this.state.top;
this._left = this.state.left;
this.setState({bg: 'red'});
},
onPanResponderMove: (evt, gs) => {
console.log(gs.dx+' '+gs.dy);
this.setState({
top: this._top+gs.dy,
left: this._left+gs.dx
});
},
onPanResponderRelease: (evt, gs)=>{
this.setState({
bg: 'gray',
top: this._top+gs.dy,
left: this._left+gs.dx
})
}
});
}
render() {
return(
<View
{...this.panResponder.panHandlers}
style={[myStyles.rect,{
"backgroundColor": this.state.bg,
"top": this.state.top,
"left": this.state.left
}]}
>
...
</View>
)
}
}
const myStyles = StyleSheet.create({
rect: {
position: 'absolute',
width: JDDevice.getRpx(100),
height: JDDevice.getRpx(100),
borderColor: 'black'
}
});
TouchableHighlight 与 Touchable 系列组件
响应系统用起来可能比较复杂。所以我们提供了一个抽象的Touchable实现,用来做“可触控”的组件。
TouchableHighlight
- 本组件用于封装视图,使其可以正确响应触摸操作
- 按下时自带透明度降低,蒙层颜色变化反馈等效果
- 底层实现上,实际会创建一个新的视图到视图层级中
-
TouchableHighlight
只支持一个子节点,(不能没有子节点也不能多于一个),如果你希望包含多个子组件,可以用一个View来包装它们 -
hitSlop
:这一属性定义了按钮的外延范围。这一范围也会使pressRetentionOffset变得更大。 注意: 触摸范围不会超过父视图的边界,也不会影响原先和本组件层叠的视图(保留原先的触摸优先级)。 -
onLayout
:当加载或者布局改变的时候被调用,参数为:{nativeEvent: {layout: {x, y, width, height}}}
-
onLongPress
、onPress
TouchableOpacity
- 本组件用于封装视图,使其可以正确响应触摸操作
- 按下时自带透明度降低效果
- 不透明度的变化是通过把子元素封装在一个
Animated.View
中来实现的,这个动画视图会被添加到视图层级中,少数情况下有可能会影响到布局。 - 此组件与
TouchableHighlight
的区别在于并没有额外的颜色变化,更适于一般场景
TouchableWithoutFeedback
- 同
TouchableHighlight
、TouchableOpacity
的区别是按下时没有任何视觉上的反馈。
TouchableNativeFeedback
- 本组件用于封装视图,使其可以正确响应触摸操作
- 仅限Android平台
- 在Android设备上,这个组件利用原生状态来渲染触摸的反馈。