React Native 響應式布局實踐
【導語】React Native 的樣式和布局部分采用了前端布局上所使用 CSS 的子集。利用 CSS 里的 Flexbox 進行布局和原生平臺的布局方式有比較大的區別。本文集中講解 Flexbox 的原理,以及 Platform、Dimensions API的用法。并結合具體例子,介紹如何具體實現跨平臺響應式的布局。
和原生的 iOS 以及 Android 的開發方式不同,React Native 的布局采用了 Web 前端布局所常用的 Flexbox 模型。這個模型的特點在于能夠在按照固定尺寸布局之后,靈活地分配屏幕上的剩余空間,利用這個模型可以輕松實現許多應用中所需要的布局設計。
開發人員掌握了 Flexbox 模型即可隨心所欲地對屏幕上的 UI 元素進行布局,再結合 React Native 所提供的獲取屏幕信息、平臺信息的 API,就可以進階實現響應式布局。本文就實現響應式布局的三大支柱——Flex box 模型,獲取屏幕信息的 Dimensions API,獲取平臺信息的 Platform API 進行介紹,最后結合例子來實踐響應式布局。
Flexbox 模型
React Native 在布局和樣式上極大程度上借鑒了 Web 前端所使用的 CSS 規格。CSS 布局方面的算法主要由三個部分組成,首先是解決單個 UI 元素的尺寸問題的 Box 模型(具體由 width,height,padding,border,margin 屬性構成),其次是解決 UI 元素相對位置的 Position 模型(具體由 position,top,right,bottom,left 屬性構成),最后是解決剩余空間分配問題的 Flexbox 模型。
三者當中,前兩者解決相對局部的布局問題,概念也相對易懂,本文中將不再多做說明。Flexbox 模型則相對復雜,會牽扯一些獨特的概念,下圖展示了 Flexbox 算法中所涉及的用語。
“容器”指定了進行 Flexbox 模型布局的范圍,任意的某個單個 UI 元素都可以當作容器,Flexbox 模型的算法不會改變該元素以及其外部元素的布局,只影響其直系子輩元素的布局。
“項目”則是 Flexbox 所直接作用的部分,通常是容器下面的直系子輩元素。
“主軸”定義了 Flexbox 進行布局的方向,在 React Native 中默認為縱向(從上往下),Flexbox 模型的算法將會沿這個方向依序對項目進行布局。
“交叉軸”為主軸所垂直的軸,在 React Native 中默認為橫向(從左往右),開發人員可以指定每個項目在交叉軸上如何布局。
在了解了 Flexbox 的主要用語之后,就可以試著理解一下 Flexbox 模型的算法。下圖輔以具體的布局例子進行圖解,算法的步驟如下:
1. 首先在主軸上按各項目默認尺寸(通常按 Box 模型屬性或是 flexBasis 屬性指定)進行布局;
2. 如果主軸方向上需要換行,則依據 flexWrap 的值,處理換行;
3. 逐行計算主軸上是否有剩余長度;
4. 如果該行剩余長度>0(有空白),則根據 flexGrow 系數伸長各個項目,盡量確保填滿空白;
5. 如果該行剩余長度<0(有溢出),則根據 flexShrink 系數縮短各個項目,盡量確保沒有溢出;
6. 依照 justifyContent 的值,處理各個項目在主軸上的對齊;
7. 依照 alignItems 以及 alignSelf 的值,處理各個項目在交叉軸上的對齊。
理解了算法的步驟之后,最后具體介紹一下構成 Flexbox 的各類屬性,這里僅簡要概括一下每個屬性的作用,更加詳細的說明則可以參考 React Native 或是 CSS 的相關文檔。
首先是可以在容器上指定的屬性,它們會作用在所有項目的布局上:
- flexDirection 控制主軸的方向,可以選擇縱向的從上往下(column),從下往上(column-reverse),或是橫向的從左往右(row),從右往左(row-reverse)
- flexWarp 控制項目換行的方式,可以選擇換行(wrap)或是強制不換行(nowrap)
- justifyContent 控制項目在主軸上的對齊方式,可以選擇起始位置對齊(flex-start),終止位置對齊(flex-end),居中對齊(center),兩側貼邊等間隔對齊(space-between),兩側非貼邊等間隔對齊(space-around)
- alignItems 控制項目在交叉軸上的對齊方式,可以選擇起始位置對齊(flex-start),終止位置對齊(flex-end),居中對齊(center),拉伸對齊(strecth)
其次是可以在項目上個別指定的屬性,它們只會作用在被指定的元素上,并且優先于級容器上所指定的內容:
- flexBasis 設置項目在主軸上的默認尺寸
- flexGrow 設置項目在需要伸長時,所伸長的比重
- flexShrink 設置項目在需要縮短時,所縮短的比重
- flex 方便同時設置 flexBasis,flexGrow,flexShrink 的屬性,按照所指定的值分 3 種情況
- N > 0 時,設置主軸上所占長度,效果上相當于 flexGrow: N, flexShrink: 1
- N = 0 時,根據 width/height 來設置尺寸,效果上相當于 flexGrow: 0, flexShrink: 0
- N = -1 時,根據 width/height 來設置尺寸,主軸空間不足時進行縮短,效果上相當于 flexGrow: 0, flexShrink: 1
- alignSelf 設置項目在交叉軸上的對齊方式,和 alignItems 一樣,可以選擇起始位置對齊(flex-start),終止位置對齊(flex-end),居中對齊(center),拉伸對齊(strecth)
flexBox 模型的容器可以指定在任意元素上,可以無限制地進行嵌套,這極大地增強了布局的自由性。結合 Box 模型和 Position 模型可以滿足應用開發過程中大部分的布局需求。
同時 flexBox 模型可以靈活分配容器的剩余空間的特性,使其在屏幕大小、內容尺寸不定的情況下具有相當強的適應性,開發人員無需事先計算并指定各種屏幕大小情況下的固定尺寸,往往是實現各式響應式布局問題的最佳解決方案。
獲取屏幕信息
Dimensions API 是 React Native 提供的獲取屏幕信息用的 API。開發人員可以通過調用 Dimensions.get()方法取得一個包含屏幕長寬信息的對象,來把握當前用戶的設備的屏幕大小,并且以此來簡易推測用戶是否處于橫屏狀態。
用戶使用應用的過程中,由于設備的旋轉方向變化或者多應用分屏等情況,屏幕信息可能隨時會產生變化。作為可以對應各種變化情況的最佳實踐,推薦在組件的 onLayout 的回調中使用 Dimensions.get()方法來獲取屏幕信息。
下面的例子展示了如何獲取屏幕信息,保存在組件的 state 中,并只在橫屏的時候顯示組件。
class DimensionsDemo extends Component { constructor(props) { super(props); this.state = { ...Dimensions.get('window') }; } render() { return ( <View onLayout={ () => this.setState({ ...Dimensions.get('window') }) }> { this.state.width >= this.state.height && <LandscapeWarning /> } </View> ); } };
獲取平臺信息
Platform API 是 React Native 提供的獲取平臺信息用的 API,同時也提供了一些方法來方便開發人員對各個平臺進行分支處理。
Platform.OS 和 Platform.Version 屬性分別提供了當前設備的 OS 信息以及 OS 版本信息。開發人員可以根據相應的值,對 UI 組件進行分支處理。下面的例子在用戶使用 iOS 的情況下會顯示組件,而在 Android 的情況下則會顯示組件。
class PlatformDemo extends Component { render() { return ( <View> { Platform.OS === 'ios' && <NavigatorIOS /> } { Platform.OS === 'android' && <NavigatorAndroid /> } </View> ); } }
另外通過利用 Platform.select()方式可以以更加精簡的方式來同樣實現上面例子中的分支處理。
class PlatformDemo extends Component { render() { return ( <View> { Platform.select({ ios: <NavigatorIOS />, android: <NavigatorAndroid /> }) } </View> ); } }
響應式布局實踐
最后來實際結合以上的知識,用單套代碼實現一個簡單的響應式布局的例子。這里以 iOS 的郵件應用主屏為例,在手機屏幕上該屏幕只顯示郵件列表,但在平板上則同時顯示列表窗格和郵件的詳細窗格,工具欄的位置也隨之調整。
手機屏幕單欄布局(屏幕寬度<768px)/平板屏幕分欄布局(屏幕寬度≧768px)
以下節選的代碼展示了如何利用同一套代碼,根據不同的屏幕尺寸情況靈活改變布局方式,實現響應式布局:
class MailAppExample extends Component { constructor(props) { super(props); this.state = { ...Dimensions.get('window') }; } render() { const isTabletLayout = this.state.width >= 768; return ( <View style={ { flex: 1, alignItems: 'stretch', flexDirection: isTabletLayout ? 'row' : 'column' } } onLayout={ () => this.setState({ ...Dimensions.get('window') }) }> <View style={ [ { flex: 1, alignItems: 'stretch' }, isTabletLayout && { flex: 0, width: 320 } ] }> <MailAppNavigator /> <MailList /> { !isTabletLayout && <Toolbar /> } </View> { isTabletLayout && ( <View style={ { flex: 2, alignItems: 'stretch', borderLeftWidth: 1 } }> <Toolbar isTabletLayout /> <MailDetail /> </View> ) } </View> ); } }
總結
開發人員只要熟悉 Flexbox 模型,再加以活用 Dimensions 以及 Platform API,即可實現一套代碼對應多平臺多設備的目的。相比按 Android 和 iOS 分別開發響應式布局,可以節省大量的時間,同時學習成本相對較低。另外相比開發 Web 應用,又可以提供更好的純生用戶體驗,這是 React Native 博得大量人氣的原因之一。如果想要進一步了解文中所介紹的技術細節,推薦可以參考閱讀以下鏈接,利用范例代碼實際操練加深理解。
來自:http://geek.csdn.net/news/detail/190557