十四、使用 Flexbox 构建响应性布局
在本章中,您将了解在移动设备屏幕上布局组件的感觉。谢天谢地,React Native polyfills 填充了许多 CSS 属性,您过去可能使用这些属性在 web 应用中实现页面布局。我们将使用 flexbox 模型来布局 React 本机屏幕。
在我们深入研究实现布局之前,您将了解 flexbox 和在 React 本机应用中使用 CSS 样式属性的简要入门知识—这与您对常规 CSS 样式表的习惯不同。这样,您将使用 flexbox 实现几个 React 本机布局。
Flexbox 是新的布局标准
在将 flexiblebox 布局模型引入 CSS 之前,构建布局的各种方法都感觉很粗糙,而且容易出错。Flexbox 通过抽象通常必须提供的许多属性来修复此问题,以使布局正常工作。
从本质上讲,flexbox 就是它听起来的样子——一个灵活的盒子模型。这就是 flexbox 的优点,它很简单。您有一个充当容器的框,并且该框中有子元素。容器和子元素在屏幕上的渲染方式都很灵活,如下图所示:
Flexbox 容器有一个方向,即列(上/下)或行(左/右)。当我第一次学习 flexbox 时,这让我感到困惑:我的大脑拒绝相信行是从左向右移动的。一排排叠在一起!要记住的关键是盒子的弯曲方向,而不是盒子在屏幕上的放置方向。
注
有关 flexbox 概念的更深入的介绍,请查看此页面:https://css-tricks.com/snippets/css/a-guide-to-flexbox/ 。
引进本土风格
是时候实现您的第一个 React 本机应用了,超越react-native init
生成的样板文件。在下一节开始实现 flexbox 布局之前,我想确保您对使用 React 原生样式表感到满意。以下是 React 本机样式表的外观:
import { StyleSheet } from 'react-native';
// Exports a "stylesheet" that can be used
// by React Native components. The structure is
// familiar for CSS authors.
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'ghostwhite',
},
box: {
width: 100,
height: 100,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'lightgray',
},
boxText: {
color: 'darkslategray',
fontWeight: 'bold',
},
});
export default styles;
这是一个 JavaScript 模块,不是 CSS 模块。如果要声明 React 本机样式,则需要使用普通对象。然后,调用StyleSheet.create()
并将其从样式模块导出。
如您所见,此样式表有三种样式:container
、box
和boxText
。让我们看看如何导入这些样式并将其应用于本机组件:
import React from 'react';
import {
AppRegistry,
Text,
View,
} from 'react-native';
// Imports the "styles" stylesheet from the
// "styles" module.
import styles from './styles';
// Renders a view with a square in the middle, and
// some text in the middle of that. The "style" property
// is passed a value from the "styles" stylesheet.
const Stylesheets = () => (
<View style={styles.container}>
<View style={styles.box}>
<Text style={styles.boxText}>
I'm in a box
</Text>
</View>
</View>
);
AppRegistry.registerComponent(
'Stylesheets',
() => Stylesheets
);
如您所见,样式通过style
属性指定给每个组件。我们试图在屏幕中间渲染一个带有文本的盒子。让我们确保这看起来与我们预期的一样:
完美的现在您已经了解了如何在 React 原生元素上设置样式,现在是开始创建一些屏幕布局的时候了。
建筑柔性箱布局图
在本节中,我们将介绍几个可以在 React 本机应用中使用的潜在布局。我不想认为一种布局比其他布局更好。相反,我将向您展示 flexbox 布局模型对于移动屏幕的强大功能,以便您可以设计最适合您的应用的布局。
简单的三列布局
首先,让我们实现一个简单的布局,其中有三个部分沿柱的方向(从上到下)伸缩。让我们先看一下生成的屏幕:
本例的想法是,我们对三个屏幕部分进行了样式设置和标记,使它们脱颖而出。换句话说,这些组件在实际应用中不一定有任何样式,因为它们用于在屏幕上排列其他组件。
这样说,让我们来看看用于创建这个屏幕布局的组件:
import React from 'react';
import {
AppRegistry,
Text,
View,
} from 'react-native';
import styles from './styles';
// Renders three "column" sections. The "container"
// view is styled so that it's children flow from
// the top of the screen, to the bottom of the screen.
const ThreeColumnLayout = () => (
<View style={styles.container}>
<View style={styles.box}>
<Text style={styles.boxText}>
#1
</Text>
</View>
<View style={styles.box}>
<Text style={styles.boxText}>
#2
</Text>
</View>
<View style={styles.box}>
<Text style={styles.boxText}>
#3
</Text>
</View>
</View>
);
AppRegistry.registerComponent(
'ThreeColumnLayout',
() => ThreeColumnLayout
);
如您所见,容器视图是列,子视图是行。<Text>
组件用于标记每一行。
注
这个例子可能被称为三行布局,因为它有三行。但同时,这三个布局部分正朝着它们所在的柱的方向弯曲。使用对您最具概念意义的命名约定。
现在让我们来看看用于创建这种布局的样式:
import { StyleSheet } from 'react-native';
// Exports a "stylesheet" that can be used
// by React Native components. The structure is
// familiar for CSS authors.
const styles = StyleSheet.create({
// The "container" for the whole screen.
container: {
// Enables the flexbox layout model...
flex: 1,
// Tells the flexbox to render children from
// top to bottom...
flexDirection: 'column',
// Aligns children to the center on the container...
alignItems: 'center',
// Defines the spacing relative to other children...
justifyContent: 'space-around',
backgroundColor: 'ghostwhite',
},
box: {
width: 300,
height: 100,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'lightgray',
borderWidth: 1,
borderStyle: 'dashed',
borderColor: 'darkslategray',
},
boxText: {
color: 'darkslategray',
fontWeight: 'bold',
},
});
export default styles;
容器的flex
和flexDirection
属性使行的布局从上到下流动。alignItems
和justifyContent
属性分别将子元素与容器中心对齐,并在其周围添加空间。
让我们看看当您将设备从纵向旋转到横向时,此布局的外观:
哇,这真是太方便了,flexbox 自动为我们找到了保存布局的方法。不过,我认为我们可以在这方面做一些改进。例如,横向方向在左侧和右侧有大量浪费的空间。我们还可以为正在渲染的框创建自己的抽象。
改进的三列布局
我认为我们可以从上一个例子中改进一些东西。首先,让我们去掉index.ios.js
和index.android.js
文件中的重复代码。我们将创建一个新的index.js
文件并将代码放在那里。因为它不特定于任何一个平台,所以不需要特殊的名称。现在index.ios.js
和index.android.js
文件中都有一行:
import './index.js';
接下来,让我们修复样式,以便 flexbox 的子项拉伸以利用可用空间。还记得上一个示例中,当您将设备从纵向旋转到横向时吗?浪费了很多空间。这将是很好的组件自动调整自己。以下是新样式模块的外观:
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'ghostwhite',
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 100,
justifyContent: 'center',
// Instead of given the flexbox child a width, we
// tell it to "stretch" to fill all available space.
alignSelf: 'stretch',
alignItems: 'center',
backgroundColor: 'lightgray',
borderWidth: 1,
borderStyle: 'dashed',
borderColor: 'darkslategray',
},
boxText: {
color: 'darkslategray',
fontWeight: 'bold',
},
});
export default styles;
这里的关键变化是alignSelf
属性。这告诉具有box
样式的元素更改其宽度或高度(取决于其容器的flexDirection
来填充空间)。另外,请注意,box
样式不再定义width
属性,因为这将是动态计算的。以下是纵向模式下各部分的外观:
现在,每个部分都占据了整个屏幕的宽度,这正是我们想要实现的。浪费空间的问题实际上在横向方向上更为普遍,因此让我们旋转设备,看看这些部分现在会发生什么:
美好的现在,无论方向如何,您的布局都将利用屏幕的整个宽度。最后,让我们实现一个适当的Box
组件,该组件可以由index.js
使用,而不是在适当的位置设置重复的样式属性。以下是Box
组件的外观:
import React, { PropTypes } from 'react';
import { View, Text } from 'react-native';
import styles from './styles';
// Exports a React Native component that
// renders a "<View>" with the "box" style
// and a "<Text>" component with the "boxText"
// style.
const Box = ({ children }) => (
<View style={styles.box}>
<Text style={styles.boxText}>
{children}
</Text>
</View>
);
Box.propTypes = {
children: PropTypes.node.isRequired,
};
export default Box;
我们已经开始了一个很好的布局。现在是时候将注意力集中在从左到右的另一个方向上了。
柔性排
在本节中,您将学习如何使屏幕布局部分从上到下伸展。为此,您需要一个灵活的行。以下是此屏幕的样式:
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
// Tells the child elements to flex from left to
// right...
flexDirection: 'row',
backgroundColor: 'ghostwhite',
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
// There's no height, so "box" elements will stretch
// from top to bottom.
width: 100,
justifyContent: 'center',
alignSelf: 'stretch',
alignItems: 'center',
backgroundColor: 'lightgray',
borderWidth: 1,
borderStyle: 'dashed',
borderColor: 'darkslategray',
},
boxText: {
color: 'darkslategray',
fontWeight: 'bold',
},
});
export default styles;
这是 React 本机组件,使用与您在上一节中实现的相同的Box
组件:
import React from 'react';
import {
AppRegistry,
View,
} from 'react-native';
import styles from './styles';
import Box from './Box';
// Renders a single row with two boxes that stretch
// from top to bottom.
const FlexibleRows = () => (
<View style={styles.container}>
<Box>#1</Box>
<Box>#2</Box>
</View>
);
AppRegistry.registerComponent(
'FlexibleRows',
() => FlexibleRows
);
下面是在纵向模式下生成的屏幕的外观:
由于alignSelf
属性,这两列从屏幕顶部一直延伸到屏幕底部,但实际上并没有说明延伸方向。这两个Box
组件从上到下伸展,因为它们显示在一个弹性行中。注意这两个部分之间的间距是如何从左到右的?这是因为容器的flexDirection
属性的值为row
。
现在,让我们看看当屏幕旋转为横向时,该弯曲方向如何影响布局:
由于 flexbox 的justifyContent
样式属性值为space-around
,因此空间按比例添加到左侧、右侧以及各部分之间。
柔性格栅
有时你需要一个像网格一样流动的屏幕布局。例如,如果您有几个宽度和高度相同的部分,但不确定其中有多少部分将被渲染,该怎么办?flexbox 可以轻松构建从左向右流动的行,直到到达屏幕的末端。然后,它会自动在下一行从左到右继续渲染元素。下面是纵向模式下的布局示例:
这种方法的优点在于,您不需要事先知道给定行中有多少列。每个子对象的尺寸决定了在给定的行中适合什么。让我们来看看用于创建这种布局的样式:
import { StyleSheet } from 'react-native';
// Exports a "stylesheet" that can be used
// by React Native components. The structure is
// familiar for CSS authors.
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
// The child elements of this container will flow
// from left to right. The "wrap" value here will
// make the row jump to the next row, and start
// flowing from left to right again.
flexWrap: 'wrap',
backgroundColor: 'ghostwhite',
alignItems: 'center',
},
box: {
// When there's an exact width and height for
// a flexbox child, there's no need to "stretch" it.
height: 100,
width: 100,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'lightgray',
borderWidth: 1,
borderStyle: 'dashed',
borderColor: 'darkslategray',
// Margins usually work better than "space-around"
// for grids.
margin: 10,
},
boxText: {
color: 'darkslategray',
fontWeight: 'bold',
},
});
export default styles;
下面是呈现每个部分的 React 本机组件:
import React from 'react';
import {
AppRegistry,
View,
} from 'react-native';
import styles from './styles';
import Box from './Box';
// An array of 10 numbers, representing the grid
// sections to render.
const boxes = new Array(10)
.fill(null)
.map((v, i) => i + 1);
const FlexibleGrids = () => (
<View style={styles.container}>
{/* Renders 10 "<Box>" sections */}
{boxes.map(i => (
<Box key={i}>#{i}</Box>
))}
</View>
);
AppRegistry.registerComponent(
'FlexibleGrids',
() => FlexibleGrids
);
最后,让我们确保横向方向与此布局配合使用:
注
您可能已经注意到右侧有一些多余的空间。请记住,这些部分仅在本书中可见,因为我们希望它们可见。在一个真正的应用中,它们只是对其他本地组件进行分组。但是,如果屏幕右侧的空间成为一个问题,请调整子组件的边距和宽度。
灵活的行和列
在本章的最后一节中,我们将结合行和列为屏幕创建复杂的布局。例如,有时您需要能够在行中嵌套列或在列中嵌套行。让我们来看看在行中嵌套列的应用的主要模块:
import React, { Component } from 'react';
import {
AppRegistry,
View,
} from 'react-native';
import styles from './styles';
import Row from './Row';
import Column from './Column';
import Box from './Box';
class FlexibleRowsAndColumns extends Component {
render() {
return (
<View style={styles.container}>
{/* This row contains two columns. The first column
has boxes "#1" and "#2". They will be stacked on
top of one another. The next column has boxes
"#3" and "#4", which are also stacked on top
of one another */}
<Row>
<Column>
<Box>#1</Box>
<Box>#2</Box>
</Column>
<Column>
<Box>#3</Box>
<Box>#4</Box>
</Column>
</Row>
<Row>
<Column>
<Box>#5</Box>
<Box>#6</Box>
</Column>
<Column>
<Box>#7</Box>
<Box>#8</Box>
</Column>
</Row>
<Row>
<Column>
<Box>#9</Box>
<Box>#10</Box>
</Column>
<Column>
<Box>#11</Box>
<Box>#12</Box>
</Column>
</Row>
</View>
);
}
}
AppRegistry.registerComponent(
'FlexibleRowsAndColumns',
() => FlexibleRowsAndColumns
);
这实际上很容易解析和理解,因为我们已经为布局块(<Row>
和<Column>
以及内容块(<Box>
创建了抽象。我们将立即检查这些部分,但现在,让我们看看此屏幕的外观:
这个布局可能看起来很熟悉,因为我们已经在本章中完成了。关键区别在于这些内容部分的排序方式。例如,【2】不在【1】的左侧,而是在其下方。这是因为我们将和放在了<Column>
中。与3和4相同。这两列放置在一行中。然后下一行开始,依此类推。****
****这只是通过嵌套行 FlexBox 和列 FlexBox 可以实现的许多可能布局之一。让我们看一下现在的 To0t0.组件:
import React, { PropTypes } from 'react';
import { View } from 'react-native';
import styles from './styles';
// Renders a "View" with the "row" style applied to
// it. It's "children" will flow from left to right.
const Row = ({ children }) => (
<View style={styles.row}>
{children}
</View>
);
Row.propTypes = {
children: PropTypes.node.isRequired,
};
export default Row;
除了将row
样式应用于<View>
组件之外,它没有什么作用。正如您在主模块中看到的,这实际上是一个大问题,因为它导致更干净的 JSX 标记。最后,让我们看一下Column
组件:
import React, { PropTypes } from 'react';
import { View } from 'react-native';
import styles from './styles';
// Renders a "View" with the "column" style applied
// to it. It's children will flow from top-to-bottom.
const Column = ({ children }) => (
<View style={styles.column}>
{children}
</View>
);
Column.propTypes = {
children: PropTypes.node.isRequired,
};
export default Column;
同样,这看起来就像Row
组件,只是应用了不同的样式。但这意味着在其他模块中易于布局。
总结
本章介绍 React Native 中的样式。虽然您可以使用许多与以前相同的 CSS 样式属性,但 web 应用中使用的 CSS 样式表看起来非常不同。也就是说,它们由普通 JavaScript 对象组成。
然后,您学习了如何使用 flexbox 的主要布局机制。这是当今布局大多数 web 应用的首选方法,因此,只有能够在本机应用中重用这种方法才有意义。您创建了几个不同的布局,并看到了它们在纵向和横向方向上的外观。
在下一章中,您将开始为应用实现导航。****
版权属于:月萌API www.moonapi.com,转载请注明出处