React-Native 拖拽列表,滑动删除。

工作需要,实现了一个可以滑动删除的拖拽列表。

主要是参考的这个网站 https://blog.csdn.net/sinat_17775997/article/details/74668541?locationNum=10&fps=1 不过因为他没有实现滑动删除的功能,所以我在这里狗尾续貂一下。

先看一下功能

拖拽

拖拽

添加

添加

删除

删除

实现方法。

  1. 拖拽是在ScrollVIew 中对data进行map,返回View 让其每个的position的值都是absolute。然后计算每个View 的位置。通过PanResponder 对小图片添加拖动的方法,在点击,移动,和释放,是对最外层的View进行操作,修改其style中的top,从而让其移动。
    核心代码
    componentWillMount(){
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
      
            onPanResponderGrant: (evt, gestureState) => {
                this.setState(()=>{
                    return {
                        scrollflag:false,
                    }
                });
                const {pageY, locationY} = evt.nativeEvent;
                this.index = this._getIdByPosition(pageY);
                this.preY = pageY - locationY - EXPROTHEIGHT;
                let item = this.items[this.index];
                if(item == undefined) {
                    console.log(this.index);
                }
                this.start = this.index;
                this.end = this.index;
                item.setNativeProps({
                    style: {
                        shadowColor: "#333",
                        shadowOpacity: 0.3,
                        shadowRadius: 50,
                        shadowOffset: {height: 0, width: 2},
                        elevation: 5,
                        zIndex: 1,
                        backgroundColor:'gray',
                    }
                });
            },
            onPanResponderMove: (evt, gestureState) => {
                let top = this.preY + gestureState.dy+this.scrollOffsetY;
                let item = this.items[this.index];
                item.setNativeProps({
                    style: {top: top}
                });
        
                let collideIndex = this._getIdByPosition(evt.nativeEvent.pageY);
                if(collideIndex !== this.index && collideIndex !== -1) {
                    let collideItem = this.items[collideIndex];
                    collideItem.setNativeProps({
                        style: {top: this._getTopValueYById(this.index)}
                    });
                    [this.items[this.index], this.items[collideIndex]] = [this.items[collideIndex], this.items[this.index]];
                    [this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
                    this.index = collideIndex;
                    this.end = this.index;
                }
            },
            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onPanResponderRelease: this._releaseAndTerminate.bind(this),
            onPanResponderTerminate: this._releaseAndTerminate.bind(this)
          });
    }

   _releaseAndTerminate(evt, gestureState){
        const shadowStyle = {
            shadowColor: "#000",
            shadowOpacity: 0,
            shadowRadius: 0,
            shadowOffset: {height: 0, width: 0,},
            backgroundColor:'white',
            elevation:2,
            zIndex: 0
        };
        this.items.splice(this.start,0,this.items.splice(this.end,1)[0]);
        for(let i = 0;i<this.items.length;i++){
            this.items[i].setNativeProps({
            style: {...shadowStyle,top: this._getTopValueYById(i)}
            })
        }
        this.items = [];
        this.order = [];
        this.scrollArray = [];
        let temp = this.state.data;
        temp.splice(this.end,0,temp.splice(this.start,1)[0]);

        this.setState(()=>{
            return {
                data:temp,
                scrollflag:true,
            }
        })
    }
  1. 添加比较简单。自己修改state 数据即可
  addItem(){
        // 这里是将所有的内部 scrollArray 回归原位 
        for(let i = 0;i<this.scrollArray.length;i++){
            this.scrollArray[i].scrollTo({x: 0, y: 0, animated: false})
        }

        let temp = this.state.data;
        temp[temp.length] = {name:'五花肉'+temp.length}
        this.setState({
            data:temp,
        });
    }
  1. 删除,删除废了很大的功夫,原先想做成,向左拖动就直接删除。尝试之后,发现容易崩溃,所以就改成这种样子了。让其先显示删除按键,然后再删除。
//拖动,判断是否超过‘删除’View宽度的一半,超过显示,不超过隐藏
   onMomentumScrollEnd(e,index){
        let offsetX = e.nativeEvent.contentOffset.x;
        if(offsetX > this.itemHight/2){
            this.scrollArray[index].scrollTo({x: this.itemHight, y: 0, animated: false})
        }else{
            this.scrollArray[index].scrollTo({x: 0, y: 0, animated: false})
        }
    }
    removeItem(index){
        this.scrollArray[index].scrollTo({x: 0, y: 0, animated: false});
        this.items = [];
        this.order = [];

        if(this.scrollOffsetY > 0){
            this.scrollOffsetY -= this.itemHight;
            if(this.scrollOffsetY <0){
                this.scrollOffsetY = 0;
            }
    
            this.outsideScroll.scrollTo({x:0,y:this.scrollOffsetY,animated:false})
        }
        let temp = this.state.data;
        temp.splice(index,1);
        this.setState({
            data:temp,
        })

        this.scrollArray = [];
    }
    // 将其他显示的删除,挪回去
    onTouchStart(e,index){
        for(let i = 0;i<this.scrollArray.length;i++){
            if(i != index){
                this.scrollArray[i].scrollTo({x: 0, y: 0, animated: false})
            }
        }
    }

