React Native 布局

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

尺寸

React Native#Dimensions

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

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,实现水平居中、垂直居中、水平垂直居中。

<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

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

<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

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

<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

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

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的属性值来决定布局,示例如下:

<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,子元素按直接父级进行定位。

<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元素。

<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:

<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:

<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

参考