1. 程式人生 > >React Native探索(二):佈局篇

React Native探索(二):佈局篇

可以看到iphone 6的寬度為 375pt,對應了上邊的375,由此可見react的單位為pt。 那如何獲取實際的畫素尺寸呢? 這對圖片的高清化很重要,如果我的圖片大小為100*100 px. 設定寬度為100 * 100. 那在iphone上的尺寸就是模糊的。 這個時候需要的影象大小應該是 100 * pixelRatio的大小 。

 var image = getImage({
   width: 200 * PixelRatio.get(),
   height: 100 * PixelRatio.get()
 });
 <Image source={image} style={{width: 200, height: 100}} />

flex的佈局

預設寬度

我們知道一個div如果不設定寬度,預設的會佔用100%的寬度, 為了驗證100%這個問題, 做三個實驗

  1. 根節點上方一個View, 不設定寬度
  2. 固定寬度的元素上設定一個View, 不設定寬度
  3. flex的元素上放一個View寬度, 不設定寬度
 <Text style={[styles.text, styles.header]}>
      根節點上放一個元素,不設定寬度
  </Text>        

  <View style={{height: 20, backgroundColor: '#333333'}} />

  <Text style={[styles.text, styles.header]}>
      固定寬度的元素上放一個View,不設定寬度
  </Text> 

  <View style={{width: 100}}>
    <View style={{height: 20, backgroundColor: '#333333'}} />
  </View>

  <Text style={[styles.text, styles.header]}>
      flex的元素上放一個View,不設定寬度
  </Text> 

  <View style={{flexDirection: 'row'}}>
    <View style={{flex: 1}}>
      <View style={{height: 20, backgroundColor: '#333333'}} />
    </View>
    <View style={{flex: 1}}/>
  </View>


結果可以看到flex的元素如果不設定寬度, 都會百分之百的佔滿父容器。

水平垂直居中

css 裡邊經常會做的事情是去講一個文字或者圖片水平垂直居中,如果使用過css 的flexbox當然知道使用alignItemsjustifyContent . 那用react-native也來做一下實驗

   <Text style={[styles.text, styles.header]}>
        水平居中
    </Text>

    <View style={{height: 100, backgroundColor: '#333333', alignItems: 'center'}}>
      <View style={{backgroundColor: '#fefefe', width: 30, height: 30, borderRadius: 15}}/>
    </View>

     <Text style={[styles.text, styles.header]}>
        垂直居中
    </Text>
    <View style={{height: 100, backgroundColor: '#333333', justifyContent: 'center'}}>
      <View style={{backgroundColor: '#fefefe', width: 30, height: 30, borderRadius: 15}}/>
    </View>

    <Text style={[styles.text, styles.header]}>
        水平垂直居中
    </Text>
    <View style={{height: 100, backgroundColor: '#333333', alignItems: 'center', justifyContent: 'center'}}>
      <View style={{backgroundColor: '#fefefe', width: 30, height: 30, borderRadius: 15}}/>
    </View>



網格佈局

網格佈局實驗, 網格佈局能夠滿足絕大多數的日常開發需求,所以只要滿足網格佈局的spec,那麼就可以證明react的flex佈局能夠滿足正常開發需求

等分的網格


    <View style={styles.flexContainer}>
      <View style={styles.cell}>
        <Text style={styles.welcome}>
          cell1
        </Text>
      </View>
      <View style={styles.cell}>
        <Text style={styles.welcome}>
          cell2
        </Text>
      </View>
      <View style={styles.cell}>
        <Text style={styles.welcome}>
          cell3
        </Text>
      </View>
    </View>

    styles = {
        flexContainer: {
            // 容器需要新增direction才能變成讓子元素flex
            flexDirection: 'row'
        },
        cell: {
            flex: 1,
            height: 50,
            backgroundColor: '#aaaaaa'
        },
        welcome: {
            fontSize: 20,
            textAlign: 'center',
            margin: 10
        },
    }

