开始学习 React Native,首先必须了解其布局的特性,因为其与传统的 Web 开发中的布局有着不小的差异。

尺寸

React Native#Dimensions

RN 提供的 Dimensions API 可以让我们在应用程序内获得屏幕和窗体的宽高尺寸。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var { height, width } = Dimensions.get('window');
var { height: height2, width: width2 } = Dimensions.get('screen');
return (
    <View style={styles.container}>
        <Text style={styles.welcome}>Welcome to React Native!</Text>
        <Text style={styles.instructions}>
            {`The window width is ${width}, height is ${height}`}
        </Text>
        <Text style={styles.instructions}>
            {`The screen width is ${width2}, height is ${height2}`}
        </Text>
    </View>
);

在 Nexus 5X(420DPI, 1080x1920)模拟器效果如下: Dimensions

窗体与屏幕尺寸之间的差异主要是,窗体仅仅指可以布局的区域,而屏幕指整个设备尺寸范围,那么垂直方向的设备,两者之间在高度上的差异就主要是虚拟按键和状态栏高度。

1080 物理像素宽度实际显示的宽度约 411,这个 411 的单位实际上是 RN 中特殊的尺寸单位dp,全称Density-independent Pixel,即密度无关像素。这个概念有点类似于 Web 浏览器中的ViewPointDP认定在一个 160DPI 的设备上1dp=1px,所以 Nexus 5X 的宽度可以如下计算1080*160/420~=411

Density-independent Pixel

Flex 布局

RN 中各元素采用 Flex 布局,这与 Web CSS3 中的 Flex 布局整体接近,但在默认值,可用属性方面有一些区别:

  • flexDirection: React Native 中默认为 flexDirection:‘column’,在 Web CSS 中默认为 flex-direction:‘row’

  • alignItems: React Native 中默认为 alignItems:‘stretch’,在 Web CSS 中默认 align-items:‘flex-start’

  • flex: 相比 Web CSS 的 flex 接受多参数,如:flex: 2 2 10%;,但在 React Native 中 flex 只接受一个参数

  • 不支持属性:align-content,flex-basis,order,flex-basis,flex-flow,flex-grow,flex-shrink

现在使用默认的flexDirection:column,实现水平居中、垂直居中、水平垂直居中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<View
    style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'stretch',
    }}
>
    <View
        style={{
            flex: 1,
            backgroundColor: 'red',
            alignItems: 'center',
        }}
    >
        <View
            style={{
                width: 50,
                height: 50,
                backgroundColor: '#eaeaea',
            }}
        >
            <Text style={{ color: '#222' }}>水平居中</Text>
        </View>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: 'yellow',
            justifyContent: 'center',
        }}
    >
        <View
            style={{
                width: 50,
                height: 50,
                backgroundColor: '#eaeaea',
            }}
        >
            <Text style={{ color: '#222' }}>垂直居中</Text>
        </View>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: 'blue',
            justifyContent: 'center',
            alignItems: 'center',
        }}
    >
        <View
            style={{
                width: 50,
                height: 50,
                backgroundColor: '#eaeaea',
            }}
        >
            <Text style={{ color: '#222' }}>水平垂直居中</Text>
        </View>
    </View>
</View>

Align

再来实现一个等分的网格布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<View
    style={{
        flex: 1,
        flexDirection: 'row',
        alignItems: 'flex-start',
    }}
>
    <View
        style={{
            flex: 1,
            backgroundColor: 'red',
            height: 30,
        }}
    >
        <Text style={{ color: 'white' }}>Cell 1</Text>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: 'yellow',
            height: 30,
        }}
    >
        <Text style={{ color: '#000' }}>Cell 2</Text>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: 'blue',
            height: 30,
        }}
    >
        <Text style={{ color: 'white' }}>Cell 3</Text>
    </View>
</View>

grid-cells

实现左边固定,右边固定,中间非固定的三栏布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<View
    style={{
        flex: 1,
        flexDirection: 'row',
        alignItems: 'flex-start',
    }}
>
    <View
        style={{
            width: 50,
            backgroundColor: 'red',
            height: 30,
        }}
    >
        <Text style={{ color: 'white' }}>Cell 1</Text>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: 'yellow',
            height: 30,
        }}
    >
        <Text style={{ color: '#000' }}>Cell 2</Text>
    </View>
    <View
        style={{
            width: 50,
            backgroundColor: 'blue',
            height: 30,
        }}
    >
        <Text style={{ color: 'white' }}>Cell 3</Text>
    </View>
</View>

grid-cells-2

大多数情况,只需要调节如下几个重要布局属性即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum flexDirection {
    row,
    column,
    'row-reverse',
    'column-reverse',
}

enum flexWrap {
    wrap,
    nowrap,
}

enum justifyContent {
    'flex-start',
    'flex-end',
    'center',
    'space-between',
    'space-around',
}

