react-native布局與組件

  • 2019 年 10 月 4 日
  • 筆記

RN布局與樣式

布局

一款好的App離不開漂亮的布局,RN中的布局方式采⽤的是FlexBox(彈性布局) 。

經典資料參考:阮一峰flex 布局語法篇:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

FlexBox提供了在不同尺⼨設備上都能保持一致的布局⽅式 。在移動端,在這裡不必擔心兼容問題。

但是RN的flex布局和真正的css還是有所差別:

  • flexDirection:RN中默認是flexDirection:』column』,Web Css中默認是 flex-direction:』row』,也就是說RN的flex默認就是打豎的。
  • alignItems:RN中默認: 『stretch』,在Web Css中默認 flex-start』,也就是說RN的flex是強制等高的。
  • RN的flex屬性,只能接收一個值
  • 不支援的屬性: align-content flex-basis order flex-flow flex-grow flex-shrink (平時也用得少)

樣式

在移動端開發中,是沒有像素概念的。所有量規無單位,表示的是是1個邏輯像素

<View style={{width:100,height:100,margin:10,backgroundColor:'gray'}}>    <Text style={{fontSize:16,margin:20}}>尺⼨寸</Text>  </View>  

上述程式碼,運⾏在Android上時,View的⻓寬被解釋成:100dp 100dp,字體被解釋成16sp,運⾏於 ios上時尺⼨單位被解釋成pt,這些單位確保了布局在任何不同DPI的手機螢幕上,顯示效果一致。

關於更詳細的換算關係,查閱:http://www.woshipm.com/pmd/176328.html

寫樣式除了可以用傳統react的css in js方式,也可以這麼寫:

<text style={[styles.aaa,{color:'red'}]}></text>  

所有文本的樣式應該直接加在text上面,如果你在view裡面寫,就不會生效了。

{/* 錯誤的實例:不生效 */}  <view style={[styles.aaa,{color:'red'}]}></view>  

組件

react native的魅力在於能夠使用系統原生的組件。他們和html標籤相似,又有不少區別。

如果寫過微信小程式,或許理解起來會比較快。因為前者」借用了」這些組件概念。

簡單認知的話,組件和UI框架差不多,用什麼引什麼。以下對某些重要組件進行介紹。

view:萬能容器

視圖布局容器,可以理解為原生開發中的萬能容器。可嵌套多層,支援flex。

一個組件通常是返回一個view包裹的,如果你想返回兩個,可以使用[<View>...</View>,<View>...</View>]的形式返回多個兄弟組件。

SafeAreaView:安全區

SafeAreaView 的目的是在一個「安全」的可視區域內渲染內容。具體來說就是因為目前有 iPhone X 這樣的帶有「劉海」的全螢幕設備,所以需要避免內容渲染到不可⻅見的「劉海」範圍內。本組件目前僅⽀持 iOS 設備以及 iOS 11 或更高版本。

SafeAreaView 會自動根據系統的各種導航欄、工具欄等預留出空間來渲染內部內容。更重要的 是,它還會考慮到設備螢幕的局限,比如螢幕四周的圓⻆角或是頂部中間不可顯示的「非安全」區域。

<SafeAreaView style={{backgroundColor:'red'}}></SafeAreaView>  

webview:載入網頁容器(即將被移除)

創建一個原生的webview,用於載入網頁.我們可結合safeAreaView使用:

      <SafeAreaView style={{flex:1}}>          <WebView          source={{uri: 'https://github.com/facebook/react-native'}}          style={{marginTop: 20}}          />        </SafeAreaView>  

在官方最新版本需要安裝react-native-webview

需要明確的認知是:webview是有可能存在跨域問題的。

Text:文本容器

主要用於顯示文本,具有響應之特性(表現為觸摸時是否支援高亮)。同時支援多層嵌套,因此樣式可繼承(內部繼承外部)。但是,不同於web css,字體樣式(font color等)只有在text組件上才能起效——所以字體樣式的實現只能依賴於text組件。