左邊固定, 右邊固定,中間flex的佈局


    <View style={styles.flexContainer}>
      <View style={styles.cellfixed}>
        <Text style={styles.welcome}>
          fixed
        </Text>
      </View>
      <View style={styles.cell}>
        <Text style={styles.welcome}>
          flex
        </Text>
      </View>
      <View style={styles.cellfixed}>
        <Text style={styles.welcome}>
          fixed
        </Text>
      </View>
    </View>

    styles = {
        flexContainer: {
            // 容器需要新增direction才能變成讓子元素flex
            flexDirection: 'row'
        },
        cell: {
            flex: 1,
            height: 50,
            backgroundColor: '#aaaaaa'
        },
        welcome: {
            fontSize: 20,
            textAlign: 'center',
            margin: 10
        },
        cellfixed: {
            height: 50,
            width: 80,
            backgroundColor: '#fefefe'
        } 
    }

巢狀的網格

通常網格不是一層的,佈局容器都是一層套一層的, 所以必須驗證在real world下面的網格佈局

 <Text style={[styles.text, styles.header]}>
    巢狀的網格
  </Text>
  <View style={{flexDirection: 'row', height: 200, backgroundColor:"#fefefe", padding: 20}}>
    <View style={{flex: 1, flexDirection:'column', padding: 15, backgroundColor:"#eeeeee"}}>  
        <View style={{flex: 1, backgroundColor:"#bbaaaa"}}>  
        </View>
        <View style={{flex: 1, backgroundColor:"#aabbaa"}}>
        </View>
    </View>
    <View style={{flex: 1, padding: 15, flexDirection:'row', backgroundColor:"#eeeeee"}}>
        <View style={{flex: 1, backgroundColor:"#aaaabb"}}>  
            <View style={{flex: 1, flexDirection:'row', backgroundColor:"#eeaaaa"}}> 
               <View style={{flex: 1, backgroundColor:"#eebbaa"}}>  
              </View>
              <View style={{flex: 1, backgroundColor:"#bbccee"}}>
              </View> 
            </View>
            <View style={{flex: 1, backgroundColor:"#eebbdd"}}>
            </View>
        </View>
        <View style={{flex: 1, backgroundColor:"#aaccaa"}}>
          <ScrollView style={{flex: 1, backgroundColor:"#bbccdd", padding: 5}}>
                <View style={{flexDirection: 'row', height: 50, backgroundColor:"#fefefe"}}>
                  <View style={{flex: 1, flexDirection:'column', backgroundColor:"#eeeeee"}}>  
                      <View style={{flex: 1, backgroundColor:"#bbaaaa"}}>  
                      </View>
                      <View style={{flex: 1, backgroundColor:"#aabbaa"}}>
                      </View>
                  </View>
                  <View style={{flex: 1, flexDirection:'row', backgroundColor:"#eeeeee"}}>
                      <View style={{flex: 1, backgroundColor:"#aaaabb"}}>  
                          <View style={{flex: 1, flexDirection:'row', backgroundColor:"#eeaaaa"}}> 
                             <View style={{flex: 1, backgroundColor:"#eebbaa"}}>  
                            </View>
                            <View style={{flex: 1, backgroundColor:"#bbccee"}}>
                            </View> 
                          </View>
                          <View style={{flex: 1, backgroundColor:"#eebbdd"}}>
                          </View>
                      </View>
                      <View style={{flex: 1, backgroundColor:"#aaccaa"}}>
                      </View>
                  </View>
                </View>
                <Text style={[styles.text, styles.header, {color: '#ffffff', fontSize: 12}]}>
                  {(function(){
                    var str = '';
                    var n = 100;
                    while(n--) {
                      str += '巢狀的網格' + '\n';
                    }
                    return str;
                  })()}
                </Text>
          </ScrollView> 
        </View>
    </View>
  </View>


好在沒被我玩兒壞,可以看到上圖的巢狀關係也是足夠的複雜的,(我還加了一個ScrollView,然後再巢狀整個結構)巢狀多層的佈局是沒有問題的。

圖片佈局

