React Native 支付寶更多頁面的實現

支付寶效果圖
  • 實現的效果:
    • 頁面向上滑動有吸頂的效果。
    • 便民服務,查詢服務 ...所在的 tabBar 與頁面下方對應的模塊內容一一對應。
    • 頁面滑動對應的 tabBartitle 居中偏移。
    • 編輯模式下:數據的 新增刪除

  • 效果展示:
還原支付寶效果圖

  • 實現吸頂的代碼(噗~感覺這個沒啥好寫 ...... 很尷尬):
    —— 段子手是運用 ScrollView ==> onScroll ==> e.nativeEvent.contentOffset.y 獲取頁面滑動的當前坐標;
    然后和頁面初次加載時 tabBar ==> onLayout ==> e.nativeEvent.layout.y 的值做比較獲取狀態。

     this.moveHeight = e.nativeEvent.contentOffset.y;
    
      if (this.moveHeight >= typeY) {
          this.setState({
               positionType: true,
          })
      } else {
          this.setState({
               positionType: false
           })
      }
    
  • 實現滑動模塊和標題一一對應代碼(這個有點篇幅):

    • 先說:點擊標題,對應的模塊自動置頂

      1. 首先初次加載時,我把 tabBar 下的各個模塊的 y 坐標都存下來, push 到數組中,并且對應的模塊下標 i 也存起來。
                  //保存 模塊的下標和 y 軸
                  let params = {
                      key: i,
                      tmpY: e.nativeEvent.layout.y
                  };
      
                  //數組去重(判斷命名的變量不建議用 type)
                  let typePis = tmpArr.some(v => v.key == params.key);
      
                  if (typePis) {
                      tmpArr.filter((v) => {
                          return v.key != params.key
                      })
                  } else {
                      tmpArr.push(params);
                  }
                  break;
      
      • 去重是因為吸頂的原因,會導致頁面的 onLayout 重新加載。
      • 這里還有一個小坑:模塊返回的坐標順序并不是按頁面展示順序來的。
      1. 拿到模塊的坐標數組 tmpArr 后,根據點擊 tabBar 的下標 indextmpArrkey 去匹配,一致時取出 tmpArr 對應的 tmpY 值,根據該值去計算頁面的偏移量。
         let y;
         tmpArr.map((v, i) => {
             if (v.key == index) {
                 y = v.tmpY;
             }
         });
      
         // 頁面中模塊的 y 軸移動 (typeY: tabBar 的坐標,
         // y:模塊的坐標,頭部    固定位置的搜索: autoHeight(45))
         this.refs.refMoveHeight.scrollTo({y: typeY + y - autoHeight(45)});
      
      1. 在點擊 tabBar 最后的標題,模塊的內容的高度不夠去偏移到置頂的位置的處理(根據已有的內容高度,去自適應填充空白區域)。
        • 保存最后一個模塊的 y
          // i:就是點擊標題的下標;typeList:是標題數組。
          if (i == typeList.length - 1) {
                this.setState({
                     listCellHeight: e.nativeEvent.layout.y
               })
          }
        
        • 在最后的一個模塊后面添加一個高度為 1View ,然后保存它的坐標 footHeight = e.nativeEvent.layout.y,接著再設置一個填充空白頁面 View,高度為 屏幕全高 - (footHeight - listCellHeight - typeY + autoHeight(45))
    • 再說下滑動模塊和 tabBar 的標題對應

      • 獲取手勢在屏幕上的滑動方向,把 <= 當前頁面滑動高度的模塊都塞選出來,然后取出最大的下標,然后和 tabBar 中的標題下標去匹配,一致則標題顯示高亮。
        let maxValue = 0;
        if (this.moveHeight > e.nativeEvent.contentOffset.y) {
            tmpArr.map((v, i) => {
                  if (e.nativeEvent.contentOffset.y >= (typeY + v.tmpY -     autoHeight(45))) {
        
                      // console.log(i + '下下下');
                      //因為模塊高度的下標不是按在頁面中的位置返回的,所以和 tabBar 的下標并不能一一對應,所以要塞選出下標的最大值
                      if (tmpArr[i].key >= maxValue) {
                          maxValue = tmpArr[i].key
                      }
        
                      // console.log(maxValue + 'maxValue下=========');
                  }
              }
          )
        } else {
          tmpArr.map((v, i) => {
                  if (e.nativeEvent.contentOffset.y >= (typeY + v.tmpY - autoHeight(45))) {
                      // console.log(i + '下下下');
                      //因為模塊高度的下標不是按在頁面中的位置返回的,所以和 tabBar 的下標并不能一一對應,所以要塞選出下標的最大值
                      if (tmpArr[i].key >= maxValue) {
                          maxValue = tmpArr[i].key
                      }
        
                      // console.log(maxValue + 'maxValue上======');
                  }
              }
          )
        }
        
        this.state.currentIndex = maxValue;
        
  • 實現頁面滑動對應的 tabBartitle 居中偏移代碼

    • 獲取屏幕 width 的寬度的一半。

        let widthHalf = 屏幕寬度 / 2;
      
    • 獲取到 tabBar 中各個標題位置的 width,同時保存對應的挑剔位置的下標,數組為 tmpArrX

             //保存 tabBar 的下標和 width
              let param = {
                  index: i,
                  tmpX: e.nativeEvent.layout.width
              };
      
              //數組去重
              let tmpType = tmpArrX.some(v => v.index == param.index);
      
              if (tmpType) {
                  tmpArrX.filter((v) => {
                      return v.index != param.index
                  })
              } else {
                  tmpArrX.push(param);
              }
      
    • 拿到點擊 tabBar 的下標,獲取到這個下標之前的模塊 width

       //獲取 index 之前模塊 width
      let widthX = 0;
      //獲取選中的 width
      let indexWidth;
      
      for (let i = 0; i <= index; i++) {
          // console.log('i===' + i);
          tmpArrX.map((item, key) => {
              if (item.index == i) {
                  widthX += item.tmpX
              }
      
              if (index == item.index) {
                  indexWidth = item.tmpX
              }
          })
      }
      
    • 根據判斷出來是不是 tabBar 中最后一個標題,然后拿 widthXindexWidth 然后去判斷編寫邏輯。

      let moveX;
      // index*20 是每個模塊的空隙 20
      if (widthX + index * 20 > widthHalf && index != tmpArrX.length - 1) {
          moveX = (widthX - widthHalf + indexWidth) / 2
      } else if (index == tmpArrX.length - 1) {
          //index + 1 :間距比個數多一個; 10 :marGinLeft = 10
          moveX = (index + 1) * 20 + widthX - SCREEN_WIDTH + 10
      }
      
      // 頁面中  tabBar 的 x 軸移動
      this.refs.moveX.scrollTo({x: autoWidth(moveX)})
      
  • 實現編輯模式下:數據的 新增刪除 的代碼(段子手快餓死了,寫不動注釋了 ......)

      //數組的加減
      _addOrDelete(keyType, data, i) {
          console.log('data===' + JSON.stringify(data) + '===' + i);
          //更改 severListType 的數據
          const {severListType, headList}=this.state;
          let tmpType = severListType.some(item => item.id === data.id);
    
          switch (keyType) {
              case 1:
                  // severListType.splice(i, 1);
                  if (tmpType) {
                      // tmpSeverId :用來存儲選中的截取的 id
                      this.state.severListType = severListType.filter((item) => {
                          return item.id != data.id
                      });
                      //編輯模式下,是否為已有模塊, true:加號,false:減號
                      data.select = !tmpType;
                  }
                  break;
              case 2:
              case 3:
                  if (!tmpType) {
                      if (severListType.length > 10) {
                          RootToast.show('首頁最多添加 11 個應用')
                      } else {
                          severListType.push(data)
                      }
                  } else {
                      //獲取輸入的值和在另一個模塊數組中的下標,然后刪除
                      console.log('data1===' + JSON.stringify(data) + '===ss' + i);
                      console.log('severListType===' + JSON.stringify(severListType));
                      this.state.severListType = severListType.filter((item) => {
                          return item.id != data.id
                      });
                      data.select = !tmpType;
                  }
                  break;
          }
          //刷新數據
          this.setState({
              severListType: this.state.severListType
          }, () => {
              // console.log('severListType===' + JSON.stringify(this.state.severListType));
    
          });
      }
    