在Text內部的元素不再使⽤flexbox布局,而是采⽤用文本布局。這意味著內部的元素不再是】一個個矩 形,而可能會在行末進⾏摺疊。

                <Text            ellipsizeMode={"tail"} //這個屬性通常和下⾯面的 numberOfLines 屬性配合使⽤用,⽂文本超出 numberOfLines設定的⾏行行數時,截取⽅方式:head- 從⽂文本內容頭部截取顯示省略略號。例例如: "...efg",middle - 在⽂文本內容中間截取顯示省略略號。例如: "ab...yz",tail - 從⽂文本內容尾 部截取顯示省略略號。例例如: "abcd...",clip - 不不顯示省略略號,直接從尾部截斷。            numberOfLines={1} //配合ellipsizeMode設置⾏行行數            onPress={...} //點擊事件 selectable={true}//決定⽤用戶是否可以⻓長按選擇⽂文本,以便便複製和粘貼。          >123          </Text>  

Image:圖片容器

類似img元素。但支援更多但來源,比如網路圖片,本機磁碟圖片,照相機圖片等。

下⾯的例⼦分別演示了如何顯示從本地快取、網路乃至base64拉取圖片。

{/* 顯示本地圖 */}  <Image    source={require('./img/favicon.png')}  />    {/* 顯示網路圖 */}  <Image      style={{width: 50, height: 50}}      //網路和 base64 數據的圖⽚需要⼿動指定尺⼨    source={{uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png'}}  />    {/* 顯示base64圖 */}  <Image  style={{width: 66, height: 58}} //⽹網路和 base64 數據的圖⽚片需要⼿手動指定尺⼨寸  source={{uri:  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='}}  />  

自從ios9.0之後,官方就一直推薦使用https協議的網路圖片。

ImageBackground 背景圖

用法和Image差不多:

{/* 顯示網路圖 */}  <ImageBackground      style={{width: 50%, height: 50%}}      //網路和 base64 數據的圖⽚需要⼿動指定尺⼨    source={{uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png'}}>      <Text>...</Text>  </ImageBackground>  

Button:按鈕

一個簡單的跨平台的按鈕組件。可以進行一些簡單的訂製。如圖,前者為Android,後者為ios。

<Button    onPress={onPressLearnMore} //⽤戶點擊此按鈕時所調用的處理理函數    title="Learn More" //按鈕內顯示的⽂文本    color="#841584" //文本的顏⾊(iOS),或是按鈕的背景⾊(Android)    disabled={false} //按鈕是否可以點擊    accessibilityLabel="Learn more about this purple button"    //用於給殘障人⼠顯示的文本(比如讀屏應⽤可能會讀取這一內容)  />  

ActivityIndicator loading的小菊花

顯示一個loading提示符Android設備時一個Google式半圓環,在ios設備上則顯示一朵小菊花。

        <ActivityIndicator            size="large" //指示器器的大⼩,默認為'small'[enum('small', 'large'), number]。⽬前只能在 Android 上設定具體的數值            animating={true} //是否要顯示指示器動畫,默認為 true 表示顯示,false 則隱藏。            hidesWhenStopped={false} //在animating為 false 的時候,是否要隱藏指示器(默認為 true)。如果animating和hidesWhenStopped都為 false,則顯示⼀一個靜⽌止的指示器。            color="#0000ff" />  

ListView:列表

這個組件的性能比較差,尤其是當有大量的數據需要展示的時候,ListView對記憶體的占⽤用較多,常出現丟幀卡頓現象。

ListView底層實現,渲染組件Item是全量渲染,而且沒有復用機制,當渲染較⼤數據量時,會不可避免地卡頓。

第⼀次打開與切換Tab時會出現卡頓或白屏的情況,比如ListView中有100個Item,只能等這 100條Item都渲染完成,ListView中的內容才會展示滑動列表時會出現卡頓。

未來有很⼤大可能性會被移除 。

VirtualizedList: 虛擬列表

