iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
Modern Web

我不用Expo啦,React Native!系列 第 8

[Day8] 輪播:神奇的上下交錯

今日關鍵字:carousel


接續昨天把資料整理好了
今天要把資料呈現在畫面上
(不過這裡我想的輪播其實跟一般的輪播不同,沒有要自動播放
總之先來找找看)
https://ithelp.ithome.com.tw/upload/images/20200908/201218284Q4FHvk9uv.png
好就第一個了(喂)/images/emoticon/emoticon32.gif
react-native-snap-carousel

yarn add react-native-snap-carousel @types/react-native-snap-carousel

官方的範例效果很不錯,符合我的想像,直接拿來用吧
https://ithelp.ithome.com.tw/upload/images/20200908/20121828FM9dpp4QIC.png
demo

首先要先把輪播寫成元件
不過先看一下基礎範例的寫法

import Carousel from 'react-native-snap-carousel';

export class MyCarousel extends Component {

    _renderItem = ({item, index}) => {
        return (
            <View style={styles.slide}>
                <Text style={styles.title}>{ item.title }</Text>
            </View>
        );
    }

    render () {
        return (
            <Carousel
              ref={(c) => { this._carousel = c; }}
              data={this.state.entries}
              renderItem={this._renderItem}
              sliderWidth={sliderWidth}
              itemWidth={itemWidth}
            />
        );
    }
}

這裡大概可以看出來renderItem這個prop輸入的就是每個子項目的長相
回頭看一下這個範例

export default class App extends Component {
  
  state = {
    index: 0
  }

  constructor(props) {
    super(props);
    this._renderItem = this._renderItem.bind(this)
  }

  _renderItem({ item }) {
    return (
      <View style={styles.itemContainer}>
        <Text style={styles.itemLabel}>{`Item ${item}`}</Text>
      </View>
    );
  }
  
  render() {
    return (
      <View>
        <Carousel
          ref={(c) => this.carousel = c}
          data={DATA}
          renderItem={this._renderItem}
          sliderWidth={SLIDER_WIDTH}
          itemWidth={ITEM_WIDTH}
          containerCustomStyle={styles.carouselContainer}
          inactiveSlideShift={0}
          onSnapToItem={(index) => this.setState({ index })}
          scrollInterpolator={scrollInterpolator}
          slideInterpolatedStyle={animatedStyles}
          useScrollView={true}          
        />
        <Text style={styles.counter}
        >
          {this.state.index}
        </Text>
      </View>
    );
  }
}

大致上看起來差不多...嗯?過場效果寫在哪?

import { scrollInterpolator, animatedStyles } from './utils/animations';

原來在別的檔案裡...

export function scrollInterpolator (index, carouselProps) {
    const range = [1, 0, -1];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;

    return { inputRange, outputRange };
}
export function animatedStyles (index, animatedValue, carouselProps) {
    const translateProp = carouselProps.vertical ? 'translateY' : 'translateX';
    let animatedOpacity = {};
    let animatedTransform = {};

    if (carouselProps.inactiveSlideOpacity < 1) {
        animatedOpacity = {
            opacity: animatedValue.interpolate({
                inputRange: [-1, 0, 1],
                outputRange: [carouselProps.inactiveSlideOpacity, 1, carouselProps.inactiveSlideOpacity]
            })
        };
    }

    if (carouselProps.inactiveSlideScale < 1) {
        animatedTransform = {
            transform: [{
                scale: animatedValue.interpolate({
                    inputRange: [-1, 0, 1],
                    outputRange: [carouselProps.inactiveSlideScale, 1, carouselProps.inactiveSlideScale]
                }),
                [translateProp]: animatedValue.interpolate({
                    inputRange: [-1, 0, 1],
                    outputRange: [
                      TRANSLATE_VALUE * carouselProps.inactiveSlideScale,
                      0,
                      -TRANSLATE_VALUE * carouselProps.inactiveSlideScale]
                }),
            }]
        };
    }

    return {
        ...animatedOpacity,
        ...animatedTransform
    };
}

這裡我是直接把兩個檔案和在一起當個元件用
過程就是複製貼上
然後我想要的子元件很簡單
就是卡片一樣的感覺,上半段是圖片,下半段是字

  const renderItem = ({ item }: RenderItemProps) => (
    <View style={styles.itemContainer}>
      <TouchableOpacity
        activeOpacity={1}
        style={{ height: '100%', width: '100%' }}>
        <Image
          style={styles.itemImg}
          source={{
            uri: item.imgLink
          }}
        />
        <View style={styles.textContainer}>
          <Text style={styles.itemLabel} numberOfLines={1}>
            {item.title}
          </Text>
        </View>
      </TouchableOpacity>
    </View>
  )

這裡簡單談一下元件


基本上React Native內部排版都是預設flex,方向跟web不同是預設column

View

類似於web的div,可以用來包覆區塊

Image

對應web的img,只是src變成了source

Text

專門用來包裹文字的內容,跟web不同的是,React Native內文字不能單獨出現,一定要被Text包裹

TouchableOpacity

button都不button,React Native內的button比較不好用,通常是用這個
顧名思義,按了後透明度會變淡再變回來
可以藉由設定activeOpacity={1}來使得透明度再點擊時不改變


完成了之後就可以把昨天的playListByWeek

import Carousel from '../common/Carousel'
...

  {playListByWeek.weeks.map(
    (playList, i) =>
      playList.length > 0 && (
        <Fragment key={`playList${i * i}`}>
          <Text style={styles.playListTitle}>
            {playListByWeek.weeksTitle[i]}
          </Text>
          <Carousel displayed={playList} />
        </Fragment>
     )
  )}

Fragment

其中Fragment為不想額外增加DOM節點(不想多包一層)時可以使用(React元件)
不用多包一層View,div
如果不使用key時,還可以這麼寫

<> ... </>

條件式渲染

如果想要在條件成立時才渲染畫面
有一種方便的寫法是

{condition && <component/>}

JS中,條件不成立時會return前者,成立時會return後者


這時可以來看成果了
https://ithelp.ithome.com.tw/upload/images/20200908/201218287TTvwccTqU.png
完美...才怪

仔細看右半部
active的區塊是疊在inactive的上方
然而inactive的圖卻壓在active上方
...
...
...WTF?/images/emoticon/emoticon04.gif

這時候看看react-native-snap-carousel的一些demo
好像很多都是撲克牌類型的上下堆疊的例子
就是z-index會遞增或遞減
跟這個demo感覺有些不同
如果強制把active的z-index設為1,其餘為0會怎麼樣?
來更改animatedStyles的return看看

const Carousel = (...) => {
  const [active, setActive] = useState(0)
  ...
  const animatedStyles=(...)=>{
    ...
    return {
      ...animatedOpacity,
      ...animatedTransform,
      zIndex: active === index ? 1 : 0
    }
  }
  ...
  return (
    <CarouselSnap
      ...
      onSnapToItem={(index) => setActive(index)}
    />
  )  

成果
https://ithelp.ithome.com.tw/upload/images/20200908/201218285b2EQxeV45.png
成功啦


今天雖然遇到了奇怪的問題,但還好沒有卡很久
明天預計來談redux

參考:


上一篇
[Day7] 訂定資料格式-2
下一篇
[Day9] Redux:我是大家的無人機
系列文
我不用Expo啦,React Native!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言