欢迎回到本系列,您将在其中学习如何使用 react Native 创建移动应用程序中常用的页面布局。你创建的布局不会是功能性的——相反,本系列的主要重点是让你在 React Native 应用程序中布局内容。
为了跟随本系列,我挑战您先尝试自己重新创建每个屏幕,然后再阅读本教程中的分步说明。仅仅通过阅读本教程,您不会真正受益于它!在此处查找答案之前先尝试。如果你成功地让它看起来像原始屏幕,将你的实现与我的进行比较。然后自己决定哪个更好!
在本系列的最后一个教程中,您将创建以下新闻提要页面:
新闻提要布局用于以易于扫描的方式呈现信息。大多数情况下,它以列表格式显示,带有标题、摘录和可选的代表每个新闻项目的预览图像。
以下是在野外使用的此类布局的几个示例:
项目设置
第一步,当然是建立一个新的 React Native 项目:
react-native init react-native-common-screens
设置项目后,打开 文件并将默认代码替换为以下内容:index.android.js
import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import News from './src/pages/News'; export default class ReactNativeCommonScreens extends Component { render() { return ( <News /> ); } } AppRegistry.registerComponent('ReactNativeCommonScreens', () => ReactNativeCommonScreens);
创建一个 src/pages
文件夹并 News.js
在其中创建一个文件。
你还需要这个 react-native-vector-icons
包。这专门用于标题中的后退图标。
npm install --save react-native-vector-icons
打开 android/app/build.gradle
文件并添加对包的引用:
dependencies { //rest of the dependencies are here at the top compile project(':react-native-vector-icons') //add this }
android/settings.gradle
通过在底部添加以下内容对文件执行相同 操作:
include ':react-native-vector-icons' project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
打开 android/app/src/main/java/com/react-native-common-screens/MainApplication.java
并导入包:
import java.util.Arrays; import java.util.List; import com.oblador.vectoricons.VectorIconsPackage; //add this
最后,初始化包:
@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new VectorIconsPackage() //add this ); }
创建新闻页面
好的,现在您已经尝试自己编写布局代码(没有作弊,对吗?)我将向您展示我是如何构建我的实现的。
你现在一定已经注意到了这种趋势。我已经根据难度安排了这些 - 或者至少根据我发现的困难!所以这个最终屏幕基本上是您迄今为止创建的其他屏幕中的大老板。不过不要担心,因为我仍然会一步一步地指导你。
每个新闻项目的预览图像都需要一些图像。我在 repo 中添加了一些图像,如果你愿意,你可以使用它们。
首先添加样板代码:
import React, { Component } from 'react'; import { StyleSheet, Text, View, ScrollView, Image } from 'react-native'; import Icon from 'react-native-vector-icons/fontAwesome'; import Button from '../components/Button'; import NewsItem from '../components/NewsItem';
这次有一个名为 NewsItem
( src/components/NewsItem
) 的新组件。顾名思义,它用于渲染每个新闻项目。我们稍后会回到它,但首先看一下 constructor()
函数。就像之前的画廊屏幕一样,它使用状态来存储新闻项目的数据源。标题和摘要来自 《纽约时报》,但图片来自 Google 图片(并标记为供其各自所有者重复使用)。
constructor(props) { super(props); this.state = { news_items: [ { pretext: 'Gray Matter', title: 'Art Makes You Smart', summary: 'Museum visits increase test scores, generate social responsibility and increase appreciation of the arts by students.', image: require('../images/pink.jpg'), }, { pretext: '', title: 'Tension and Flaws Before Health Website Crash', summary: 'Interviews and documents offer new details into how the rollout of President Obama\'s signature program turned into a major humiliation.', image: require('../images/beach.jpg') }, { pretext: '', title: '36 Hours in Charleston, S.C.', summary: 'Crowds are thinner and temperatures are mild during winter in this..', image: require('../images/rails.jpg') }, ] }; }
内容分为三部分:标题、说明文本和新闻项目。标题与前面日历屏幕的标题非常相似;唯一的区别是只有两个可见元素,而不是三个。(如果您想重新了解日历屏幕的制作方法,请继续阅读该教程。)
我说“可见”是因为实际上有三个元素——最后一个是隐藏的!这允许文本在中间轻松居中。如果标题中只有两个元素,那么很难弄清楚如何在两个元素之间划分空间并且仍然让中间的元素居中。但是如果你有三个元素,每个元素都可以有相同的 flex
值,你可以只 textAlign
用来定位文本或 alignItems
定位 View
组件。
render() { return ( <View style={styles.container}> <View style={styles.header}> <Button noDefaultStyles={true} styles={{button: styles.header_button}} onPress={this.press.bind(this)} > <View style={styles.back_button}> <Icon name="chevron-left" size={20} color="#397CA9" /> <Text style={[styles.back_button_label]}> Sections</Text> </View> </Button> <View style={styles.header_text}> <Text style={styles.header_text_label}>Most E-Mailed</Text> </View> <View style={styles.whitespace}></View> </View> <View style={styles.instruction}> <Text style={styles.instruction_text}>SWIPE ACROSS SECTIONS</Text> </View> <ScrollView style={styles.news_container}> { this.renderNews() } </ScrollView> </View> ); }
该 renderNews()
函数是循环遍历状态中的所有新闻项并使用 NewsItem
组件呈现它们的函数。
renderNews() { return this.state.news_items.map((news, index) => { return <NewsItem key={index} index={index} news={news} /> }); }
接下来是 NewsItem
组件的代码。首先添加样板 React 组件代码。正如你之前看到的,这个组件接受 key
, index
, 和 news
作为它的 props。你只需要 index
and news
。 key
只是 React Native 唯一标识列表中每一行的方式。每次 Array.map
用于渲染时都需要提供;否则,它会抱怨。
当您使用函数式组件时,props 作为单个参数传递。下面,使用 解构赋值提取单个道具,因此 { news, index }
基本上从道具中提取 news
和 index
属性。从那里你可以得到要渲染的数字。
import React, { Component } from 'react'; import { StyleSheet, Text, View, Image } from 'react-native'; import Button from './Button'; const NewsItem = ({ news, index }) => { let number = (index + 1).toString(); return ( ... ); }
如果您查看之前的屏幕截图,您可以看到每个新闻条目可以分为两组:一组显示新闻文本(编号、标题和摘录),另一组显示专题图片。
这解决了特征图像的问题,因为它只是一个元素。但是对于新闻文本,你还是要进一步划分。您可能已经注意到,即使标题有借口(例如“Gray Matter”),数字也处于相同的位置。借口也具有与标题和数字不同的样式。
使用这些知识,您可以推断出不应将数字、借口和标题放在一个容器中。此外,借口、标题和摘录看起来好像是垂直堆叠的,因此您可以将它们放在一个容器中。只有数字应该被拿出来。这样,您将获得以下标记:
<Button key={index} noDefaultStyles={true} onPress={onPress.bind(this, news)} > <View style={styles.news_item}> <View style={styles.news_text}> <View style={styles.number}> <Text style={styles.title}>{number}.</Text> </View> <View style={styles.text_container}> { getPretext(news) } <Text style={styles.title}>{news.title}</Text> <Text>{news.summary}</Text> </View> </View> <View style={styles.news_photo}> <Image source={news.image} style={styles.photo} /> </View> </View> </Button>
该 getPretext()
功能允许您 Text
仅在新闻项目中有一个借口时有条件地呈现组件。
function getPretext(news) { if(news.pretext){ return ( <Text style={styles.pretext}>{news.pretext}</Text> ); } }
这是 onPress
功能。它所做的只是提醒新闻标题,但在真正的应用程序中,它应该导航到实际文章:
function onPress(news) { alert(news.title); }
此时,页面现在将如下所示:
现在,将以下样式添加到新闻页面:
const styles = StyleSheet.create({ container: { flex: 1 }, header: { flexDirection: 'row', backgroundColor: '#FFF', padding: 20, justifyContent: 'space-between', borderBottomColor: '#E1E1E1', borderBottomWidth: 1 }, header_button: { flex: 1, }, whitespace: { flex: 1 }, back_button: { flexDirection: 'row', alignItems: 'center' }, back_button_label: { color: '#397CA9', fontSize: 20, }, instruction: { alignSelf: 'center', marginTop: 5 }, instruction_text: { color: '#A3A3A3' }, header_text: { flex: 1, alignSelf: 'center' }, header_text_label: { fontSize: 20, textAlign: 'center' }, news_container: { flex: 1, flexDirection: 'column' }, });
我将不再向您介绍每行代码的作用,因为它基本上应用了您在本系列前面的教程中学到的相同概念。应用上述样式后,页面将如下所示:
接下来,为每个新闻项目添加样式。每个 news_item
都有一个 flexDirection
of, row
以便新闻文本和特色图像都在一行上。 news_text
占用三分之二的可用空间,而 news_photo
占用剩余空间。
const styles = StyleSheet.create({ news_item: { flex: 1, flexDirection: 'row', paddingRight: 20, paddingLeft: 20, paddingTop: 30, paddingBottom: 30, borderBottomWidth: 1, borderBottomColor: '#E4E4E4' }, news_text: { flex: 2, flexDirection: 'row', padding: 10 }, });
接下来,您需要添加样式以解决文本与预览图像重叠的问题。您可以通过flex
为news_text
. flex
已经给 赋值了一个值news_text
,但是由于View
在其中使用了 a ,你还需要给它们赋值flex
,这样它们就不会越过它们的 parent 的范围View
。
我们将为 分配一个 flex 值 0.5
, number
而 text_container
将具有 3
. 有了这些值, text_container
将占据 6 倍的空间 number
。
number: { flex: 0.5, }, text_container: { flex: 3 },
现在您所要做的就是添加最后的润色以设置文本样式:
pretext: { color: '#3F3F3F', fontSize: 20 }, title: { fontSize: 28, fontWeight: 'bold', color: '#000', fontFamily: 'georgia' }, news_photo: { flex: 1, justifyContent: 'center', alignItems: 'center' }, photo: { width: 120, height: 120 }
并且不要忘记导出组件!
export default NewsItem;
最后的想法
而已!在本系列的最后一部分中,您学习了如何实现新闻页面中常用的布局。本教程汇集了您在本系列前面部分中学到的所有内容。您已经使用了 flexDirection: 'row'
和flexDirection: 'column'
来完成每个新闻项目的样式。您还利用自己的知识为预览图像对齐图像。