遲早您的專案中會需要一個輪播。也許您想要顯示圖像列表,也許是您的應用程式的簡介,或者您可能希望您的應用程式有幾個可滑動的螢幕。無論您的用例是什麼,本文都可能對您有所幫助。

讓我們開始吧。我們的輪播的基礎將是一個簡單的FlatList元件。原因很簡單 - 它基於ScrollView元件,使我們能夠滑動幻燈片,此外,它還實現了VirtualizedList ,當幻燈片中有大量圖像或性能較高的 UI 元素時,我們可以使用 VirtualizedList 進行優化。

首先,讓我們建立一些虛擬資料。我們將使用Lorem Picsum獲取隨機圖像,並為輪播的 30 張幻燈片建立隨機資料。

const { width: windowWidth, height: windowHeight } = Dimensions.get("window");

const slideList = Array.from({ length: 30 }).map((_, i) => {
  return {
    id: i,
    image: `https://picsum.photos/1440/2842?random=${i}`,
    title: `This is the title! ${i + 1}`,
    subtitle: `This is the subtitle ${i + 1}!`,
  };
});

請注意,我們必須新增查詢參數random=${i}才能為每張投影片取得隨機影像。否則,React Native 將快取第一個圖像並使用它來代替輪播中的每個圖像。

接下來,我們將建立一個 FlatList 並將我們的slideList傳遞給data屬性。我們還將向其傳遞帶有flex: 1 style屬性,以便它覆蓋整個螢幕。最後,我們必須定義投影片的外觀。這是使用renderItem屬性完成的。

我們將建立一個Slide元件並在renderItem函數中使用它。

