在上篇中,筆者分享了部分安裝並除錯React Native應用過程裡的一點經驗,如果還沒有看過的同學請點選《React Native基礎&入門教程:除錯React Native應用的一小步》。
在本篇裡,讓我們一起來了解一下,什麼是Flexbox佈局,以及如何使用。
一、長度的單位
在開始任何佈局之前,讓我們來首先需要知道,在寫React Native元件樣式時,長度的不帶單位的,它表示“與裝置畫素密度無關的邏輯畫素點”。
這個怎麼理解呢?
我們知道,螢幕上一個發光的最小點,對應著一個pixel(畫素)點。
假設下面三個矩形,代表三個螢幕大小一樣的裝置,但是,它們擁有的解析度(resolution)不同:
圖1.相同尺寸的裝置 不同的解析度
圖上的每一個小格子,其實就代表了一個畫素(pixel)。可以看到,一個畫素點的大小,在這個三個物理尺寸一樣但擁有不同解析度的裝置上,是不一樣的。
如果我們以畫素為單位來設定一個介面元素的大小,比如說2px的高度,那麼這2px的長度上面的裝置中就會是下面這個樣子:
圖2.不同解析度下的2px實際高度
它們真實顯示出的長度是不一樣的。
我們想要一種長度單位,在同樣物理尺寸大小的螢幕上(不論解析度誰高誰低,只要物理尺寸大小一樣即可),1個單位的長度所代表的物理尺寸是一樣的。這種單位就應該是獨立於解析度的,把它起一個名字叫做 density-independent pixels,簡稱dp。這其實就是Android系統中所使用的長度單位。
舉例來說,2dp寬,2dp高的內容,在不同解析度但螢幕尺寸一樣的裝置上所顯示出的物理大小是一樣的。(一個題外話:有些Android開發者建議所有可點選的按鈕,寬高都不應該少於48dp。)
圖3. 2dp * 2dp大小的內容 在同樣尺寸的螢幕中所佔據的物理大小一致
Android中字型大小使用另外一個單位,叫做scale independent pixels,簡稱sp。這個單位和dp很類似,不過它通常是用在對字型大小的設定中。通過它設定的字型,可以根據系統字型大小的變化而變化。
pixel與dp存在一個公式:px = dp * (dpi/160)。
dpi表示dot per inch,是每英寸上的畫素點,它也有個自己的計算公式,具體這裡就不展開了。只需要知道我們之所以要使用一個獨立於裝置解析度的單位,主要是為了讓應用在不同解析度的裝置中,看起來一致。
在RN中,同樣也擁有一個類似於dp的長度單位。如果我們想知道自己的螢幕以這種長度的計量下是多少單位,可以通過引入react-native包中的Dimensions拿到,同時還可以檢視本機的畫素比例是多少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Text, View, Dimensions, PixelRatio } from 'react-native'; const { height, width } = Dimensions.get('window'); const pxRatio = PixelRatio.get(); <View style={styles.container}> <Text style={styles.welcome}> {`width: ${width}, height: ${height}`} </Text> <Text style={styles.welcome}> {`pixel radio: ${pxRatio}`} </Text> </View> |
顯示如下:
圖4. 當前手機的螢幕資訊
它反映出,當前手機螢幕的寬度佔據360個單位,高度佔據640個單位。畫素比例是3,實際上這就是一個 1080 * 1920 畫素的手機。其中1080 = width * pixelRadio, 1920 = height * pixelRatio
二、Flexbox佈局
Flexbox佈局,也就是彈性盒模型佈局。也許有Android開發經驗的朋友還對LinearLayout,RelativeLayout,FrameLayout等佈局方法記憶猶新,但是對於更瞭解CSS的Web開發者而言,使用flexbox佈局肯定會讓他感受到更加順手的開發體驗。
RN中的flexbox佈局,其實源於CSS中的flexbox(彈性盒子)佈局規範。其實它在CSS中還處於Last Call Working Draft(最終徵求意見稿)階段,但是主流瀏覽器對它都有了良好的支援。在RN中,幾乎完全借鑑了其中的佈局語義,同時更沒有瀏覽器相容的煩惱,用起來是很方便的。RN中只是把CSS的屬性用camelCase寫法代替連字元寫法。後面還還會看到,預設的flex方向也不同。
理解彈性盒模型佈局,首先要知道四個最基本的概念:Flex Container(容器),Flex Item(項),Flex Direction(方向)和Axis(軸)。
1.Flex Container
就是包裹內容的容器,需要把它的display設定為‘flex’(或者’inline-flex’)。
以下6個屬性設定在容器上。
- alignItems 指定item在側軸上的對齊方式
- alignContent 指定item在多條軸上的對齊方式
- flexDirection 指定主軸方向
- flexWrap 指定item在主軸方向如何換行
- flexFlow flexDirection屬性和flexWrap屬性的簡寫形式
- justifyContent 指定item在主軸上的分佈方式
2.Flex Item
容器做直接包裹的元素。所謂彈性盒佈局,通常想要佈局的東西就是它們。
以下6個屬性設定在專案上。
- alignSelf 每個item可以單獨設定對齊方式 覆蓋Flex Container給設定的alignItems
- order 指定item排列順序 數字越小越靠前
- flexGrow 指定item的拉伸比例
- flexShrink 指定item的壓縮比例
- flexBasis 指定item在分配多餘空間之前,佔主軸的大小
- flex 其實是 flexGrow flexShrink flexBasis的簡寫
3.Flex Direction and Axis
在彈性盒子中,專案預設沿著main axis(主軸)排列,和主軸垂直的軸叫做cross axis,叫做側軸,或者交叉軸。
在盒子中,排列專案又四個方向:水平的正反兩個,垂直的正反兩個。
結構程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<View> <View style={styles.row}> <Text style={styles.item}>1</Text> <Text style={styles.item}>2</Text> <Text style={styles.item}>3</Text> </View> <View style={styles.rowReverse}> <Text style={styles.item}>1</Text> <Text style={styles.item}>2</Text> <Text style={styles.item}>3</Text> </View> <View style={styles.column}> <Text style={styles.item}>1</Text> <Text style={styles.item}>2</Text> <Text style={styles.item}>3</Text> </View> <View style={styles.columnReverse}> <Text style={styles.item}>1</Text> <Text style={styles.item}>2</Text> <Text style={styles.item}>3</Text> </View> </View> |
樣式程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
row: { backgroundColor: '#ffe289', flexDirection: 'row' }, rowReverse: { flexDirection: 'row-reverse' }, column: { backgroundColor: '#ffe289', flexDirection: 'column' }, columnReverse: { flexDirection: 'column-reverse' }, |
圖5. flexDirection
由於網上關於flex佈局講解的資源挺豐富的,讀者可以參考最後給出的連線,或者自行上網搜尋,CSS中的和RN是相通的。
這裡主要分享個人在學習過程中,覺得容易引起混淆的兩個小點。
首先,justify-content和align-content這兩個屬性,可能比較容易搞錯它們作用的方向。
其中,justify-content是設定items沿著主軸上是如何分佈的。align-content是設定items沿著側軸如何對齊的。
還是拿之前的例子,預設情況下,flex的方向是column(這個與移動端與web頁面不同,在web頁面用CSS設定flex佈局,預設的fiex-direction是row,即水平從左往右)。
在移動端,主軸預設是垂直方向,從上往下。讓我們把它的高度設定高一點,放3個item在裡面:
結構程式碼:
1 2 3 4 5 6 7 |
<View> <View style={styles.defaultFlex}> <Text style={styles.item}>1</Text> <Text style={styles.item}>2</Text> <Text style={styles.item}>3</Text> </View> </View> |
樣式程式碼:
1 2 3 4 5 |
defaultFlex: { height: 300, backgroundColor: '#ffe289', display: 'flex' } |
圖6. 預設的flex
justify-content設定items在主軸方向的如何分佈,比如,如果我們加上justifyContent: ‘space-between’
1 2 3 4 5 6 |
defaultFlex: { height: 300, backgroundColor: '#ffe289', display: 'flex', justifyContent: 'space-between' } |
items就沿主軸分開了。
圖7. justifyContent: ‘space-between’
如果我們設定alignItems: ‘center’,專案就沿側軸(這裡就是水平軸)居中了。注意這兩個屬性是可以同時起作用的。
圖8. justifyContent: ‘space-between’ 以及 alignItems: ‘center’
然後,值得指出的是,flex這個屬性,其實是flexGrow, flexShrink, flexBasis(對應的CSS屬性flex-grow, flex-shrink和flex-basis)三個屬性的結合。
我們通常在移動端看到的flex:1這個設定,其實是對flex-grow的設定。後者的預設值為0。使用把flex-grow設定為正整數的方法,可以讓item按比例分佈,或者在其他item為固定大小時撐滿剩餘的盒子空間,就彷彿具有彈性一樣。
結構程式碼:
1 2 3 4 5 |
<View style={styles.container}> <View style={styles.flex1}></View> <View style={styles.flex2}></View> <View style={styles.flex3}></View> </View> |
樣式程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
container: { flex: 1 }, flex1: { // height: 99, flexGrow: 1, backgroundColor: 'orange', }, flex2: { flexGrow: 2, backgroundColor: 'lightblue', }, flex3: { flexGrow: 3, backgroundColor: 'green', }, |
圖9. 按比例分佈
需要注意的是,如果父容器的尺寸為零(即沒有設定寬高,或者沒有設定flex),即使子元件如果使用了flex,也是無法顯示的。
所以這裡最外層的使用了flex佈局的,flex:1,表示讓它佔據了垂直的整個空間。
三、小小實戰演練
讓我們來簡單使用flex佈局,對之前的例子稍加調整,實現一個頭部,底部固定高度,中間內容佔滿剩下的螢幕的佈局:
第一步,調整結構:
1 2 3 4 5 |
<View style={styles.container}> <View style={styles.header}></View> <View style={styles.body}></View> <View style={styles.footer}></View> </View> |
調整樣式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
container: { flex: 1 }, header: { height: 60, backgroundColor: 'orange', }, body: { flexGrow: 1, backgroundColor: 'lightblue', }, footer: { height: 60, backgroundColor: 'green', } |
圖10. 有頭尾的佈局
第二部,給header新增標題。
我們讓頭部的分成3部分,左邊模擬一個返回按鈕,中間顯示標題文字,右邊模擬一把小叉:
1 2 3 4 5 |
<View style={styles.header}> <Text style={styles.back}>返回</Text> <Text style={styles.title}>這是一個標題</Text> <Text style={styles.exit}>×</Text> </View> |
需要把header的flexDirection設定為水平方向:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
header: { height: 60, backgroundColor: 'orange', flexDirection: 'row', alignItems: 'center' }, back: { color: 'white', marginLeft: 15 }, title: { flexGrow: 1, fontSize: 20, color: 'white', textAlign: 'center' }, exit: { marginRight: 20, fontSize: 20, color: 'white' } |
圖11. header有了標題
第三步,我們可以把footer三等分,模擬成選單的樣子:
1 2 3 4 5 |
<View style={styles.footer}> <Text style={styles.firstMenu}>新增</Text> <Text style={styles.menu}>刪除</Text> <Text style={styles.menu}>修改</Text> </View> |
新增樣式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
footer: { height: 60, backgroundColor: 'green', flexDirection: 'row', alignItems: 'center' }, menu: { flexGrow: 1, textAlign: 'center', borderColor: 'white', borderLeftWidth: 1, color: 'white' }, firstMenu: { flexGrow: 1, textAlign: 'center', color: 'white' }, |
圖12. footer三等分 模擬選單
最後,讓我們在body裡也填入幾個帶按鈕的輸入框。
引入TextInput和Button元件,然後把它們分三組放入body中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<View style={styles.body}> <View style={styles.inputRow}> <TextInput style={styles.textInput}></TextInput> <Button style={styles.btn} onPress={() => {}} title="確定"></Button> </View> <View style={styles.inputRow}> <TextInput style={styles.textInput}></TextInput> <Button style={styles.btn} onPress={() => {}} title="非常確定"></Button> </View> <View style={styles.inputRow}> <TextInput style={styles.textInput}></TextInput> <Button style={styles.btn} onPress={() => {}} title="確定一定以及肯定"></Button> </View> </View> |
新增樣式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
body: { flexGrow: 1, backgroundColor: 'lightblue', }, inputRow: { flexDirection: 'row', alignItems: 'center', marginLeft: 10, marginRight: 10 }, textInput: { flex: 1 }, btn: { minWidth: 60 } |
flex佈局的一個常用實踐是,部分內容固定寬高,讓剩下的內容自適應。
像上面這樣,我們給Button有一個最小寬度,且TextInput的flexGrow為1,這樣的做法可以實現,TextInput總是佔滿剩下的寬度,且可伸縮。
看了上面的例子,是否覺得在React Native中使用Flexbox佈局也挺簡單呢?
希望這是個不錯的開始。