React-Native带你一步一步实现侧滑删除

前言:好久没有写博客了,回想起刚开始写博客的时候对自己的要求,“每周至少一篇!!!“(还有当初说减肥跟写博客同步进行,结果越减越肥。),嗯嗯,说多了都是泪,最近在一直在学习h5,然后看到现在rn项目中有小伙伴在用一个第三方的侧滑删除控件,于是想去看看那些大神是咋实现的,最后发现,也就这样哈~没想象中的那么难,写这篇博客的目的也就是当作一个学习笔记,大牛勿喷!!2017对我来说是不平凡的一年,期间换了几次工作,但最后还是找到了自己的归宿,所以对2017还是比较满意的,感恩!!2018迎来了又一个本命年,回头想想自己,其实也不小了,但心里却总是对自己说:“我还是一个小鲜肉!“,唉唉!感叹时光的流逝,身边的亲人一个一个离去,有些时候一个人发呆的时候总问自己“你到底想做什么?你能做什么?“我却被自己问的哑口无言,骚年!现实点吧~ 也请对自己和身边的人好一点,坚持自己的初衷,永远不要做思想的巨人行动的矮子,嗯嗯!说了那么多废话,想必大家也觉得我无聊,管你们无不无聊!我开心就行,哈哈哈哈~~~

最后实现的效果呢也很简单,大概是这样的:

这里写图片描述

先说一下我们的大体思路,很简单!底部绝对定位放一个默认的(含有“删除“按钮)的view,然后上面盖住一个默认的需要渲染的view,给整个item一个滑动监听,然后慢慢的漏出底部view,嗯嗯!! 原理真的很简单,下面我们一步一步的撸我们的代码哈~

首先新建一个很干净的项目叫SwipeDemo然后跑起来:

这里写图片描述

照着我们的思路简单实现一下,代码很简单,我直接贴出来了:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, {Component} from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    TouchableOpacity
} from 'react-native';

export default class App extends Component {
    render() {
        return (
            <View style={styles.container}>
                <View style={styles.swipeContainer}>
                    {/*绝对在底部的view*/}
                    <View style={styles.swipeActions}>
                        <TouchableOpacity
                            style={styles.delTextContainer}
                            onPress={()=>{
                                alert('ss');
                            }}
                        >
                            <Text
                                style={styles.deleteTextStyle}
                            >删除</Text>
                        </TouchableOpacity>
                    </View>
                    {/*内容content*/}
                    <View style={styles.content}>
                        <Text>我是item的内容</Text>
                    </View>
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    swipeContainer: {
        width: '100%',
        marginTop: 100,
        height:100,
    },
    swipeActions:{
        backgroundColor: 'grey',
        width: '100%',
        overflow:'hidden',
        ...StyleSheet.absoluteFillObject,
        flexDirection:'row',
        justifyContent:'flex-end'
    },
    delTextContainer:{
        width:100,
        backgroundColor:'red',
        alignItems:'center',
        justifyContent:'center'
    },
    deleteTextStyle:{
        color:'#fff',
    },
    content:{
        width: '100%',
        flex:1,
        backgroundColor:'yellow',
        justifyContent:'center',
        alignItems:'center',
    }
});

运行代码:

这里写图片描述

然后我们给内容content一个向左的偏移量:

 render() {
        return (
            <View style={styles.container}>
                <View style={styles.swipeContainer}>
                    {/*绝对在底部的view*/}
                    <View style={styles.swipeActions}>
                        <TouchableOpacity
                            style={styles.delTextContainer}
                            onPress={()=>{
                                alert('ss');
                            }}
                        >
                            <Text
                                style={styles.deleteTextStyle}
                            >删除</Text>
                        </TouchableOpacity>
                    </View>
                    {/*内容content*/}
                    <View style={[styles.content,{transform:[{translateX:-10}]}]}>
                        <Text>我是item的内容</Text>
                    </View>
                </View>
            </View>
        );

我们向左偏移了10:
这里写图片描述

可以看到我们绝对在底部的view漏出了一点点~

我们再向左偏移左偏移了100:

这里写图片描述
可以看到我们绝对在底部的view完全漏出来了~

所以看到这里小伙伴是不是有点明白了呢??我们只需要给一个手势在move的时候动态的设置左偏移量,然后手指松开的时候做一些逻辑判断,最后给一个动画,就可以简单的实现我们的侧滑删除功能了。

在写组件之前建议大家了解一下rn的手势,也就是PanResponder组件,大家可以参考下官网及这篇文章:

React Native 触摸事件处理详解
PanResponder

好啦~ 我们先创建一个view叫SwipeRow.js:

/**
 * @author YASIN
 * @version [React-Native Ocj V01, 2018/3/13]
 * @date 17/2/23
 * @description SwipeRow
 */
import React, {
    Component,
} from 'react';
import PropTypes from 'prop-types';
import {
    Animated,
    PanResponder,
    Platform,
    StyleSheet,
    TouchableOpacity,
    ViewPropTypes,
    View,
    Text
} from 'react-native';

export default class SwipeRow extends Component {
    render() {
        return (
            <View style={[styles.swipeContainer, this.props.style]}>
                <View style={styles.swipeActions}>
                    {this.props.children[0]}
                </View>
                {this.props.children[1]}
            </View>
        );
    }
}
const styles = StyleSheet.create({
    swipeContainer: {
        width: '100%',
    },
    swipeActions: {
        backgroundColor: 'grey',
        width: '100%',
        overflow: 'hidden',
        ...StyleSheet.absoluteFillObject,
        flexDirection: 'row',
        justifyContent: 'flex-end'
    },
});

然后App.js代码改为:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, {Component} from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    TouchableOpacity
} from 'react-native';
import SwipeRow from './SwipeRow';

export default class App extends Component {
    render() {
        return (
            <View style={styles.container}>
                <SwipeRow style={{marginTop: 100}}>
                    {/*绝对在底部的view*/}
                    <TouchableOpacity
                        style={styles.delTextContainer}
                        onPress={() => {
                            alert('ss');
                        }}
                    >
                        <Text
                            style={styles.deleteTextStyle}
                        >删除</Text>
                    </TouchableOpacity>
                    {/*内容content*/}
                    <View style={[styles.content, {transform: [{translateX: -100}]}]}>
                        <Text>我是item的内容</Text>
                    </View>
                </SwipeRow>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
        width: '100%',
    },
    delTextContainer: {
        width: 100,
        backgroundColor: 'red',
        alignItems: 'center',
        justifyContent: 'center'
    },
    deleteTextStyle: {
        color: '#fff',
    },
    content: {
        width: '100%',
        height: 100,
        backgroundColor: 'yellow',
        justifyContent: 'center',
        alignItems: 'center',
    }
});

运行代码:
这里写图片描述

可以发现我们得到的是一样的效果,我们只是简单的封装到了一个叫SwipeRow的组件中了~

接着我们封装一个手势的处理类:

/**
 * @author YASIN
 * @version [React-Native Ocj V01, 2018/3/13]
 * @date 17/2/23
 * @description SwipeRow
 */
import React, {
    Component,
} from 'react';
import PropTypes from 'prop-types';
import {
    Animated,
    PanResponder,
    Platform,
    StyleSheet,
    TouchableOpacity,
    ViewPropTypes,
    View,
    Text
} from 'react-native';

export default class SwipeRow extends Component {
    // 构造
    constructor(props) {
        super(props);
        // 初始状态
        this._panResponder = PanResponder.create({
            onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
            onPanResponderGrant: this._handlePanResponderGrant,
            onPanResponderMove: this._handlePanResponderMove,
            onPanResponderRelease: this._handlePanResponderEnd,
            onPanResponderTerminate: this._handlePanResponderEnd,
            onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
        });
    }

    render() {
        return (
            <View style={[styles.swipeContainer, this.props.style]}>
                <View style={styles.swipeActions}>
                    {this.props.children[0]}
                </View>
                {this.renderRowContent()}
            </View>
        );
    }

    renderRowContent() {
        return (
            <Animated.View
                {...this._panResponder.panHandlers}
            >
                {this.props.children[1]}
            </Animated.View>
        );
    }

    /**
     * 是否需要成为move事件响应者,返回true直接走onPanResponderMove
     * @param event
     * @param gestureState
     * @returns {boolean}
     * @private
     */
    _handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
        //当垂直滑动的距离<10 水平滑动的距离>10的时候才让捕获事件
        console.log('_handleMoveShouldSetPanResponderCapture');
        return gestureState.dy < 10 && Math.abs(gestureState.dx) > 10;
    }

    /**
     * 表示申请成功,组件成为了事件处理响应者
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderGrant(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderGrant');
    }

    /**
     * 处理滑动事件
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderMove(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderMove');
    }

    /**
     * 结束事件的时候回调
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderEnd(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderEnd');
    }
}
const styles = StyleSheet.create({
    swipeContainer: {
        width: '100%',
    },
    swipeActions: {
        backgroundColor: 'grey',
        width: '100%',
        overflow: 'hidden',
        ...StyleSheet.absoluteFillObject,
        flexDirection: 'row',
        justifyContent: 'flex-end'
    },
});

然后运行代码(手指从右向左拖动)看log:
这里写图片描述

好啦,我们在_handlePanResponderMove方法中处理下滑动事件:

 /**
     * 处理滑动事件
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderMove(event: Object, gestureState: Object): void {
        if (this._previousLeft === null) {
            this._previousLeft = this.state.currentLeft._value
        }
        let nowLeft = this._previousLeft + gestureState.dx / 0.5;
        //右滑最大距离为0(边界值)
        nowLeft = Math.min(nowLeft, 0);
        this.state.currentLeft.setValue(
            nowLeft,
        );
    }

然后去除App.js中的{transform: [{translateX: -100}]:

  {/*内容content*/}
                    <View style={[styles.content, {transform: [{translateX: -100}]}]}>
                        <Text>我是item的内容</Text>
                    </View>

