weixin_39901558
weixin_39901558
2021-01-10 04:06

react-native 之布局篇

宽度单位和像素密度

react的宽度不支持百分比,设置宽度时不需要带单位 {width: 10}, 那么10代表的具体宽度是多少呢?

不知道是官网文档不全还是我眼瞎,反正是没找到,那做一个实验自己找吧:

 javascript
    var Dimensions = require('Dimensions');
    <text style="{styles.welcome}">
          window.width={Dimensions.get('window').width + '\n'} 
          window.height={Dimensions.get('window').height + '\n'} 
          pxielRatio={PixelRatio.get()}
    </text> 

默认用的是iphone6的模拟器结果是:

 javascript
    window.width=375
    window.height=667
    pxielRatio=2

我们知道iphone系列的尺寸如下图:

iphones

可以看到iphone 6的宽度为 375pt,对应了上边的375,由此可见react的单位为pt。 那如何获取实际的像素尺寸呢? 这对图片的高清化很重要,如果我的图片大小为100*100 px. 设置宽度为100 * 100. 那在iphone上的尺寸就是模糊的。 这个时候需要的图像大小应该是 100 * pixelRatio的大小 。

react 提供了PixelRatio 的获取方式https://facebook.github.io/react-native/docs/pixelratio.html

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

flex的布局

默认宽度

我们知道一个div如果不设置宽度,默认的会占用100%的宽度, 为了验证100%这个问题, 做三个实验 1. 根节点上方一个View, 不设置宽度 2. 固定宽度的元素上设置一个View, 不设置宽度 3. flex的元素上放一个View宽度, 不设置宽度

 html
 <text style="{[styles.text," styles.header>
      根节点上放一个元素,不设置宽度
  </text>        

  <view style="{{height:" backgroundcolor:></view>

  <text style="{[styles.text," styles.header>
      固定宽度的元素上放一个View,不设置宽度
  </text> 

  <view style="{{width:">
    <view style="{{height:" backgroundcolor:></view>
  </view>

  <text style="{[styles.text," styles.header>
      flex的元素上放一个View,不设置宽度
  </text> 

  <view style="{{flexDirection:">
    <view style="{{flex:">
      <view style="{{height:" backgroundcolor:></view>
    </view>
    <view style="{{flex:"></view>
  </view>

结果可以看到flex的元素如果不设置宽度, 都会百分之百的占满父容器。

水平垂直居中

css 里边经常会做的事情是去讲一个文本或者图片水平垂直居中,如果使用过css 的flexbox当然知道使用alignItemsjustifyContent . 那用react-native也来做一下实验

 html
   <text style="{[styles.text," styles.header>
        水平居中
    </text>

    <view style="{{height:" backgroundcolor: alignitems:>
      <view style="{{backgroundColor:" width: height: borderradius:></view>
    </view>

     <text style="{[styles.text," styles.header>
        垂直居中
    </text>
    <view style="{{height:" backgroundcolor: justifycontent:>
      <view style="{{backgroundColor:" width: height: borderradius:></view>
    </view>

    <text style="{[styles.text," styles.header>
        水平垂直居中
    </text>
    <view style="{{height:" backgroundcolor: alignitems: justifycontent:>
      <view style="{{backgroundColor:" width: height: borderradius:></view>
    </view>

网格布局

网格布局实验, 网格布局能够满足绝大多数的日常开发需求,所以只要满足网格布局的spec,那么就可以证明react的flex布局能够满足正常开发需求