全部代码

import React, {Component} from 'react';
import {
    StyleSheet,
    View,
    Text,
    TouchableOpacity,
    ScrollView,
    Image,
    PanResponder,
} from 'react-native';
import Utils from '../../common/Utils'
const EXPROTHEIGHT = 20;

export default class CustomScrollView extends Component {
    constructor(props){
        super(props);
        this.state={
            data:[
                {name:'五花肉',},
                {name:'青椒',},
                {name:'红椒',},
            ],
        }

        this.itemHight = 50;
        this.interval = 5;
        this.countHeigth =  this.itemHight+this.interval; // interval为中间的间隙。
        this.scrollOffsetY = 0;
        this.items = [];
        this.order = [];
        this.ViewHeight = (this.state.data.length)*this.countHeigth>Utils.size.height-20-Utils.statusBarHeight?
            (this.state.data.length)*this.countHeigth:Utils.size.height-20-Utils.statusBarHeight,
        this.scrollArray = [];

    }

    static navigationOptions = { 
        header:null,
    };

    render(){
        return(
            <View style={styles.container}>
                <ScrollView 
                    showsVerticalScrollIndicator={false}
                    onScrollEndDrag={(e)=>this.exportOnScrollEndDrag(e)}
                    onMomentumScrollEnd={(e)=>this.exportonMomentumScrollEnd(e)}
                    ref = {(ref)=>{this.outsideScroll = ref}}
                >
                    <View style={{height:this.ViewHeight,width:Utils.size.width}}>
                        {this.state.data.map((item,index)=>{
                            return(
                                <View 
                                    ref={
                                        (ref)=>{
                                            if(index == this.items.length&&ref != null){
                                                this.items[this.items.length] = ref;
                                                this.order.push(index);
                                            }
                                        }
                                    }
                                    key={index+''} 
                                    style={{
                                        top:this._getTopValueYById(index),
                                        position: 'absolute',
                                        width:Utils.size.width-40,
                                        height:this.itemHight,
                                        backgroundColor:'white',
                                        marginBottom:this.interval,
                                        elevation:2,
                                        justifyContent:'center'
                                    }}
                                >
                                    <ScrollView
                                        ref={(ref)=>{
                                            if(index == this.items.length&&ref != null){
                                              this.scrollArray[index] = ref;
                                            }
                                          }}
                                        horizontal={true}
                                        style={{
                                            // backgroundColor:'red'
                                        }}
                                        showsHorizontalScrollIndicator={false}
                                        onTouchStart = {(e)=>{this.onTouchStart(e,index)}}                                        
                                        onMomentumScrollEnd = {(e)=>{this.onMomentumScrollEnd(e,index)}}
                                    >
                                        <View style={{width:Utils.size.width-40,height:this.itemHight,justifyContent:'center'}}>
                                            <TouchableOpacity style={{marginLeft:10,marginRight:10}} onPress={()=>{console.log(index)}}>
                                                <View style={{flexDirection:'row',}}>
                                                    <Image 
                                                        source={require('../../resources/image/moveImage.png')} 
                                                        style={{width:30,height:30}}
                                                        {...this._panResponder.panHandlers}
                                                    />
                                                    <Text style={{marginLeft:10,fontSize:18}}>
                                                        {item.name}
                                                    </Text>
                                                </View>
                                             </TouchableOpacity>
                                        </View>
                                        <TouchableOpacity 
                                                onPress={this.removeItem.bind(this,index)}
                                                style={{
                                                    width:this.itemHight,
                                                    height:this.itemHight,     
                                                    justifyContent:'center',
                                                    alignItems:'center',
                                                    backgroundColor:'red'
                                                    }}>
                                                    <Text style={{fontSize:15}}>{"删除"}</Text>
                                        </TouchableOpacity>                                  
                                    </ScrollView>
                                </View>
                            )
                        })}
                    </View>
                </ScrollView>
                <TouchableOpacity 
                    onPress={
                        this.addItem.bind(this)
                    } 
                    style={{
                        top:0.8*Utils.size.height,
                        left:0.7*Utils.size.width,
                        position: 'absolute',
                    }}>
                        <Image style={{zIndex:2}} source={require('../../resources/image/add.png')}></Image>
                </TouchableOpacity>
            </View>
        )
    }

    addItem(){
        // 这里是将所有的内部 scrollArray 回归原位 
        for(let i = 0;i<this.scrollArray.length;i++){
            this.scrollArray[i].scrollTo({x: 0, y: 0, animated: false})
        }

        let temp = this.state.data;
        temp[temp.length] = {name:'五花肉'+temp.length}
        this.setState({
            data:temp,
        });

        
    }

    exportOnScrollEndDrag(e){
        this.scrollOffsetY = e.nativeEvent.contentOffset.y;
    }
     
