react-native 觸摸和動畫實例

實現的效果如下

GIF.gif

主要結合了react-native的觸摸和動畫事件,可通過點擊和滑動進行操作。

組件結構

四個滑塊是由父組件map而來,因此只分析一個。以touch部分在左邊為標準,滑塊結構如下

<View style={styles.container}>
  <Animated.View
    style={[
      styles.touch,
      {
        transform: [
          {translateX: this._animatedValue.x}
        ],
      }
    ]}
  >
  </Animated.View>
  <View style={styles.card}>
  </View>
</View>

實質上只是分成了左右結構,左邊的touch較為特殊,因為要實現動畫效果,由動畫組件代替。
想用動畫實現什么屬性進行變化可通過在style中對該屬性的值用Animated.Value()進行初始化。比如想讓touch的寬度用動畫進行變化, 便可初始化寬度為width: new Animated.Value(0).

開始

起初,沒有引入動畫,將touch定位設置為relative,在觸摸事件中監聽其onLayout,通過setState實時刷新位置,代碼實現見這一版
為了性能,為了交互,也為了折騰,引入Animated與PanResponder,讓這兩個好基友一起做點什么。

關于Animated和PanResponder的詳細介紹可查看本文底部講得非常好的參考鏈接,下面說實現。

constructor

  constructor(props) {
    super(props);
    this.state = {
      isTouch: false, // 是否處于點擊狀態
      blockInLeft: true, // touch是否在左側
    }

    this._containerWidth = null; //滑塊組件寬度,可在render內通過onLayout得到
    this._touchBlockWidth = null; //touch寬度
    this._touchTimeStamp = null; // 為不允許雙擊事件發生設置的一個當前點擊時間點

    this._startAnimation = this._startAnimation.bind(this)

    this._animatedDivisionValue = new Animated.Value(0); //初始化動畫值
  }

觸摸事件注冊

  componentWillMount() {
    this._animatedValue = new Animated.ValueXY()
    this._value = {x: 0}
    // 這里為了監聽后面動畫事件中setValue的值
    this._animatedValue.addListener((value) => this._value = value);
    this._panResponder = PanResponder.create({
    // 寫法基本是固定的
      onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
      onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder.bind(this),
      onPanResponderGrant: this._handlePanResponderGrant.bind(this),
      onPanResponderMove: this._handlePanResponderMove.bind(this),
      onPanResponderRelease: this._handlePanResponderEnd.bind(this),
      onPanResponderTerminate: this._handlePanResponderEnd.bind(this),
      });
  }

與動畫的結合

 _handleStartShouldSetPanResponder(e, gestureState){
    // 避免雙擊,與上次點擊在500ms以內時不處理點擊事件
    const tick = new Date().getTime();
    if (tick - this._touchTimeStamp < 500) {
      return false;
    }
    this._touchTimeStamp = tick;
    return true;
  }
  _handleMoveShouldSetPanResponder(e, gestureState){
    // 是否響應移動事件
    return true;
  }
  _handlePanResponderGrant(e, gestureState){
    // touch告訴事件處理器,有人把手放我身上了
    this.setState({
      isTouch: true
    })
    // 歸位
    this._animatedValue.setOffset({x: this._value.x});
    this._animatedValue.setValue({x: 0});
  }

  _handlePanResponderMove(e, gestureState) {
    // 這個方法在手指移動過程中連續調用
    
    // 計算滑塊組件減去touch部分剩余的寬度,可寫在外部
    let canTouchLength = this._containerWidth - this._touchBlockWidth
    
    // 在邊界處不可向己邊滑動。祥看下面endValue介紹
    if ( (this.state.blockInLeft && gestureState.dx > 0 && gestureState.dx < canTouchLength) || (!this.state.blockInLeft && gestureState.dx < 0 && gestureState.dx > -canTouchLength) ) {

      // 動畫跟隨觸摸移動的關鍵,觸摸動畫實現的核心所在。只有在符合上述條件下touch才進行移動。
      this._animatedValue.setValue({x: gestureState.dx})
    }
    
    // 如果不需要邊界處理,也可用event代替setValue
    // Animated.event([
    //     null, {dx: this._animatedValue.x}
    // ])
  }

  _handlePanResponderEnd(e, gestureState){
  // 這個方法在手指離開屏幕時調用

    // 同上,代碼冗余,建議寫在外部
    let canTouchLength = this._containerWidth - this._touchBlockWidth

    // 偏移。moveDistance計算touch的偏移值,判斷其不等于0是為了處理點擊操作
    // gestureState.moveX有移動才會有值,點擊的話值為0
    let moveDistance = gestureState.moveX !== 0 ? gestureState.moveX - gestureState.x0 : 0;
    
    
    // 確定移動方向。moveDistance大于0時代表touch向右移動,不管在左邊還是右邊
    const toRight = moveDistance>0 ? true : false;

    // 取移動距離
    moveDistance = Math.abs(moveDistance)
    
    // 設定個中間值決定滑塊最終移向哪邊。中間值為滑塊寬度減去touch寬度的一半
    const middleValue = canTouchLength / 2

    // endValue為以左邊為原點,最終移動位置相對于左邊的距離。
    // 這里為了實現觸摸時如果沒有將touch移動到最大位置釋放操作,touch最終選擇移動到左邊還是右邊
    // 所以,向右移動touch時,中點以前為0,過了中點endValue為最大值
    // 再向左移動時,中點以前為0(即不移動),過了中點為最大值的反向
    // 這里還有個問題,touch的偏移實現上,是有累加性的。
    // 即比如先向右移動touch到最大值,0 + maxValue,實現這個操作后,滑塊所處的位置maxValue會重設為0
    // 如果想移回來到左邊,就需要0 - maxValue,這便是偏移的累加性
    let endValue = 0

    // 防止touch會被鼠標拽出邊界,給第二個條件加上 this.state.blockInLeft 的判斷      
    if ( (this.state.blockInLeft && moveDistance === 0) || (toRight && this.state.blockInLeft && (moveDistance > middleValue)) ) {
      // touch向右移動時過了中點,或者touch在左邊時,被單擊
      endValue = canTouchLength
      this.setState({
        blockInLeft: false
      })
    } else if ( (!this.state.blockInLeft && moveDistance === 0) || (!toRight && !this.state.blockInLeft && (moveDistance > middleValue)) ) {
      // touch向左移動時過了中點,或者touch在右邊時,被單擊
      endValue = -canTouchLength
      this.setState({
        blockInLeft: true
      })
    }
    
    // touch到邊界時會回彈的動畫開始
    this._startAnimation(endValue);

    this.setState({
      // 這人把手從我身上拿開了
      isTouch:  false
    })

  }

  _startAnimation(endValue) {
    Animated.spring(this._animatedValue, {
      toValue: endValue,
      tension: 80
    }).start(
      () => {
        // 這里本來想在動畫結束后做一些事情,但是發現回調有些問題
        // 可能是回彈的動畫不一定會在touch移動的動畫結束后觸發
      }
    );
  }

這是整個觸摸與動畫結合的實踐。對于touch移動后另一邊的信息也發生移動,可通過監聽touch的blockInLeft,用margin對另一邊信息進行定位,這是我試過最簡單而且沒有副作用的方法。
還想實現的一個功能是,隨著touch從一邊移動到另一邊,底部文字的透明度從1 -> 0 -> 1 這樣切換。
代碼可以精簡,性能還可以優化,先提供一個實現該功能的方法。歡迎拍磚指正,交流學習。

參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容