等分的网格

 javascript
    <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的布局

 javascript
    <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下面的网格布局

 html
 <text style="{[styles.text," styles.header>
    嵌套的网格
  </text>
  <view style="{{flexDirection:" height: backgroundcolor: padding:>
    <view style="{{flex:" flexdirection: padding: backgroundcolor:>  
        <view style="{{flex:" backgroundcolor:>  
        </view>
        <view style="{{flex:" backgroundcolor:>
        </view>
    </view>
    <view style="{{flex:" padding: flexdirection: backgroundcolor:>
        <view style="{{flex:" backgroundcolor:>  
            <view style="{{flex:" flexdirection: backgroundcolor:> 
               <view style="{{flex:" backgroundcolor:>  
              </view>
              <view style="{{flex:" backgroundcolor:>
              </view> 
            </view>
            <view style="{{flex:" backgroundcolor:>
            </view>
        </view>
        <view style="{{flex:" backgroundcolor:>
          <scrollview style="{{flex:" backgroundcolor: padding:>
                <view style="{{flexDirection:" height: backgroundcolor:>
                  <view style="{{flex:" flexdirection: backgroundcolor:>  
                      <view style="{{flex:" backgroundcolor:>  
                      </view>
                      <view style="{{flex:" backgroundcolor:>
                      </view>
                  </view>
                  <view style="{{flex:" flexdirection: backgroundcolor:>
                      <view style="{{flex:" backgroundcolor:>  
                          <view style="{{flex:" flexdirection: backgroundcolor:> 
                             <view style="{{flex:" backgroundcolor:>  
                            </view>
                            <view style="{{flex:" backgroundcolor:>
                            </view> 
                          </view>
                          <view style="{{flex:" backgroundcolor:>
                          </view>
                      </view>
                      <view style="{{flex:" backgroundcolor:>
                      </view>
                  </view>
                </view>
                <text style="{[styles.text," styles.header fontsize:>
                  {(function(){
                    var str = '';
                    var n = 100;
                    while(n--) {
                      str += '嵌套的网格' + '\n';
                    }
                    return str;
                  })()}
                </text>
          </scrollview> 
        </view>
    </view>
  </view>

好在没被我玩儿坏,可以看到上图的嵌套关系也是足够的复杂的,(我还加了一个ScrollView,然后再嵌套整个结构)嵌套多层的布局是没有问题的。

图片布局

首先我们得知道图片有一个stretchMode. 通过Image.resizeMode访问

找出有哪些mode

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

打印出来的是 contain, cover, stretch 这几种模式, (官方文档不知道为什么不直接给出)

尝试使用这些mode

 javascript
  <text style="{styles.welcome}"> 100px height </text>
  <image style="{{height:" source="{{uri:"></image>

100px 高度, 可以看到图片适应100高度和全屏宽度,背景居中适应未拉伸但是被截断也就是cover。

 javascript
  <text style="{styles.welcome}"> 100px height with resizeMode contain </text>
  <view style="{[{flex:" backgroundcolor:>
      <image style="{{flex:" height: resizemode: image.resizemode.contain source="{{uri:"></image>
  </view>

contain 模式容器完全容纳图片,图片自适应宽高

 javascript
  <text style="{styles.welcome}"> 100px height with resizeMode cover </text>
  <view style="{[{flex:" backgroundcolor:>
      <image style="{{flex:" height: resizemode: image.resizemode.cover source="{{uri:"></image>
  </view>

cover模式同100px高度模式

 javascript

  <text style="{styles.welcome}"> 100px height with resizeMode stretch </text>
  <view style="{[{flex:" backgroundcolor:>
      <image style="{{flex:" height: resizemode: image.resizemode.stretch source="{{uri:"></image>
  </view>

stretch模式图片被拉伸适应屏幕

 javascript
  <text style="{styles.welcome}"> set height to image container </text>
  <view style="{[{flex:" backgroundcolor: height:>
      <image style="{{flex:" source="{{uri:"></image>
  </view>

随便试验了一下, 发现高度设置到父容器,图片flex的时候也会等同于cover模式

绝对定位和相对定位

 javascript
 <view style="{{flex:" height: backgroundcolor:>
    <view style="{[styles.circle," top: left:>
    </view>
  </view>
  styles = {
    circle: {
    backgroundColor: '#fe0000',
    borderRadius: 10,
    width: 20,
    height: 20
    }
  }

和css的标准不同的是, 元素容器不用设置position:'absolute|relative' .

 javascript
 <view style="{{flex:" height: backgroundcolor:>
    <view style="{[styles.circle," top: left: marginleft:>
    </view>
  </view>

相对定位的可以看到很容易的配合margin做到了。 (我还担心不能配合margin,所以测试了一下:-:)

padding和margin

我们知道在css中区分inline元素和block元素,既然react-native实现了一个超级小的css subset。那我们就来实验一下padding和margin在inline和非inline元素上的padding和margin的使用情况。

_padding _

 html
 <text style="{[styles.text," styles.header>
    在正常的View上设置padding 
  </text>

  <view style="{{padding:" backgroundcolor:>
    <text style="{[styles.text,"> Text Element</text>
  </view>

  <text style="{[styles.text," styles.header>
    在文本元素上设置padding
  </text>
  <view style="{{padding:" backgroundcolor:>
    <text style="{[styles.text," padding:>
      text 元素上设置paddinga
    </text>
  </view>

在View上设置padding很顺利,没有任何问题, 但是如果在inline元素上设置padding, 发现会出现上面的错误, paddingTop和paddingBottom都被挤成marginBottom了。 按理说,不应该对Text做padding处理, 但是确实有这样的问题存在,所以可以将这个问题mark一下。

