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;
            }
          }

        }

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

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

推荐阅读更多精彩内容

  • 不支持上传文件,所以就复制过来了。作者信息什么的都没删。对前端基本属于一窍不通,所以没有任何修改,反正用着没问题就...
    全栈在路上阅读 1,950评论 0 2
  • 不经意间,陈晖和夏天已经一起走过了十个年头。高一那年,陈晖向夏天表白的画面还清晰如昨。夏天是典型的乖乖女,面对陈晖...
    aishe阅读 218评论 0 0
  • 对人世最绝望的控诉,就是你看着身边的亲人,还在生命招展的鲜活的时候,却溘然长逝,让你来不及领悟。
    森小阅读 295评论 0 0
  • 1.今天天气晴好,心情也好。 2.中午吃到嫂子给下的饺子,非常美味。 3.开车时发现老公给洗了车,加满油,心里满满...
    华丽的美丽丽阅读 179评论 1 3