enum alignItems {
    'flex-start',
    'flex-end',
    'center',
    'stretch',
}

要记住的一点即是justifyContent定义的是子元素沿主轴方向(flexDirect 为 column 时是垂直方向,为 row 时是水平方向)的排列,alignItems定义的是子元素沿侧轴方向(flexDirect 为 column 时是水平方向,为 row 时是垂直方向)的排列。由于主轴方向是 1-N 个子元素排列,因此多了space-betweenspace-around的属性值来决定布局,示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<View
    style={{
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'center',
    }}
>
    <View
        style={{
            flex: 1,
            flexDirection: 'column',
            justifyContent: 'space-between',
            alignItems: 'center',
        }}
    >
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>between</Text>
        </View>
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>between</Text>
        </View>
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>between</Text>
        </View>
    </View>
    <View
        style={{
            flex: 1,
            flexDirection: 'column',
            justifyContent: 'space-around',
            alignItems: 'center',
            backgroundColor: '#aaa',
        }}
    >
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>around</Text>
        </View>
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>around</Text>
        </View>
        <View
            style={{
                width: 50,
                backgroundColor: '#5ba4e5',
                height: 100,
            }}
        >
            <Text style={{ color: 'white' }}>around</Text>
        </View>
    </View>
</View>

Space-between-around

绝对与相对定位

RN 中绝对与相对定位仍然可以使用,但是父级容器元素无需定义position,子元素按直接父级进行定位。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<View
    style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
    }}
>
    <View
        style={{
            flex: 1,
            backgroundColor: '#eaeaea',
        }}
    >
        <View
            style={{
                width: 100,
                height: 100,
                backgroundColor: '#5ba4e5',
                position: 'relative',
                left: 20,
                top: 50,
            }}
        >
            <Text style={{ color: 'white', flexWrap: 'wrap' }}>
                relative left 20, top 50
            </Text>
        </View>
    </View>
    <View
        style={{
            flex: 1,
            backgroundColor: '#888',
        }}
    >
        <View
            style={{
                width: 100,
                height: 100,
                backgroundColor: '#5ba4e5',
                position: 'absolute',
                left: 100,
                top: 60,
            }}
        >
            <Text style={{ color: 'white', flexWrap: 'wrap' }}>
                absolute left 100, top 60
            </Text>
        </View>
    </View>
</View>

position

padding 与 margin

分别对ViewText组件进行 padding 测试,两个分别对应blockinline元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<View
    style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
    }}
>
    <View
        style={{
            backgroundColor: '#666',
            padding: 50,
        }}
    >
        <Text
            style={{
                color: 'white',
                flexWrap: 'wrap',
                backgroundColor: '#234',
            }}
        >
            view padding 50
        </Text>
    </View>
    <View
        style={{
            backgroundColor: '#888',
        }}
    >
        <Text
            style={{
                padding: 50,
                color: 'white',
                flexWrap: 'wrap',
                backgroundColor: '#234',
            }}
        >
            text padding 50
        </Text>
    </View>
    <View style={{ flex: 1 }} />
</View>

padding

可以看到不论是 Text(深蓝背景)还是 View(灰色背景)均可撑开容器高度。

仍然按照上面例子,只是为有padding属性的元素再添加一个固定高度 200:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<View
    style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
    }}
>
    <View
        style={{
            backgroundColor: '#666',
            padding: 50,
            height: 200,
        }}
    >
        <Text
            style={{
                color: 'white',
                flexWrap: 'wrap',
                backgroundColor: '#234',
            }}
        >
            view padding 50 height 200
        </Text>
    </View>
    <View
        style={{
            backgroundColor: '#888',
            padding: 50,
        }}
    >
        <Text
            style={{
                color: 'white',
                flexWrap: 'wrap',
                backgroundColor: '#234',
            }}
        >
            view padding 50
        </Text>
    </View>
    <View style={{ flex: 1 }} />
</View>

padding-with-height

可以看出height包含了padding在内。

如果padding换成margin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<View
    style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
    }}
>
    <View style={{ backgroundColor: '#eaeaea' }}>
        <View
            style={{
                backgroundColor: '#234',
                margin: 50,
                height: 200,
            }}
        >
            <Text
                style={{
                    color: 'white',
                    flexWrap: 'wrap',
                }}
            >
                view margin 50 height 200
            </Text>
        </View>
    </View>
    <View style={{ backgroundColor: '#aaaaaa' }}>
        <View
            style={{
                backgroundColor: '#234',
                height: 200,
            }}
        >
            <Text
                style={{
                    color: 'white',
                    flexWrap: 'wrap',
                }}
            >
                view margin 0 height 200
            </Text>
        </View>
    </View>
    <View style={{ flex: 1 }} />
</View>

margin-with-height

图片中的View(蓝色区域)高度均为 200,而添加了margin之后,其实际高度未变,即 height 的计算是不包括margin

参考