美文网首页React Native学习
React-native 仿微信发布图片拖拽删除

React-native 仿微信发布图片拖拽删除

作者: 随遇而安_2750 | 来源:发表于2017-10-18 10:50 被阅读85次

    1.React-Native手势系统

    参考连接:

    “指尖上的魔法” -- 谈谈React-Native中的手势

    React-Native ListView拖拽交换Item

    2.利用手势系统PanResponder开发图片拖拽删除Demo

    要实现的效果如下图所示:

    shoushi.gif

    代码详解:

    1.如何布局

    关键点:

    • 每个图片view都是绝对定位,这样才能脱离文档流来进行拖拽操作,并且图片整体区域距离页面顶部200距离;
    • 每四个一行显示,利用计算得出每张图片的left值和top值。

    我们把所有图片资源存放在state中:

    this.state = {
          imgs: [
            'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2908558741,1476032262&fm=27&gp=0.jpg',
            'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2660096790,1445343165&fm=27&gp=0.jpg',
            'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=586492344,3997176522&fm=27&gp=0.jpg',
            'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1138659146,799893005&fm=27&gp=0.jpg',
            'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2634329296,2422503635&fm=27&gp=0.jpg',
            'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2793821546,4232499990&fm=27&gp=0.jpg',
          ],
          showDelModal: false,
          delText: '拖拽此处可以删除',
    };
    

    render方法:

    render() {
        return (
          <View style={styles.imgContainer}>
            {
              this.state.imgs.map((v, i) => {
                let l = 0; // left
                let t = 0; // top
                if(i >= 0 && i <= 3){
                  l = (10 + IMG_WIDTH)*i + 10;
                }else if(i > 3 && i <= 7){
                  l = (10 + IMG_WIDTH)*(i - 4) + 10;
                }else if(i > 7){
                  l = 10;
                };
                t = Math.floor(i/4)*(10+IMG_HEIGHT)+10 + 200;
                return (
                  <View
                    style={[styles.imageStyle, {left: l, top: t}]}
                    {...this._panResponder.panHandlers}
                    ref={ref => this.items[i] = ref}
                    activeOpacity={0.2}
                    key={i}
                  >
                    <TouchableOpacity onPress={() => this.pressImage(v, i)} style={[styles.imageStyle, {left: 0, top: 0}]}>
                      <Image  source={{uri: v}} style={[styles.imageStyle, {left: 0, top: 0}]} />
                    </TouchableOpacity>
                  </View>
                )
              })
            }
            
          </View>
        )
      }
    

    其中:{...this._panResponder.panHandlers}是将手势系统应用于当前View节点;ref={ref => this.items[i] = ref}是拿到每个View的DOM,因为我们要对每个View进行拖拽操作,通过ref可以设置它的style。

    2.使用手势系统

    我们通常在componentWillMount生命周期中使用手势系统:

    componentWillMount(){
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,   
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {// 手指触碰屏幕那一刻触发
               
            },
            onPanResponderMove: (evt, gestureState) => {// 手指移动过程中触达
              
            },
            onPanResponderTerminationRequest: (evt, gestureState) => true,// 当有其他不同手势出现,响应是否中止当前的手势  
            onPanResponderRelease: (evt, gestureState) => {// 手指离开屏幕触发
    
            },
            onPanResponderTerminate: (evt, gestureState) => { // 当前手势中止触发
              
            }
        });
    

    实际上,我们只需要操作三个方法即可:

    • 手指触发的时候,成为响应者,记录此时的位置信息(onPanResponderGrant)
    • 手指移动过程中,根据计算移动信息,主要就是left、top值,来确定view的位置(onPanResponderMove)
    • 手指释放的时候,根据位置信息来决定是否删除该view,还是恢复原来的位置(onPanResponderRelease)

    分别看一下每个方法的应用:

    手指触碰触发的方法

    onPanResponderGrant: (evt, gestureState) => {   // 手指触碰屏幕那一刻触发
                
                const {pageX, pageY, locationY, locationX} = evt.nativeEvent;  // pageY是相对于根节点的位置,locationY是相对于元素自己
                
                this.index = this._getIdByPosition(pageX, pageY);
    
                this.preY = pageY - locationY;   // 保存当前正确点击item的位置,为了后面移动item
                this.preX = pageX - locationX;   // 保存当前正确点击item的位置,为了后面移动item
                
                let item = this.items[this.index];
    
                item.setNativeProps({
                    style: {
                        shadowColor: "#000",
                        shadowOpacity: 0.7,
                        shadowRadius: 5,
                        shadowOffset: {height: 4, width: 2},
                        elevation: 15,
                        zIndex: 999
                    }
                });
    
                this.setState({
                  showDelModal: true
                });
    
                // 删除区域出来
                // this.slideAniValue.setValue(-60);
                Animated.timing(this.slideAniValue, {
                    toValue: 0,
                    duration: 300,
                    easing: Easing.linear,// 线性的渐变函数
                }).start();
    
    },
    

    两个参数: evt, gestureState

    evt: 使用evt.nativeEvent可以拿到类似于web的event对象,常用的两个属性pageX, locationX,其中pageX代表手指相对于根节点的位置;locationX代表手指在当前元素的位置。

    gestureState: 这个gestureState是一个对象,包含手势进行过程中更多的信息,其中比较常用的几个是:

    • dx/dy:手势进行到现在的横向/纵向相对位移
    • vx/vy:此刻的横向/纵向速度
    • numberActiveTouches:responder上的触摸的个数

    1.this.index = this._getIdByPosition(pageX, pageY)这是为了得到到底触摸了哪个view,然后可以通过this.index拿到他的dom对象;

    2.this.preYthis.preX是保存当前点击的view的位置,为了之后的拖拽移动做准备;

    3.item.setNativeProps修改该dom属性,使其突出显示;

    手指移动触发的方法

    onPanResponderMove: (evt, gestureState) => {
              
          let top = this.preY + gestureState.dy;
          let left = this.preX + gestureState.dx;
          let item = this.items[this.index];
    
          item.setNativeProps({
              style: {top: top, left: left},
          });
            
        if(top >= HEIGHT- IMG_HEIGHT - 60){ // 图片进入删除区域
                  this.setState({
                    delText: '松开删除',
                  });
    
                }else{
                  this.setState({
                    delText: '拖拽此处可以删除'
                  })
                }   
    },
    

    1.根据当前的手指位置,确定移动的view的位置;

    2.移动到删除区域,提示“松开删除”,否则是“拖拽此处可以删除”;

    手指离开屏幕时触发的方法

    onPanResponderRelease: (evt, gestureState) => {  // 手指离开屏幕触发
    
        this.setState({
          showDelModal: false
        });
    
        // 删除区域隐藏
        // this.state.slideOutBottom.setValue(-60);
        Animated.timing(this.slideAniValue, {
            toValue: -60,
            duration: 300,
            easing: Easing.linear,// 线性的渐变函数
        }).start();
    
        if(this.state.delText == '松开删除'){
          // 删除图片
          this.delImage(this.index);
        }else{
    
          const shadowStyle = {
              shadowColor: "#000",
              shadowOpacity: 0,
              shadowRadius: 0,
              shadowOffset: {height: 0, width: 0,},
              elevation: 0,
              zIndex: 1
          };
          let item = this.items[this.index];
          // 回到原来的位置
          item.setNativeProps({
              style: {...shadowStyle, top: this._getTopValueYById(this.index).top, left: this._getTopValueYById(this.index).left}
          });
        }
        
    },
    

    抬起手指的时候,需要做两件事:

    1.如果移动到删除区域,松开就删除当前view;

    2.如果没有移动到删除区域就松开,那么恢复位置,删除区域撤销;

    删除照片的方法

    delImage(index) {
        // 删除照片
        this.imgDelAni(index);
        let cacheData = this.state.imgs;
        cacheData.splice(index,1);
        // 调整位置
        this.setState({
          imgs: cacheData
        });
    
        let l = 0; // left
        let t = 0; // top
        if(index>=0 && index<=3){
          l = (10+IMG_WIDTH)*index + 10;
        }else if(index>3 && index<=7){
          l = (10+IMG_WIDTH)*(index-4) + 10;
        }else if(index>7){
          l = 10;
        };
        t = Math.floor(index/4)*(10+IMG_HEIGHT)+10 + 200;
    
        this.items[index].setNativeProps({
          style: {
            left: l,
            top: t,
            zIndex: 1
          }
        })
    
      }
    

    1.删除照片,更新删除后的照片数据;

    2.重新调整位置;

    根据view的index,来确定它的位置信息

    _getTopValueYById(id) {
        
        let top = 0;
        let left = 0;
    
        if(id >= 0 && id <= 3){
          left = (10 + IMG_WIDTH)*id + 10;
        }else if(id > 3 && id <= 7){
          left = (10 + IMG_WIDTH)*(id - 4) + 10;
        }else if(id > 7){
          left = 10;
        };
    
        top = Math.floor(id/4)*(10+IMG_HEIGHT)+10 + 200;
    
        return {
          top,
          left
        }
    
      }
    

    1.10表示图片view之间的间距;

    2.200表示图片区域盒子距离也买你顶部的top值,并且必须是固定的;

    根据手指的当前位置确定是哪一个图片view

    _getIdByPosition(pageX, pageY) {
        let w = IMG_WIDTH;
        let h = IMG_HEIGHT;
        let id = -1;
    
        if(pageY >= 210 && pageY <= 210 + h){
          // 在第一排点击
          if(pageX >= 10 && pageX <= 10 + w){
            id = 0;
          }
          if(pageX >= 20 + w && pageX <= 20 + w + w){
            id = 1;
          }
          if(pageX >= 30 + 2*w){
            id = 2;
          }
          if(pageX >= 40 + 3*w){
            id = 3;
          }
        }
        if(pageY >= 210 + 20 + h && pageY <= 210 + 20 + h + h){
          // 在第二排点击
          if(pageX >= 10 && pageX <= 10 + w){
            id = 4;
          }
          if(pageX >= 20 + w && pageX <= 20 + w + w){
            id = 5;
          }
          if(pageX >= 30 + 2*w){
            id = 6;
          }
          if(pageX >= 40 + 3*w){
            id = 7;
          }
        }
    
        if(pageY >= 210 + 20 + h + h + 10 && pageY <= 210 + 20 + h + h + 10 + h){
          // 在第三排点击的
          if(pageX >= 10 && pageX <= 10 + w){
            id = 8;
          }
          
        }
    
        return id;
      }
    

    完整代码:

    /**
     * Sample React Native App
     * https://github.com/facebook/react-native
     * @flow
     */
    
    import React, { Component } from 'react';
    import {
      Platform,
      StyleSheet,
      Text,
      View,
      TouchableOpacity,
      BackHandler,
      Image,
      ViewPagerAndroid,
      DeviceEventEmitter,
      PanResponder,
      TouchableWithoutFeedback,
      Animated,
      Easing 
    } from 'react-native';
    
    import {WIDTH, HEIGHT} from '../utils/config';
    import Button from '../components/Button';
    
    const IMG_WIDTH = (WIDTH-52) / 4;
    const IMG_HEIGHT = (WIDTH-40) / 4;
    
    export default class ImageView extends Component<{}> {
      constructor(props){
        super(props);
        this.state = {
          imgs: [
            'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2908558741,1476032262&fm=27&gp=0.jpg',
            'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2660096790,1445343165&fm=27&gp=0.jpg',
            'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=586492344,3997176522&fm=27&gp=0.jpg',
            'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1138659146,799893005&fm=27&gp=0.jpg',
            'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2634329296,2422503635&fm=27&gp=0.jpg',
            'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2793821546,4232499990&fm=27&gp=0.jpg',
          ],
          showDelModal: false,
          delText: '拖拽此处可以删除',
        };
    
        this.slideAniValue = new Animated.Value(-60);
    
        this.items = [];
      }
      static navigationOptions = ({navigation}) => {
    
        return {
          title: "照片拖拽删除",
          
          headerLeft: (
            <TouchableOpacity >
              <Text style={styles.titleText} onPress={()=>navigation.goBack(null)}>返回</Text>
            </TouchableOpacity>
          )
        }
      }
    
      componentDidMount() {
        //注册通知
        DeviceEventEmitter.addListener('ChangeImgData',(data)=>{
            //接收到详情页发送的通知,刷新首页的数据,改变按钮颜色和文字,刷新UI
            this.setState({
              imgs: data.imgData
            })
        });
    
      }
    
      pressImage(v, i) {
        const {navigation} = this.props;
        navigation.navigate('ImageShowScreen', {uri: v, index: i, images: this.state.imgs});
      } 
    
      _getIdByPosition(pageX, pageY) {
        let w = IMG_WIDTH;
        let h = IMG_HEIGHT;
        let id = -1;
    
        if(pageY >= 210 && pageY <= 210 + h){
          // 在第一排点击
          if(pageX >= 10 && pageX <= 10 + w){
            id = 0;
          }
          if(pageX >= 20 + w && pageX <= 20 + w + w){
            id = 1;
          }
          if(pageX >= 30 + 2*w){
            id = 2;
          }
          if(pageX >= 40 + 3*w){
            id = 3;
          }
        }
        if(pageY >= 210 + 20 + h && pageY <= 210 + 20 + h + h){
          // 在第二排点击
          if(pageX >= 10 && pageX <= 10 + w){
            id = 4;
          }
          if(pageX >= 20 + w && pageX <= 20 + w + w){
            id = 5;
          }
          if(pageX >= 30 + 2*w){
            id = 6;
          }
          if(pageX >= 40 + 3*w){
            id = 7;
          }
        }
    
        if(pageY >= 210 + 20 + h + h + 10 && pageY <= 210 + 20 + h + h + 10 + h){
          // 在第三排点击的
          if(pageX >= 10 && pageX <= 10 + w){
            id = 8;
          }
          
        }
    
        return id;
      }
    
      _getTopValueYById(id) {
        
        let top = 0;
        let left = 0;
    
        if(id >= 0 && id <= 3){
          left = (10 + IMG_WIDTH)*id + 10;
        }else if(id > 3 && id <= 7){
          left = (10 + IMG_WIDTH)*(id - 4) + 10;
        }else if(id > 7){
          left = 10;
        };
    
        top = Math.floor(id/4)*(10+IMG_HEIGHT)+10 + 200;
    
        return {
          top,
          left
        }
    
      }
    
      componentWillMount(){
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,   
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {   // 手指触碰屏幕那一刻触发
                
                const {pageX, pageY, locationY, locationX} = evt.nativeEvent;  // pageY是相对于根节点的位置,locationY是相对于元素自己
                
                this.index = this._getIdByPosition(pageX, pageY);
    
                this.preY = pageY - locationY;   // 保存当前正确点击item的位置,为了后面移动item
                this.preX = pageX - locationX;   // 保存当前正确点击item的位置,为了后面移动item
                
                let item = this.items[this.index];
    
                item.setNativeProps({
                    style: {
                        shadowColor: "#000",
                        shadowOpacity: 0.7,
                        shadowRadius: 5,
                        shadowOffset: {height: 4, width: 2},
                        elevation: 15,
                        zIndex: 999
                    }
                });
    
                this.setState({
                  showDelModal: true
                });
    
                // 删除区域出来
                // this.slideAniValue.setValue(-60);
                Animated.timing(this.slideAniValue, {
                    toValue: 0,
                    duration: 300,
                    easing: Easing.linear,// 线性的渐变函数
                }).start();
    
            },
            onPanResponderMove: (evt, gestureState) => {
              
                let top = this.preY + gestureState.dy;
                let left = this.preX + gestureState.dx;
                let item = this.items[this.index];
    
                item.setNativeProps({
                    style: {top: top, left: left},
                });
    
                if(top >= HEIGHT- IMG_HEIGHT - 60){ // 图片进入删除区域
                  this.setState({
                    delText: '松开删除',
                  });
    
                }else{
                  this.setState({
                    delText: '拖拽此处可以删除'
                  })
                }
            },
            onPanResponderTerminationRequest: (evt, gestureState) => true,   // 当有其他不同手势出现,响应是否中止当前的手势  
            onPanResponderRelease: (evt, gestureState) => {  // 手指离开屏幕触发
    
                this.setState({
                  showDelModal: false
                });
    
                // 删除区域隐藏
                // this.state.slideOutBottom.setValue(-60);
                Animated.timing(this.slideAniValue, {
                    toValue: -60,
                    duration: 300,
                    easing: Easing.linear,// 线性的渐变函数
                }).start();
    
                if(this.state.delText == '松开删除'){
                  // 删除图片
                  this.delImage(this.index);
                }else{
    
                  const shadowStyle = {
                      shadowColor: "#000",
                      shadowOpacity: 0,
                      shadowRadius: 0,
                      shadowOffset: {height: 0, width: 0,},
                      elevation: 0,
                      zIndex: 1
                  };
                  let item = this.items[this.index];
                  // 回到原来的位置
                  item.setNativeProps({
                      style: {...shadowStyle, top: this._getTopValueYById(this.index).top, left: this._getTopValueYById(this.index).left}
                  });
                }
                
            },
            onPanResponderTerminate: (evt, gestureState) => {    // 当前手势中止触发
              
            }
        });
    
      }
    
      imgDelAni(index) {
        
      }
    
      delImage(index) {
        // 删除照片
        this.imgDelAni(index);
        let cacheData = this.state.imgs;
        cacheData.splice(index,1);
        // 调整位置
        this.setState({
          imgs: cacheData
        });
    
        let l = 0; // left
        let t = 0; // top
        if(index>=0 && index<=3){
          l = (10+IMG_WIDTH)*index + 10;
        }else if(index>3 && index<=7){
          l = (10+IMG_WIDTH)*(index-4) + 10;
        }else if(index>7){
          l = 10;
        };
        t = Math.floor(index/4)*(10+IMG_HEIGHT)+10 + 200;
    
        this.items[index].setNativeProps({
          style: {
            left: l,
            top: t,
            zIndex: 1
          }
        })
    
      }
    
      render() {
        return (
          <View style={styles.imgContainer}>
            {
              this.state.imgs.map((v, i) => {
                let l = 0; // left
                let t = 0; // top
                if(i >= 0 && i <= 3){
                  l = (10 + IMG_WIDTH)*i + 10;
                }else if(i > 3 && i <= 7){
                  l = (10 + IMG_WIDTH)*(i - 4) + 10;
                }else if(i > 7){
                  l = 10;
                };
                t = Math.floor(i/4)*(10+IMG_HEIGHT)+10 + 200;
                return (
                  <View
                    style={[styles.imageStyle, {left: l, top: t}]}
                    {...this._panResponder.panHandlers}
                    ref={ref => this.items[i] = ref}
                    activeOpacity={0.2}
                    key={i}
                  >
                    <TouchableOpacity onPress={() => this.pressImage(v, i)} style={[styles.imageStyle, {left: 0, top: 0}]}>
                      <Image  source={{uri: v}} style={[styles.imageStyle, {left: 0, top: 0}]} />
                    </TouchableOpacity>
                  </View>
                )
              })
            }
    
            <Animated.View style={[styles.delWraper, {bottom: this.slideAniValue}]}>
              <Text style={{color: '#fff'}}>{this.state.delText}</Text>
            </Animated.View>
    
            {
              this.state.showDelModal &&
              <View style={styles.shadowModal}></View>
            }
            
          </View>
        )
      }
    }
    
    const styles = StyleSheet.create({
      imgContainer: {
        flex: 1
      },
      imageStyle: {
        width: IMG_WIDTH,
        height: IMG_HEIGHT,
        position: 'absolute',
        borderRadius: 3,
      },
      delWraper:{
        width: WIDTH,
        height: 60,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'red',
        position: 'absolute',
        // bottom: 0,
        left: 0,
        zIndex: 998
      },
      shadowModal:{
        width: WIDTH,
        height: HEIGHT,
        position: 'absolute',
        backgroundColor: '#000',
        opacity: 0.4,
        zIndex: 888,
        bottom: 0,
        left: 0,
      }
    });
    
    

    相关文章

      网友评论

      • haisonLIN:大佬,把你的demo报错了,undefined is not an object (evaluating 'item.setNativeProps')
      • haisonLIN:厉害了,大神

      本文标题:React-native 仿微信发布图片拖拽删除

      本文链接:https://www.haomeiwen.com/subject/phvpyxtx.html