首先我們得知道圖片有一個stretchMode. 通過Image.resizeMode訪問

找出有哪些mode

  var keys = Object.keys(Image.resizeMode).join('  ');

打印出來的是 contain, cover, stretch 這幾種模式, (官方文件不知道為什麼不直接給出)

嘗試使用這些mode


100px 高度, 可以看到圖片適應100高度和全屏寬度,背景居中適應未拉伸但是被截斷也就是cover。

  <Text style={styles.welcome}> 100px height with resizeMode contain </Text>
  <View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
      <Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.contain}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
  </View>


contain 模式容器完全容納圖片,圖片自適應寬高

  <Text style={styles.welcome}> 100px height with resizeMode cover </Text>
  <View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
      <Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.cover}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
  </View>


cover模式同100px高度模式

  <Text style={styles.welcome}> 100px height with resizeMode stretch </Text>
  <View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
      <Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.stretch}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
  </View>


stretch模式圖片被拉伸適應螢幕

  <Text style={styles.welcome}> set height to image container </Text>
  <View style={[{flex: 1, backgroundColor: '#fe0000', height: 100}]}>
      <Image style={{flex: 1}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
  </View>


隨便試驗了一下, 發現高度設定到父容器,圖片flex的時候也會等同於cover模式

絕對定位和相對定位

 <View style={{flex: 1, height: 100, backgroundColor: '#333333'}}>
    <View style={[styles.circle, {position: 'absolute', top: 50, left: 180}]}>
    </View>
  </View>
  styles = {
    circle: {
    backgroundColor: '#fe0000',
    borderRadius: 10,
    width: 20,
    height: 20
    }
  }


和css的標準不同的是, 元素容器不用設定position:'absolute|relative' .

 <View style={{flex: 1, height: 100, backgroundColor: '#333333'}}>
    <View style={[styles.circle, {position: 'relative', top: 50, left: 50, marginLeft: 50}]}>
    </View>
  </View>


相對定位的可以看到很容易的配合margin做到了。 (我還擔心不能配合margin,所以測試了一下:-:)

padding和margin

我們知道在css中區分inline元素和block元素,既然react-native實現了一個超級小的css subset。那我們就來實驗一下padding和margin在inline和非inline元素上的padding和margin的使用情況。

*padding *

 <Text style={[styles.text, styles.header]}>
    在正常的View上設定padding 
  </Text>

  <View style={{padding: 30, backgroundColor: '#333333'}}>
    <Text style={[styles.text, {color: '#fefefe'}]}> Text Element</Text>
  </View>

  <Text style={[styles.text, styles.header]}>
    在文字元素上設定padding
  </Text>
  <View style={{padding: 0, backgroundColor: '#333333'}}>
    <Text style={[styles.text, {backgroundColor: '#fe0000', padding: 30}]}>
      text 元素上設定paddinga
    </Text>
  </View>


在View上設定padding很順利,沒有任何問題, 但是如果在inline元素上設定padding, 發現會出現上面的錯誤, paddingTop和paddingBottom都被擠成marginBottom了。 按理說,不應該對Text做padding處理, 但是確實有這樣的問題存在,所以可以將這個問題mark一下。

margin

 <Text style={[styles.text, styles.header]}>
    在正常的View上設定margin 
  </Text>

  <View style={{backgroundColor: '#333333'}}>
    <View style={{backgroundColor: '#fefefe', width: 30, height: 30, margin: 30}}/>
  </View>

  <Text style={[styles.text, styles.header]}>
    在文字元素上設定margin
  </Text>
  <View style={{backgroundColor: '#333333'}}>
    <Text style={[styles.text, {backgroundColor: '#fe0000', margin: 30}]}>
      text 元素上設定margin
    </Text>
    <Text style={[styles.text, {backgroundColor: '#fe0000', margin: 30}]}>
      text 元素上設定margin
    </Text>
  </View>


我們知道,對於inline元素,設定margin-left和margin-right有效,top和bottom按理是不會生效的, 但是上圖的結果可以看到,實際是生效了的。所以現在給我的感覺是Text元素更應該理解為一個不能設定padding的block。

  <Text>
    <Text>First part and </Text>
    <Text>second part</Text>
  </Text>
  // Text container: all the text flows as if it was one
  // |First part |
  // |and second |
  // |part       |

  <View>
    <Text>First part and </Text>
    <Text>second part</Text>
  </View>
  // View container: each text is its own block
  // |First part |
  // |and        |
  // |second part|

也就是如果Text元素在Text裡邊,可以考慮為inline, 如果單獨在View裡邊,那就是Block。

下面會專門研究一下文字相關的佈局

文字元素

首先我們得考慮對於Text元素我們希望有哪些功能或者想驗證哪些功能:

  1. 文字是否能自動換行?
  2. overflow ellipse?
  3. 是否能對部分文字設定樣式 ,類似span等標籤

先看看文字有哪些支援的style屬性

 /*==========TEXT================*/
  Attributes.style = {
    color string
    containerBackgroundColor string
    fontFamily string
    fontSize number
    fontStyle enum('normal', 'italic')
    fontWeight enum("normal", 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900')
    lineHeight number
    textAlign enum("auto", 'left', 'right', 'center')
    writingDirection enum("auto", 'ltr', 'rtl')
  }

實驗1, 2, 3

 <Text style={[styles.text, styles.header]}>
      文字元素
  </Text>

  <View style={{backgroundColor: '#333333', padding: 10}}>
    <Text style={styles.baseText} numberOfLines={5}>
      <Text style={styles.titleText} onPress={this.onPressTitle}>
        文字元素{'\n'}
      </Text>
      <Text>
        {'\n'}In this example, the nested title and body text will inherit the fontFamily from styles.baseText, but the title provides its own additional styles. The title and body will stack on top of each other on account of the literal newlines, numberOfLines is Used to truncate the text with an elipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number.
      </Text>
    </Text>
  </View>
  styles = {
    baseText: {
      fontFamily: 'Cochin',
      color: 'white'
    },
    titleText: {
      fontSize: 20,
      fontWeight: 'bold',
    }
  }


從結果來看1,2,3得到驗證。 但是不知道各位有沒有發現問題, 為什麼底部空出了這麼多空間, 沒有設定高度啊。 我去除numberOfLines={5} 這行程式碼,效果如下:


所以實際上, 那段空間是文字撐開的, 但是文字被numberOfLines={5} 截取了,但是剩餘的空間還在。 我猜這應該是個bug。

其實官方文件裡邊把numberOfLines={5}這句放到的是長文字的Text元素上的,也就是子Text上的。 實際結果是不生效。 這應該又是一個bug。

Text元素的子Text元素的具體實現是怎樣的, 感覺這貨會有很多bug, 看官文

 <Text style={{fontWeight: 'bold'}}>
  I am bold
  <Text style={{color: 'red'}}>
    and red
  </Text>
 </Text>

Behind the scenes, this is going to be converted to a flat
NSAttributedString that contains the following information

 "I am bold and red"
  0-9: bold
  9-17: bold, red

好吧, 那對於numberOfLines={5} 放在子Text元素上的那種bug倒是可以解釋了。

Text的樣式繼承

實際上React-native裡邊是沒有樣式繼承這種說法的, 但是對於Text元素裡邊的Text元素,上面的例子可以看出存在繼承。 那既然有繼承,問題就來了!

到底是繼承的最外層的Text的值呢,還是繼承父親Text的值呢?

 <Text style={[styles.text, styles.header]}>
      文字樣式繼承
  </Text>

  <View style={{backgroundColor: '#333333', padding: 10}}>
    <Text style={{color: 'white'}}>
      <Text style={{color: 'red'}} onPress={this.onPressTitle}>
         文字元素{'\n'}
        <Text>我是white還是red呢?{'\n'} </Text>
      </Text>
      <Text>我應該是white的</Text>
    </Text>
  </View>


結果可見是直接繼承父親Text的。

總結