替代ListView的主要解決方案就是VirtualizedList。RN0.43版本中引⼊了了FlatList,SectionList和VirtualizedList,其中VirtualizedList是FlatList和SectionList的底層實現。

FlatList 和 SectionList 的底層實現:VirtualizedList通過維護一個有限的渲染窗⼝(其中包含可⻅的元素),並將渲染窗⼝之外的元素全部用合適的定⻓空⽩空間代替的⽅式,極⼤的改善了記憶體使⽤,提⾼了大量數據情況下的渲染性能。這個渲染窗⼝能響應滾動行為,元素離可視區越遠優先順序越低,越近優先順序越高,當用戶滑動速度過快時,會出現短暫空⽩的情況。

<FlatList      data={[{key: 'a'}, {key: 'b'}]}      renderItem={({item}) => <Text>{item.key}</Text>}  />  

缺點:

(1)為了優化記憶體占⽤同時保持滑動的流暢,列表內容會在螢幕外非同步繪製。這意味著如果用戶滑動的速度超過渲染的速度,則會先看到空白的內容。

(2)不支援分組列列表

扯了那麼多理論,如果列表寫不了想說自己懂rn是很扯的。是時候開始寫一個了。

需求:列表的下拉刷新和上划動載入

看今日頭條等新聞列表類app時,都需要用到。

import React,{Component} from 'react';  import {View,Text,StyleSheet,Button,FlatList,RefreshControl} from 'react-native';    const listData=Array(20).fill(1).map((x,i)=>{      return {          key:i,          value:`列表項${i+1}`      }  });    export default class HotPage extends Component{      static navigationOptions=({navigation})=>{          return {              headerTitle:navigation.getParam('title')          }      }        constructor(props){          super(props);          this.state={              listData,              isLoading:false          }      }        loadData(refresh){          if(refresh){              this.setState({                  isLoading:refresh              });          }              setTimeout(()=>{              let _listData=[];              if(refresh){                  for(let i=this.state.listData.length-1;i>=0;i--){                      _listData.push(this.state.listData[i])                  }              }else{                  _listData=this.state.listData.concat(listData)              }                this.setState({                  listData:_listData,                  isLoading:false              })          },2000)      }        render(){            return (            <View style={styles.container}>                <FlatList                  data={this.state.listData}                  renderItem={({item}) =>                      <View style={{                          justifyContent:'center',                          alignItems:'center',                          flex:1,                          height:60,                          backgroundColor:'#ccc'                          }}>                      <Text>{item.value}</Text>                      </View>                      }                  // 分割線:不會出現在第一行之前,也不會出現在第一行之後                  ItemSeparatorComponent={()=>{                      return <View style={{height:2,backgroundColor:'#eee'}}/>                  }}                  // 列表為空時渲染組件                  ListEmptyComponent={()=>{                      return <Text style={{textAlign:'center'}}>空空如也</Text>                  }}                  // 頂部組件                  // ListHeaderComponent={()=>{                    // }}                  // 尾部組件                  ListFooterComponent={()=>{                      return <Text>我也是有底線的</Text>                  }}                    // 刷新相關:                  // 如果設置了此選項,則會在列表頭部增加一個標準的RefreshControl控制項,                  // 同時也需要正確設置refreshing屬性                  refreshControl={                      <RefreshControl                        title='loading'                        colors={['red']}                        // 如果設置該屬性為true,列表將出現一個正在載入的符號                        refreshing={this.state.isLoading}                        onRefresh={()=>{                            this.loadData(true)                        }}                        tintColor={'orange'}                      />                  }                    Threshold='0.4'                  // 當列表滾動到地步距離不足Threshold時調用                  onEndReached={()=>{                      this.loadData();                  }}                />            </View>          )        }  }    const styles=StyleSheet.create({      container:{          flex:1,          width:'100%',          backgroundColor:'#f5f5f5'      },      text:{          fontSize:26,          marginBottom:20      }  })  

其它組件(Switch/Modal)

可自行查閱api。