    exportonMomentumScrollEnd(e){
        this.scrollOffsetY =  e.nativeEvent.contentOffset.y;
    }


    componentWillMount(){
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
      
            onPanResponderGrant: (evt, gestureState) => {
                this.setState(()=>{
                    return {
                        scrollflag:false,
                    }
                });
                const {pageY, locationY} = evt.nativeEvent;
                this.index = this._getIdByPosition(pageY);
                this.preY = pageY - locationY - EXPROTHEIGHT;
                let item = this.items[this.index];
                if(item == undefined) {
                    console.log(this.index);
                }
                this.start = this.index;
                this.end = this.index;
                item.setNativeProps({
                    style: {
                        shadowColor: "#333",
                        shadowOpacity: 0.3,
                        shadowRadius: 50,
                        shadowOffset: {height: 0, width: 2},
                        elevation: 5,
                        zIndex: 1,
                        backgroundColor:'gray',
                    }
                });
            },
            onPanResponderMove: (evt, gestureState) => {
                let top = this.preY + gestureState.dy+this.scrollOffsetY;
                let item = this.items[this.index];
                item.setNativeProps({
                    style: {top: top}
                });
        
                let collideIndex = this._getIdByPosition(evt.nativeEvent.pageY);
                if(collideIndex !== this.index && collideIndex !== -1) {
                    let collideItem = this.items[collideIndex];
                    collideItem.setNativeProps({
                        style: {top: this._getTopValueYById(this.index)}
                    });
                    [this.items[this.index], this.items[collideIndex]] = [this.items[collideIndex], this.items[this.index]];
                    [this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
                    this.index = collideIndex;
                    this.end = this.index;
                }
            },
            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onPanResponderRelease: this._releaseAndTerminate.bind(this),
            onPanResponderTerminate: this._releaseAndTerminate.bind(this)
          });
    }

    componentWillUpdate(){
        this.ViewHeight = (this.state.data.length)*this.countHeigth>Utils.size.height-20-Utils.statusBarHeight?
            (this.state.data.length)*this.countHeigth:Utils.size.height-20-Utils.statusBarHeight;
    }

    _releaseAndTerminate(evt, gestureState){
        const shadowStyle = {
            shadowColor: "#000",
            shadowOpacity: 0,
            shadowRadius: 0,
            shadowOffset: {height: 0, width: 0,},
            backgroundColor:'white',
            elevation:2,
            zIndex: 0
        };
        this.items.splice(this.start,0,this.items.splice(this.end,1)[0]);
        for(let i = 0;i<this.items.length;i++){
            this.items[i].setNativeProps({
            style: {...shadowStyle,top: this._getTopValueYById(i)}
            })
        }
        this.items = [];
        this.order = [];
        this.scrollArray = [];
        let temp = this.state.data;
        temp.splice(this.end,0,temp.splice(this.start,1)[0]);

        this.setState(()=>{
            return {
                data:temp,
                scrollflag:true,
            }
        })
    }

    _getIdByPosition(pageY){
        pageY = pageY - EXPROTHEIGHT;
        var id = -1;
        id = Math.floor(parseFloat((pageY+this.scrollOffsetY)/(this.countHeigth)));
        if(id<0 || id >= this.state.data.length) {
            return -1;

        }
        return id;
    }

    _getTopValueYById(id){
        return (id) * (this.countHeigth);
    }

    //拖动,判断是否超过‘删除’View宽度的一半,超过显示,不超过隐藏
    onMomentumScrollEnd(e,index){
        let offsetX = e.nativeEvent.contentOffset.x;
        if(offsetX > this.itemHight/2){
            this.scrollArray[index].scrollTo({x: this.itemHight, y: 0, animated: false})
        }else{
            this.scrollArray[index].scrollTo({x: 0, y: 0, animated: false})
        }
    }

    onTouchStart(e,index){
        for(let i = 0;i<this.scrollArray.length;i++){
            if(i != index){
                this.scrollArray[i].scrollTo({x: 0, y: 0, animated: false})
            }
        }
    }

    removeItem(index){
        this.scrollArray[index].scrollTo({x: 0, y: 0, animated: false});
        this.items = [];
        this.order = [];

        if(this.scrollOffsetY > 0){
            this.scrollOffsetY -= this.itemHight;
            if(this.scrollOffsetY <0){
                this.scrollOffsetY = 0;
            }
    
            this.outsideScroll.scrollTo({x:0,y:this.scrollOffsetY,animated:false})
        }
        let temp = this.state.data;
        temp.splice(index,1);
        this.setState({
            data:temp,
        })

        this.scrollArray = [];
    }
}


const styles = StyleSheet.create({
    container: {
        flex:1,
        marginRight:20,
        marginLeft:20,
        marginTop:20
    },
});

另外 Utils 文件在
https://gitee.com/yizhi108/react-native-location/
可以下载到。
其实里面全部都是一些常量,可以直接用常量进行替换。
如果能帮助到大家深感荣幸。
另外如有其它问题,欢迎留言讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容