function Slide({ data }) {
  return (
    <View
      style={{
        height: windowHeight,
        width: windowWidth,
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <Image
        source={{ uri: data.image }}
        style={{ width: windowWidth * 0.9, height: windowHeight * 0.9 }}
      ></Image>
      <Text style={{ fontSize: 24 }}>{data.title}</Text>
      <Text style={{ fontSize: 18 }}>{data.subtitle}</Text>
    </View>
  );
}

function Carousel() {
  return (
    <FlatList
      data={slideList}
      style={{ flex: 1 }}
      renderItem={({ item }) => {
        return <Slide data={item} />;
      }}
    />
  );
};

垂直輪播,無捕捉點

如果我們現在保存,我們將看到幻燈片,但滾動行為不是我們想要的。我們必須讓 ScrollView 捕捉到每張投影片的開頭。實現此目的的最簡單方法是將pagingEnabled={true}屬性新增至 FlatList。

另一件事 - 我們的旋轉木馬目前是垂直的 - 它可以上下滾動。大多數旋轉木馬都是水平的,因此讓我們更改方向,但是請記住,如果您需要建造垂直旋轉木馬,這是可能的,並且只需要進行一些更改。

因此,讓我們將horizontal={true}屬性新增至 FlatList 中,使其左右滾動,同時新增showsHorizontalScrollIndicator={false}屬性來隱藏滾動指示器。

function Carousel() {
  return (
    <FlatList
      data={slideList}
      style={{ flex: 1 }}
      renderItem={({ item }) => {
        return <Slide data={item} />;
      }}
      pagingEnabled
      horizontal
      showsHorizontalScrollIndicator={false}
    />
  );
}

水平轉盤,附捕捉點

這看起來很棒,但我們缺少一件重要的事情。我們可能需要活動幻燈片的索引。例如,如果我們正在為應用程式介紹之旅建立一個輪播,我們可能希望有一個「繼續」按鈕,僅當使用者到達最後一張投影片時才啟用,或者如果我們正在建立一個圖像庫,我們可能會想要顯示一個分頁元件,讓使用者知道它包含多少圖像。

我花了一些時間優化下一部分,所以它可能看起來有點複雜。但別擔心,我會解釋一切。

function Carousel() {
  const [index, setIndex] = useState(0);
  const indexRef = useRef(index);
  indexRef.current = index;
  const onScroll = useCallback((event) => {
    const slideSize = event.nativeEvent.layoutMeasurement.width;
    const index = event.nativeEvent.contentOffset.x / slideSize;
    const roundIndex = Math.round(index);

    const distance = Math.abs(roundIndex - index);

    // Prevent one pixel triggering setIndex in the middle
    // of the transition. With this we have to scroll a bit
    // more to trigger the index change.
    const isNoMansLand = 0.4 < distance;

    if (roundIndex !== indexRef.current && !isNoMansLand) {
      setIndex(roundIndex);
    }
  }, []);

  // Use the index
  useEffect(() => {
    console.warn(index);
  }, [index]);

  return (
    <FlatList
      data={slideList}
      style={{ flex: 1 }}
      renderItem={({ item }) => {
        return <Slide data={item} />;
      }}
      pagingEnabled
      horizontal
      showsHorizontalScrollIndicator={false}
      onScroll={onScroll}
    />
  );
}

首先,我們使用useState定義index - 這將表示輪播中活動幻燈片的索引。然後我們定義indexRef - 一個與索引變數保持同步的參考值 - 當index發生變化時, indexRef.current的值也會改變。

那我們為什麼要這樣做呢?答案在下一行。 onScroll回呼對layoutMeasurementcontentOffset值進行一些計算,以便根據我們滾動的距離計算當前索引。當計算的索引發生變化時,我們希望更新我們的index

問題是 - 如果我們在onScroll中使用index變數來檢查計算的索引是否與當前索引不同,那麼我們必須將index放入useCallback的依賴陣列中。這反過來意味著每次索引更改時, onScroll函數也會更改,並且當它作為 prop 傳遞給 FlatList 時,這意味著清單將重新渲染。

請注意,由於輪播是水平的,因此我們使用layoutMeasurement.widthcontentOffset.x來計算目前索引。如果它是垂直的,我們就必須使用高度和 y 偏移量。

然後是isNoMansLand變數背後的邏輯。當我們將輪播拖曳到兩張投影片中間時,此邏輯可防止滑桿觸發一堆setIndex呼叫。當我們不實現此邏輯時,會發生以下情況 - 當我們處於兩張投影片的中間時,最輕微的移動都會觸發索引變更。這可能會導致大量重新渲染,因此最好避免它。

解決方案與此有關:施密特觸發器

無施密特觸發器

現在,我們到目前為止建立的內容已經很酷了,甚至可能足以滿足您的用例,但是我們的實作存在一些隱藏的效能問題,可能會減慢甚至使您的應用程式崩潰。這是因為它提前渲染了一大堆幻燈片,並且還將以前的幻燈片保留在記憶體中。 FlatList 預設情況下這樣做是為了提高我們快速滾動清單時的感知效能,但在我們的例子中,它會對效能產生負面影響。

我編寫了一個簡單的視覺化程式碼來顯示哪些投影片已安裝,哪些未安裝,此外它還突出顯示了我們目前的索引。底部的綠點代表已安裝的幻燈片,黑點代表未安裝的幻燈片,紅點代表目前活動的幻燈片。

水平輪播,未優化

您可以注意到投影片已提前安裝 10 張投影片。此外,前 10 張投影片永遠不會被卸載。這是 FlatList 預設最佳化的一部分,適用於較長的列表,但不適用於我們的用例。

那麼讓我們實現一些優化。我們將最佳化 props 分組到一個物件中並將它們傳遞給 FlatList 。

  const flatListOptimizationProps = {
    initialNumToRender: 0,
    maxToRenderPerBatch: 1,
    removeClippedSubviews: true,
    scrollEventThrottle: 16,
    windowSize: 2,
    keyExtractor: useCallback(e => e.id, []);
    getItemLayout: useCallback(
      (_, index) => ({
        index,
        length: windowWidth,
        offset: index * windowWidth,
      }),
      []
    ),
  };

  <FlatList
    data={slideList}
    style={{ flex: 1 }}
    renderItem={({ item }) => {
      return <Slide data={item} />;
    }}
    pagingEnabled
    horizontal
    showsHorizontalScrollIndicator={false}
    onScroll={onScroll}
    {...flatListOptimizationProps}
  />

這是所有這些含義的解釋。

initialNumToRender - 這控制從第一張幻燈片開始始終保持渲染狀態的幻燈片數量。這在我們可以以編程方式滾動到頂部的列表中非常有用 - 在這種情況下,我們不想等待前幾張幻燈片渲染,因此 FlatList 始終保持渲染。我們不需要這個功能,所以在這裡輸入0是安全的。

maxToRenderPerBatch - 這控制每批渲染的幻燈片數量。當我們有一個包含許多元素的 FlatList 並且使用者可以快速捲動到尚未載入資料的 FlatList 區域時,這再次很有用。

removeClippedSubviews - 這會刪除 FlatLists 視窗之外的視圖。 Android 預設將此設為 true,我建議在 iOS 上也進行設定。它可以從記憶體中刪除Image元件並節省一些資源。

scrollEventThrottle - 控制當使用者拖曳輪播時觸發多少個滾動事件。設定為 16 表示該事件每 16ms 觸發一次。我們可能可以將其設置為更高的數字,但 16 似乎工作得很好。

windowSize - 這控制有多少投影片安裝在前面,以及有多少投影片安裝在目前索引後面。

它實際上控制 VirtualizedList 用於渲染專案的視窗的寬度 - 視窗內的所有內容都被渲染,而視窗外是空白的。例如,如果我們將此屬性設為 2,則視窗的寬度將是 FlatList 寬度的兩倍。以下視覺化中的粉紅色線表示視窗。

視窗視覺化

對於這個輪播範例,值 2 效果很好,但如果您願意,可以嘗試一下。

keyExtractor - React 使用它進行內部優化。如果不這樣做,新增和刪除投影片可能會中斷。此外,它還刪除了警告,這很好。

getItemLayout - 一種可選的最佳化,如果我們事先知道專案的大小(高度或寬度),則允許跳過動態內容的測量。在我們的例子中,專案的寬度始終是windowWidth 。請注意,如果您希望輪播是垂直的,則必須使用windowHeight

最後,我們可以將樣式移到元件定義之外,並將renderItem函數包裝在useCallback中,以避免 FlatList 不必要的重新渲染。

為了進一步優化我們的輪播,我們還可以做的一件事是將我們的 Slide 元素包裝在React.memo中。

就是這樣!我加入了一個分頁元件並稍微調整了樣式,最終產品如下所示。

最後輪播

您可以自己嘗試:https://snack.expo.io/@hrastnik/carousel


原文出處:https://dev.to/lloyds-digital/let-s-create-a-carousel-in-react-native-4ae2


共有 0 則留言