RN打造自己的大图查看浏览

rn触摸手势学习——PanResponder。
打造一个大图浏览功能,实现:单击事件、双击事件(双击缩放图片)、长按事件、图片滑动、双指缩放图片。
效果预览:


20161220124610-2f36201a79.[gif-2-mp4.com].gif

下面来一步步实现。

1. PanResponder

PanResponder类可以将多点触摸操作协调成一个手势。它使得一个单点触摸可以接受更多的触摸操作,也可以用于识别简单的多点触摸手势。
主要方法:

  • onMoveShouldSetPanResponder: (e, gestureState) => {...}
  • onStartShouldSetPanResponder: (e, gestureState) => {...}
  • onPanResponderGrant: (e, gestureState) => {...}
  • onPanResponderMove: (e, gestureState) => {...}
  • onPanResponderRelease: (e, gestureState) => {...}
基本用法
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 现在会被设置为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 ( 
     
  ); },

详细请看文档 PanResponder

2. 实现图片跟随滑动

很明显,我们需要在onPanResponderMove=(evt, gs)=>{...}中实现逻辑代码。
直接贴代码:

/**
*滑动距离大于5才会触发滑动事件
*longPress 是长按事件标识
*this.isScale是双击缩放标识
*/
if((Math.abs(gs.dx)>5 || Math.abs(gs.dy)>5) && !longPress && !this.isScale) {
    isSlide = true; //触发滑动事件,标记滑动为真
    this._clickTimeout && clearTimeout(this._clickTimeout);
    this._longPressTimeout && clearTimeout(this._longPressTimeout);
}
if(!longPress) {
    //this._offsetY、this._offsetX是上次移动距离,gs.dy、gs.dx当前移动距离
    //算出x和y轴的偏移量dy,dx
    let dy = gs.dy - this._offsetY;
    let dx = gs.dx - this._offsetX;
    this._offsetX = gs.dx;
    this._offsetY = gs.dy;
    //dy就是上下方向,这里限制如果比屏幕小,这上下方向不可移动
    if(dy > 0) {
        if(this.state.top <= 0 && this.state.top + dy > 0) {
            this.setState({top: 0,});
            dy = 0;
          }else if(this.state.top > 0){
            dy = 0;
          }
        }else {
          if(this.state.viewHeight <= ScreenHeight - this.state.top) {
            dy = 0;
          }
        }
      //改变top和left,就可以看到图片位置发生变化了
        this.setState({
          top: this.state.top + dy,
          left: this.state.left + dx,
        });
      }

上面注释已经很清晰了。

3. 双击缩放图片

先来实现双击事件的监听。
手指全部离开屏幕后,会触发onPanResponderRelease: (evt, gs) => {...}
所以我们clickNum变量记录点击数,设定一个很短时间内连续触摸,就当作双击事件被触发了。超过那个时间就当是单击(每个事件触发后,重置clickNum)
代码:

clickNum++; //记录点击数
        if(!isSlide && !longPress) {//滑动和长按都没有被触发
          if(clickNum == 1) {
          //启动一个200毫秒计时器,这个时间内没有再次触摸抬起的话,就是单击事件,重置clickNum=0;
            this._clickTimeout = setTimeout(
              () => {
                 if(clickNum == 1 && !touchBegin){
                  // alert('单击');
                }
                clickNum = 0;
              },
              200
            );
          }else if(clickNum == 2){//否则,触发双击事件
            // alert('双击'+gs.x0);
            this._scale(1, 0, 0, this._x - ScreenWidth / 2, this._y - (ScreenHeight - 20) / 2);//缩放
            this._clickTimeout && clearTimeout(this._clickTimeout);//取消点击计时器
            this._longPressTimeout && clearTimeout(this._longPressTimeout);//取消长按计时器
            clickNum = 0;//重置点击次数
          }

这样我们就能监听到是否双击了。
下面实现this._scale函数

/**
*type:缩放类型,1双击缩放,2手势缩放
*w:目标缩放宽度,双击为0
*h:目标缩放高度,双击为0
*offsetX:x中心轴偏移量
*offsetY:y中心轴偏移量
*/
_scale(type, w, h, offsetX, offsetY) {
    if (type === 1) {
      let sw = this.state.viewWidth;
      let sh = this.state.viewHeight;
      let pt = 0;
      let pl = 0;
      let offsetH = 0;
      let offsetW = 0;
      if(this.state.viewWidth <= ScreenWidth) {
        if(this.state.viewWidth < MaxW) {
          sw = MaxW;
          sh = MaxH;
          offsetH = offsetY*MaxH/this.state.viewHeight;
          offsetW = offsetX*MaxW/this.state.viewWidth;
          pt = (ScreenHeight - sh - 20) / 2 - offsetH;
          pl = (ScreenWidth - sw) / 2 - offsetW;

          if(MaxH < ScreenHeight) {
            pt = (ScreenHeight - sh - 20) / 2;
          }else {
            if(pt > 0) {
              pt = 0;
            }else if(ScreenHeight - pt > sh) {
              pt =ScreenHeight - sh;
            }
          }

          if(pl > 0) {
            pl = 0;
          }else if(ScreenWidth - pl > sw) {
            pl =ScreenWidth - sw;
          }
        }
      }else {
        sw = ScreenWidth;
        sh = (MaxH*ScreenWidth)/MaxW;
        pt = (ScreenHeight - sh - 20) / 2;
        pl = (ScreenWidth - sw) / 2;
      }
      // this.setState({
      //   viewWidth: sw,
      //   viewHeight: sh,
      //   top: pt,
      //   left: pl,
      // });
      // alert(sw+', '+sh+', '+pt+', '+pl);
      this._scaleAnimated(sw, sh, pt, pl,400);
      this.interval && clearInterval(this.interval);
    }else {
      //两手指缩放操作
      if(w > ScreenWidth && w < MaxW) {
        let sw = w;
        let sh = h;
        let offsetH = offsetY*sw/this.state.viewHeight;
        let offsetW = offsetX*sw/this.state.viewWidth;
        let pt = this.state.top + (this.state.viewWidth - sw)/2;
        let pl = this.state.left + (this.state.viewHeight - sh)/2;

        if(sh < ScreenHeight) {
          pt = (ScreenHeight - sh - 20) / 2;
        }else {
          if(pt > 0) {
            pt = 0;
          }else if(ScreenHeight - pt > sh) {
            pt =ScreenHeight - sh;
          }
        }
        // alert(sw+', '+pl+', '+ScreenWidth+', '+offsetW+', '+offsetX);
        if(pl > 0) {
          pl = 0;

        }else if(ScreenWidth - pl > sw) {
          pl =ScreenWidth - sw;
        }

        this.setState({
          viewWidth: sw,
          viewHeight: sh,
          top: pt,
          left: pl,
        });
        // this._scaleAnimated(sw, sh, pt, pl,0);
        // this.interval && clearInterval(this.interval);
      }
    }
  }

直接看type=1里面的,有点麻烦,没想到优化,将就着先
思路就是,确认缩放后的长宽,计算缩放后top和left的位置,然后就是执行this._scaleAnimated(sw, sh, pt, pl,400);执行动画缩放

/**
  sw: 缩放后宽度
  sh: 缩放后高度
  pt: 缩放后top
  pl: 缩放后left
  */
  _scaleAnimated(sw, sh, pt, pl,time) {

    let vw = (sw - this.state.viewWidth)/ (time/60.0);
    let vh = (sh - this.state.viewHeight) / (time/60.0);
    let vt = (pt - this.state.top) / (time/60.0);
    let vl = (pl - this.state.left) / (time/60.0);

    // let time = 0.0;
    let ss =sw+', '+sh+', '+pt+', '+pl;
    this.interval2 = setInterval(()=>{
      // time = time + (time/60.0);
      if(Math.abs(this.state.viewWidth - sw) < Math.abs(vw)) {
        vw = sw - this.state.viewWidth;
        vh = sh - this.state.viewHeight;
        vt = pt - this.state.top;
        vl = pl - this.state.left;
        this.interval2 && clearInterval(this.interval2);
      }
      // if(time >= 400.0) {
      //   this.interval2 && clearInterval(this.interval2);
      // }
      console.log(vw+', '+vh+', '+vt+', '+vl);
      this.setState({
        viewWidth: this.state.viewWidth + vw,
        viewHeight: this.state.viewHeight + vh,
        top: this.state.top + vt,
        left: this.state.left + vl,
      });
      // alert(this.state.viewWidth+', '+this.state.viewHeight+', '+this.state.top+', '+this.state.left+'==='+ss);
    }, 10);
  }

这里用setInterval来实现动画,性能问题没考虑过,
尝试用animated动画来实现,但是那些位置我把控不了,尝试很多遍还是放弃了,谁知道还望赐教。

4. 手势缩放

if(gs.numberActiveTouches >= 2 ) {
          this.isScale = true;
          if(!longPress) {
            this._longPressTimeout && clearTimeout(this._longPressTimeout);
            if(this._touches[0].x <= 0) {
              this._touches[0].x = evt.nativeEvent.changedTouches[0].pageX;
              this._touches[0].y = evt.nativeEvent.changedTouches[0].pageY;
              this._touches[1].x = evt.nativeEvent.changedTouches[1].pageX;
              this._touches[1].y = evt.nativeEvent.changedTouches[1].pageY;
              this._offsetXY = {};
              this._offsetXY.x = (evt.nativeEvent.changedTouches[1].pageX + evt.nativeEvent.changedTouches[0].pageX)/2;
              this._offsetXY.y = (evt.nativeEvent.changedTouches[1].pageY + evt.nativeEvent.changedTouches[0].pageY)/2;
            }else {
              //计算上次两点距离

              const distanceX = Math.abs(this._touches[1].x - this._touches[0].x);
              const distanceY = Math.abs(this._touches[1].y - this._touches[0].y);
              this._distance = Math.sqrt(distanceX*distanceX + distanceY*distanceY);
              //计算本次两点距离
              const distanceX2 = Math.abs(evt.nativeEvent.changedTouches[1].pageX - evt.nativeEvent.changedTouches[0].pageX);
              const distanceY2 = Math.abs(evt.nativeEvent.changedTouches[1].pageY - evt.nativeEvent.changedTouches[0].pageY);
              this._distance2 = Math.sqrt(distanceX2*distanceX2 + distanceY2*distanceY2);
              //缩放两点中心的偏移量
              const offsetXY2 = {};
              offsetXY2.x = (evt.nativeEvent.changedTouches[1].pageX + evt.nativeEvent.changedTouches[0].pageX)/2;
              offsetXY2.y = (evt.nativeEvent.changedTouches[1].pageY + evt.nativeEvent.changedTouches[0].pageY)/2;

              const sw = this.state.viewWidth+((this._distance2-this._distance)/8);
              const sh = this.state.viewHeight*sw/this.state.viewWidth;

              this._scale(2,sw,sh,0,0);
              this._clickTimeout && clearTimeout(this._clickTimeout);
              clickNum = 0;
            }
          }

        }

这里就不解释了,也不注释了,有耐心就看,就是计算两次手指移动距离什么的。
代码以后上传。

你可能感兴趣的:(RN打造自己的大图查看浏览)