margin

 html
 <text style="{[styles.text," styles.header>
    在正常的View上设置margin 
  </text>

  <view style="{{backgroundColor:">
    <view style="{{backgroundColor:" width: height: margin:></view>
  </view>

  <text style="{[styles.text," styles.header>
    在文本元素上设置margin
  </text>
  <view style="{{backgroundColor:">
    <text style="{[styles.text," margin:>
      text 元素上设置margin
    </text>
    <text style="{[styles.text," margin:>
      text 元素上设置margin
    </text>
  </view>

我们知道,对于inline元素,设置margin-left和margin-right有效,top和bottom按理是不会生效的, 但是上图的结果可以看到,实际是生效了的。所以现在给我的感觉是Text元素更应该理解为一个不能设置padding的block。

算了不要猜了, 我们看看官方文档怎么说Text,https://facebook.github.io/react-native/docs/text.html

 html
  <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属性

 javascript
 /*==========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

 html
 <text style="{[styles.text," styles.header>
      文本元素
  </text>

  <view style="{{backgroundColor:" padding:>
    <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, 看官文

 html
 <text style="{{fontWeight:">
  I am bold
  <text style="{{color:">
    and red
  </text>
 </text>

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

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

好吧, 那对于numberOfLines={5} 放在子Text元素上的那种bug倒是可以解释了。

Text的样式继承

实际上React-native里边是没有样式继承这种说法的, 但是对于Text元素里边的Text元素,上面的例子可以看出存在继承。 那既然有继承,问题就来了!

到底是继承的最外层的Text的值呢,还是继承父亲Text的值呢?

 html
 <text style="{[styles.text," styles.header>
      文本样式继承
  </text>

  <view style="{{backgroundColor:" padding:>
    <text style="{{color:">
      <text style="{{color:" onpress="{this.onPressTitle}">
         文本元素{'\n'}
        <text>我是white还是red呢?{'\n'} </text>
      </text>
      <text>我应该是white的</text>
    </text>
  </view>

结果可见是直接继承父亲Text的。

总结

  1. react 宽度基于pt为单位, 可以通过Dimensions 来获取宽高,PixelRatio 获取密度,如果想使用百分比,可以通过获取屏幕宽度手动计算。
  2. 基于flex的布局
  3. view默认宽度为100%
  4. 水平居中用alignItems, 垂直居中用justifyContent
  5. 基于flex能够实现现有的网格系统需求,且网格能够各种嵌套无bug
  6. 图片布局
  7. 通过Image.resizeMode来适配图片布局,包括contain, cover, stretch
  8. 默认不设置模式等于cover模式
  9. contain模式自适应宽高,给出高度值即可
  10. cover铺满容器,但是会做截取
  11. stretch铺满容器,拉伸
  12. 定位
  13. 定位相对于父元素,父元素不用设置position也行
  14. padding 设置在Text元素上的时候会存在bug。所有padding变成了marginBottom
  15. 文本元素
  16. 文字必须放在Text元素里边
  17. Text元素可以相互嵌套,且存在样式继承关系
  18. numberOfLines 需要放在最外层的Text元素上,且虽然截取了文字但是还是会占用空间

该提问来源于开源项目:tmallfe/tmallfe.github.io

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

44条回答

  • weixin_39889597 weixin_39889597 3月前

    学习了

    点赞 评论 复制链接分享
  • weixin_39641450 weixin_39641450 3月前

    nice

    点赞 评论 复制链接分享
  • weixin_39695701 weixin_39695701 3月前

    很不错

    点赞 评论 复制链接分享
  • weixin_39886469 weixin_39886469 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39769187 weixin_39769187 3月前

    good!

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前

    图片的resizeMode 我设置在Props生效,设置在style不生效:https://facebook.github.io/react-native/docs/image.html#resizemode

    RN版本:0.26

    点赞 评论 复制链接分享
  • weixin_39662834 weixin_39662834 3月前

    good

    点赞 评论 复制链接分享
  • weixin_39519798 weixin_39519798 3月前

    贵猫的大神,问个问题,你们如何解决rem在 魅族,华为荣耀系列的手机下的不兼容的? 我做了一个测试,设置 1rem = 50px ,然后给一个div设置 1rem 的宽, 在华为荣耀c3下打印出来 这个div的宽度为 57px 这个怎么破? 求解....

    点赞 评论 复制链接分享
  • weixin_39842611 weixin_39842611 3月前

    很nice!

    点赞 评论 复制链接分享
  • weixin_39563722 weixin_39563722 3月前

    赞。。

    点赞 评论 复制链接分享
  • weixin_39831567 weixin_39831567 3月前

    nice

    点赞 评论 复制链接分享
  • weixin_39861669 weixin_39861669 3月前

    👍

    点赞 评论 复制链接分享
  • weixin_39887577 weixin_39887577 3月前

    您好,请问有没有试过,alignSelf:'flex-end'后,view的高度就失效了

    点赞 评论 复制链接分享
  • weixin_39859954 weixin_39859954 3月前

    赞一个

    点赞 评论 复制链接分享
  • weixin_39661881 weixin_39661881 3月前

    Good!

    点赞 评论 复制链接分享
  • weixin_39645165 weixin_39645165 3月前

    点赞 评论 复制链接分享
  • weixin_39636718 weixin_39636718 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39558317 weixin_39558317 3月前

    好文,赞~

    点赞 评论 复制链接分享
  • weixin_39925813 weixin_39925813 3月前

    点赞 评论 复制链接分享
  • weixin_39616416 weixin_39616416 3月前

    不错

    点赞 评论 复制链接分享
  • weixin_39927993 weixin_39927993 3月前

    怒赞

    点赞 评论 复制链接分享
  • weixin_39719427 weixin_39719427 3月前

    请问一下,

     javascript
    <image source="require('image!image_name')/"> 
    </image>

    怎么和PixelRatio一起使用?谢谢!

    点赞 评论 复制链接分享
  • weixin_39927158 weixin_39927158 3月前

    不错,好文章

    点赞 评论 复制链接分享
  • weixin_39996478 weixin_39996478 3月前

    学习了,赞!

    点赞 评论 复制链接分享
  • weixin_39685130 weixin_39685130 3月前

    谢谢!

    点赞 评论 复制链接分享
  • weixin_39640265 weixin_39640265 3月前

    :+1:

    点赞 评论 复制链接分享
  • weixin_39744384 weixin_39744384 3月前

    很赞,对于因为react-native才去了解react的我很有帮助

    点赞 评论 复制链接分享
  • weixin_39589557 weixin_39589557 3月前

    相比看原版的英文文档更清晰,细腻,赞,持续关注

    点赞 评论 复制链接分享
  • weixin_39696665 weixin_39696665 3月前

    很好的文章,赞!👍

    点赞 评论 复制链接分享
  • weixin_39589557 weixin_39589557 3月前

    提个小bug,开头的第一个例子的输出少了一个: pxielRatio=2

    点赞 评论 复制链接分享
  • weixin_39901558 weixin_39901558 3月前

    已修改 thx!

    点赞 评论 复制链接分享
  • weixin_39890633 weixin_39890633 3月前

    Good Job!

    点赞 评论 复制链接分享
  • weixin_39989941 weixin_39989941 3月前

    SF那篇同款也是楼主的吗?赞!

    点赞 评论 复制链接分享
  • weixin_39901571 weixin_39901571 3月前

    不错,很好的总结,可以对照走

    点赞 评论 复制链接分享
  • weixin_39901558 weixin_39901558 3月前

    是的,同款

    点赞 评论 复制链接分享
  • weixin_39568659 weixin_39568659 3月前

    所以默认主轴是垂直方向哎。。一直按照flexbox的理解把justify和alignitem搞反了。。

    点赞 评论 复制链接分享
  • weixin_39913628 weixin_39913628 3月前
    
    <image style="{{flex:" height: resizemode: image.resizemode.contain source="{{uri:"></image> 
    

    这里的height: 100 应该是100pt吧

    点赞 评论 复制链接分享
  • weixin_39806388 weixin_39806388 3月前

    出个中文文档就好了

    点赞 评论 复制链接分享
  • weixin_39918128 weixin_39918128 3月前

    火前留名

    点赞 评论 复制链接分享
  • weixin_39637370 weixin_39637370 3月前

    :+1: 学习了

    点赞 评论 复制链接分享
  • weixin_39548832 weixin_39548832 3月前

    点赞好评!很有质量的文章

    点赞 评论 复制链接分享
  • weixin_39579483 weixin_39579483 3月前

    :clap::clap::clap:

    点赞 评论 复制链接分享
  • weixin_39775577 weixin_39775577 3月前

    :+1:

    点赞 评论 复制链接分享
  • weixin_39996478 weixin_39996478 3月前

    :+1:

    点赞 评论 复制链接分享