TIP:
  • 吸頂效果在 android 低配中會出現卡頓現象。
  • 頁面滑動對應的 tabBartitle 居中偏移(我的寫法還是有問題的,還有就是模塊滑動時 tabBar 對應居中也會有卡頓)。
新增手勢
  • TouchableHighlight 屬性
名稱 屬性 注釋
accessibilityComponentType View.AccessibilityComponentType 設置可訪問的組件類型
accessibilityTraits View.AccessibilityTraits 設置訪問特征
accessible bool 設置當前組件是否可以訪問
delayLongPress View.AccessibilityTraits 設置當前組件是否可以訪問
accessibilityTraits number 設置延遲的時間,單位為毫秒。從 onPressIn 方法開始,到 onLongPress 被調用這一段時間
delayPressIn number 設置延遲的時間,單位為毫秒,從用戶觸摸控件開始到 onPressIn 被調用這一段時間
delayPressOut number 設置延遲的時間,單位為毫秒,從用戶觸摸事件釋放開始到 onPressOut 被調用這一段時間
onLayout function 當組件加載或者改組件的布局發生變化的時候調用。調用傳入的參數為 {nativeEvent:{layout:{x,y,width,height}}}
onLongPress function 當用戶長時間按壓組件(長按效果)的時候調用該方法
onPress function 當用戶點擊的時候調用(觸摸結束)。 但是如果事件被取消了就不會調用。(例如:當前被滑動事件所替代)
onPressIn function 用戶開始觸摸組件回調方法
onPressOut function 用戶完成觸摸組件之后回調方法
pressRetentionOffset {top: ,left: ,bottom: ,right: } 該設置當視圖滾動禁用的情況下,可以定義當手指距離組件的距離。當大于該距離該組件會失去響應。當少于該距離的時候,該組件會重新進行響應。確保你傳入一個常量來減少內存分配。

段子手不才,歡迎來補充
  • 由于篇幅原因,具體想要知道整個效果圖的代碼或者有補充地方的可以加技術群:631730313
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,878評論 0 38
  • 在C語言中,五種基本數據類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,392評論 0 2
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,046評論 0 2
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,857評論 0 6
  • “孩子,不要怕,叔叔帶你去玩一會 兒”老男人猥瑣的聲音傳入女孩的耳朵里, 女孩害怕的不知所措。 不一會兒,車停下了...
    盈盈子清閱讀 627評論 1 3