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 ( 
    <View {...this._panResponder.panHandlers} /> 
  ); },

詳細請看文檔 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;
            }
          }

        }

這里就不解釋了,也不注釋了,有耐心就看,就是計算兩次手指移動距離什么的。
代碼以后上傳。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 不支持上傳文件,所以就復制過來了。作者信息什么的都沒刪。對前端基本屬于一竅不通,所以沒有任何修改,反正用著沒問題就...
    全棧在路上閱讀 1,999評論 0 2
  • 不經意間,陳暉和夏天已經一起走過了十個年頭。高一那年,陳暉向夏天表白的畫面還清晰如昨。夏天是典型的乖乖女,面對陳暉...
    aishe閱讀 270評論 0 0
  • 對人世最絕望的控訴,就是你看著身邊的親人,還在生命招展的鮮活的時候,卻溘然長逝,讓你來不及領悟。
    森小閱讀 304評論 0 0
  • 1.今天天氣晴好,心情也好。 2.中午吃到嫂子給下的餃子,非常美味。 3.開車時發現老公給洗了車,加滿油,心里滿滿...
    華麗的美麗麗閱讀 191評論 1 3