SwipeRow组件中我们添加一个:

//上一次滑动最后的left偏移量
        this._previousLeft = 0;
        //left偏移动画
        this.state = {
            currentLeft: new Animated.Value(this._previousLeft),
        };

然后在move跟end的时候操作:

 /**
     * 处理滑动事件
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderMove(event: Object, gestureState: Object): void {
        if (this._previousLeft === null) {
            this._previousLeft = this.state.currentLeft._value
        }
        let nowLeft = this._previousLeft + gestureState.dx / 0.5;
        //右滑最大距离为0(边界值)
        nowLeft = Math.min(nowLeft, 0);
        this.state.currentLeft.setValue(
            nowLeft,
        );
    }

    /**
     * 结束事件的时候回调
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderEnd(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderEnd');
        this._previousLeft = null;
    }

最后付给动画组件:


    render() {
        return (
            <View style={[styles.swipeContainer, this.props.style]}>
                <View style={styles.swipeActions}>
                    {this.props.children[0]}
                </View>
                {this.renderRowContent()}
            </View>
        );
    }

    renderRowContent() {
        return (
            <Animated.View
                {...this._panResponder.panHandlers}
                style={{
                    transform: [
                        {translateX: this.state.currentLeft}
                    ]
                }}
            >
                {this.props.children[1]}
            </Animated.View>
        );
    }

全部代码:

/**
 * @author YASIN
 * @version [React-Native Ocj V01, 2018/3/13]
 * @date 17/2/23
 * @description SwipeRow
 */
import React, {
    Component,
} from 'react';
import PropTypes from 'prop-types';
import {
    Animated,
    PanResponder,
    Platform,
    StyleSheet,
    TouchableOpacity,
    ViewPropTypes,
    View,
    Text
} from 'react-native';

export default class SwipeRow extends Component {
    // 构造
    constructor(props) {
        super(props);
        // 初始状态
        this._panResponder = PanResponder.create({
            onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
            onPanResponderGrant: this._handlePanResponderGrant.bind(this),
            onPanResponderMove: this._handlePanResponderMove.bind(this),
            onPanResponderRelease: this._handlePanResponderEnd.bind(this),
            onPanResponderTerminate: this._handlePanResponderEnd.bind(this),
            onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
        });
        //上一次滑动最后的left偏移量
        this._previousLeft = 0;
        //left偏移动画
        this.state = {
            currentLeft: new Animated.Value(this._previousLeft),
        };
    }

    render() {
        return (
            <View style={[styles.swipeContainer, this.props.style]}>
                <View style={styles.swipeActions}>
                    {this.props.children[0]}
                </View>
                {this.renderRowContent()}
            </View>
        );
    }

    renderRowContent() {
        return (
            <Animated.View
                {...this._panResponder.panHandlers}
                style={{
                    transform: [
                        {translateX: this.state.currentLeft}
                    ]
                }}
            >
                {this.props.children[1]}
            </Animated.View>
        );
    }

    /**
     * 是否需要成为move事件响应者,返回true直接走onPanResponderMove
     * @param event
     * @param gestureState
     * @returns {boolean}
     * @private
     */
    _handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
        //当垂直滑动的距离<10 水平滑动的距离>10的时候才让捕获事件
        console.log('_handleMoveShouldSetPanResponderCapture');
        return Math.abs(gestureState.dy) < 10 && Math.abs(gestureState.dx) > 10;
    }

    /**
     * 表示申请成功,组件成为了事件处理响应者
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderGrant(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderGrant');
    }

    /**
     * 处理滑动事件
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderMove(event: Object, gestureState: Object): void {
        if (this._previousLeft === null) {
            this._previousLeft = this.state.currentLeft._value
        }
        let nowLeft = this._previousLeft + gestureState.dx / 0.5;
        //右滑最大距离为0(边界值)
        nowLeft = Math.min(nowLeft, 0);
        this.state.currentLeft.setValue(
            nowLeft,
        );
    }

    /**
     * 结束事件的时候回调
     * @param event
     * @param gestureState
     * @private
     */
    _handlePanResponderEnd(event: Object, gestureState: Object): void {
        console.log('_handlePanResponderEnd');
        this._previousLeft = null;
    }
}
const styles = StyleSheet.create({
    swipeContainer: {
        width: '100%',
    },
    swipeActions: {
        backgroundColor: 'grey',
        width: '100%',
        overflow: 'hidden',
        ...StyleSheet.absoluteFillObject,
        flexDirection: 'row',
        justifyContent: 'flex-end'
    },
});

最后运行效果:

这里写图片描述

我们只差最后一步了,就是在手指提起的时候让滑动部分反弹回去就可以了~ 先留给小伙伴自己实现吧,好吧~~感觉很简单的东西,写成文字还不是件容易的事情,吃饭去啦!!下节见咯~

猜你喜欢

转载自blog.csdn.net/vv_bug/article/details/79536156