Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2015-08-12] React Native 浅入门 —— 交互篇 #2

Open
leowang721 opened this issue Aug 17, 2017 · 0 comments
Open

[2015-08-12] React Native 浅入门 —— 交互篇 #2

leowang721 opened this issue Aug 17, 2017 · 0 comments

Comments

@leowang721
Copy link
Owner

leowang721 commented Aug 17, 2017

权当一个笔记,再写写或许更明白点

习惯了前端世界的交互模式之后(其实就是 DOM 事件),在这个入门的过程中感觉 React Native 的交互处理就是个不适应。

如果要做 React Native 的交互,首先至少要知道这样几个东西:

不过干读这几个文档的话,基本就是一头雾水……

还是一点一点来看罢:

普通行为

TouchableHighlight、TouchableOpacity、TouchableWithoutFeedback 这几个很好弄,官方贴心的直接封装了最基础的 Touch 行为,在任何需要点击的 View 外面直接包上这样的标签就行了。

TouchableHighlight 在点击时表现为高亮
TouchableOpacity 在点击时表现为透明
TouchableWithoutFeedback 在点击时无反馈

这几个效果都是封装好了的,无需开发者操心。

Sample Code

<TouchableOpacity
    onPressIn={this._onPressInCircle.bind(this)}
    onPressOut={this._onPressOutCircle.bind(this)}>
    <View style={styles.gridItem}></View>
</TouchableOpacity>

然而这些只适用于按钮系……

支持事件:

  • 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:

<View onResponderStart={(evt) => true} />

也可以使用...运算符:

class GridItem extends Component {
    get touchProps() {
        return {
            onStartShouldSetResponder: (evt) => true,
            onResponderGrant: (evt) => {
                console.log('child');
                console.log(evt);
            },
            onResponderTerminationRequest: (evt) => true
        };
    }
    render() {
        return (
            <View {...this.touchProps}></View>
        )
    }
}

也可以使用 PanResponder(这个会在实际处理的事件前加个 Pan,输出时又会去掉,而且会增加一个参数 gestureState):

class GridItem extends Component {
    componentWillMount() {
        this._panGesture = PanResponder.create({
            onStartShouldSetResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {
                console.log('child');
                console.log(evt);
                console.log(gestureState);
            },
            onPanResponderTerminationRequest: (evt, gestureState) => true
        });
    }
    render() {
        return (
            <View {...this._panResponder.panHandlers}></View>
        )
    }
}

特殊行为

划走切换的效果

可以参考:http://www.terlici.com/2015/04/06/simle-slide-menu-react-native.html
就是使用 PanResponder + Animation做的。

这个回头我自己再搞个出来。

手势解锁

因为不想用 WebView 做,所以这里都是从纯 React Native 的角度去考虑的。

因为生命周期中,TouchIn 是起点,所以如果在外面按住了划过元素,元素是不会有反应的……

单纯的子级接管作为处理器然后释放也是没用的,如果同时设置 Capture,父级的优先级大……

那么是否可以这样呢?父级判断碰撞,然后释放处理权?但是拦截的判断只在最开始触发的时候能搞,所以似乎还是行不通的。而且都碰撞到了,如果能直接处理子元素不是更简便么?

没那么简单,需要看看这三个方法:

子元素的获取

使用 refs:

<View style={styles.gridView} {...this._panResponder.panHandlers} >
    <View style={styles.gridLine}>
        <GridItem ref="item1" />
        <GridItem ref="item2" />
        <GridItem ref="item3" />
    </View>
    <View style={styles.gridLine}>
        <GridItem ref="item4" />
        <GridItem ref="item5" />
        <GridItem ref="item6" />
    </View>
    <View style={styles.gridLine}>
        <GridItem ref="item7" />
        <GridItem ref="item8" />
        <GridItem ref="item9" />
    </View>
</View>

这样就可以直接通过 this.refs[name] 获取到子元素了。

获取子元素的位置

这是从 React Native 的 Issue 1374 拿到的方法:

var RCTUIManager = require('NativeModules').UIManager;
var view = this.refs[name];
var handle = React.findNodeHandle(view);
RCTUIManager.measure(handle, (x, y, width, height, pageX, pageY) => {
    // x,y 似乎是当前container的坐标
    // width, height 是宽高
    // pageX, pageY 是在屏幕中的坐标(起始坐标)
})

所以,元素在屏幕中的范围是: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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant