React Native 小计
布局
手机端布局通常采用 flex 弹性盒子, View 容器组件默认就是弹性盒子.
- 主轴的布局方向:
flex-direction
- row: 横向
- row-reverse: 横向逆向
- column: 竖向,
默认值
- column-reverse: 竖向逆向
- 主轴的排版方向:
justify-content
- flex-start: 起始对齐
- flex-end: 结尾对齐
- center: 居中
- space-between: 中间留白
- space-around: 空白环绕子元素周围
- space-evenly: 空白均分
- 交叉轴:
align-items
- flex-start: 起始对齐
- flex-end: 结尾对齐
- center: 居中
- stretch: 拉伸
- 子元素的交叉轴:
align-self
- 子元素占据留白的份数:
flex
- 换行:
flex-wrap
rpx
相对单位, 把屏幕的实际宽度分为 750 份. 符合美工提供的效果图上的宽度.
// 获取宽度
const { width, height } = Dimension.get("screen");
const rpx = (x) => (width / 750) * x;
文本组件
Text
:
- 只有文本组件的样式 才能继承给子文本组件; 其它容器类组件都没有继承
- 属性:
numberOfLines
行数
图片组件
Image
- 网络图:
<Image source={{uri: 图片地址}}
必须给宽高, 默认为 0 - 本地图:
<Image source={require(图片地址)}
背景图
ImageBackground
: 必须给宽高
状态栏
StatusBar
: 利用 background
为透明(transparent), 可穿透:translucent
实现沉浸式体验
高度: StatusBar.currentHeight
按钮
系统按钮: <Button title="标题" onPress={点击事件}
; 不能订制样式
自定义按钮: <TouchableOpacity>
样式的写法
// App.js
// rnc
import React, { Component } from "react";
// RN的组件必须引入之后才能使用, 只能使用 RN 提供的组件, 不能用div span
// 只有RN的组件才能 被编译成 android 或 iOS 原生代码
import { Text, View, StyleSheet } from "react-native";
// 样式有两种写法: 内联style 和 内部样式, 没有外部css文件
export default class App extends Component {
render() {
return (
<View>
{/* 手机端 大小没有单位, 默认为 物理像素 */}
<Text style={{ fontSize: 30 }}> Hello World! </Text>
{/* 内联样式 会导致 JSX 语法过于复杂, 提取到外部书写更加易读 */}
<Text style={ss.danger}>Danger</Text>
{/* 多样式, 利用 数组 */}
<Text style={[ss.success, ss.strong]}>Success</Text>
</View>
);
}
}
// 内部样式
const ss = StyleSheet.create({
danger: {
color: "red",
fontSize: 30,
},
success: {
color: "green",
fontSize: 40,
},
strong: {
backgroundColor: "yellow",
},
});
布局
// App.js
// rncs
import React, { Component } from "react";
import { Text, StyleSheet, View } from "react-native";
export default class App extends Component {
render() {
return (
// View 容器默认为 弹性盒子布局, 竖向排列 横向拉伸
<View style={ss.box}>
<Text style={ss.one}>One</Text>
<Text style={ss.two}>Two</Text>
<Text style={ss.three}>Three</Text>
</View>
);
}
}
const ss = StyleSheet.create({
box: {
backgroundColor: "pink",
height: 400,
//盒子内容的 主轴方向
flexDirection: "column-reverse", // 竖向 逆向
flexDirection: "row", // 横向
flexDirection: "row-reverse", //横向逆向
flexDirection: "column", //默认 竖向
// 交叉轴布局
alignItems: "stretch", //默认,拉伸充满
alignItems: "flex-start", //交叉轴起始对齐
alignItems: "flex-end", //交叉轴结尾对齐
alignItems: "center", //居中
// 主轴
justifyContent: "flex-start", //起始对齐
justifyContent: "flex-end", //结尾对齐
justifyContent: "center", //居中
justifyContent: "space-evenly", //均分留白
justifyContent: "space-between", //中间留白
justifyContent: "space-around", //元素环绕留白
},
one: {
backgroundColor: "green",
padding: 20,
fontSize: 30,
// 单独布局 交叉轴对齐方式
alignSelf: "stretch",
// flex: 设置子元素 在主轴上 占据剩余空间的份数
flex: 1,
},
two: {
flex: 1,
backgroundColor: "blue",
padding: 20,
fontSize: 30,
},
three: {
flex: 1,
backgroundColor: "red",
padding: 20,
fontSize: 30,
},
});
折行 宫格布局
// App.js
// rncs
import React, { Component } from "react";
import { Text, StyleSheet, View } from "react-native";
export default class App extends Component {
render() {
return (
<View style={ss.box}>
{[1, 2, 3, 4, 5, 6].map((item, index) => (
<Text style={ss.item} key={index}>
{item}
</Text>
))}
</View>
);
}
}
const ss = StyleSheet.create({
box: {
backgroundColor: "pink",
height: 400,
flexDirection: "row",
alignItems: "flex-start",
//横向布局: 可以折行
flexWrap: "wrap",
// 主轴方向, 空白均分
justifyContent: "space-evenly",
},
item: { padding: 66, borderWidth: 2, marginTop: 10 },
});
相对大小
rpx
// App.js
// rnc
import React, { Component } from "react";
import { Dimensions, Text, View } from "react-native";
/**
* rpx: 微信小程序中, 把屏幕的实际大小分为 750 份
* rpx: 相对像素, 为了适配不同的手机大小;
* 通常UI工程师 会提供效果图, 宽度设计为750
* 我们需要把手机的实际宽度 分成 750 份, 然后就可以根据份数算出每个元素的实际大小
*/
const { width, height } = Dimensions.get("screen");
// 分成750份
const rpx = (x) => (width / 750) * x;
// 例如 200份的实际像素宽度: rpx(200)
export default class App extends Component {
render() {
return (
<View>
<Text style={{ width: rpx(375), backgroundColor: "red", height: 50 }}>
textInComponent
</Text>
<Text style={{ width: 240, backgroundColor: "blue", height: 50 }}>
textInComponent
</Text>
</View>
);
}
}
文本组件
Text
// App.js
// rnc
// 文本组件 Text
import React, { Component } from "react";
import { Text, View } from "react-native";
export default class App extends Component {
render() {
return (
// 在 RN 中, 样式不能继承, 除了 Text 组件
<View style={{ color: "blue" }}>
<Text style={{ color: "red", fontSize: 50 }}>
¥<Text style={{ fontSize: 70 }}>999</Text>
</Text>
{/* numberOfLines: 值为number类型, 必须用{} 只有js才有类型概念 */}
<Text style={{ fontSize: 30 }} numberOfLines={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dignissimos
aut illo quos cum maiores commodi? Quidem, illum quasi. Aut ab
voluptas ut, deleniti praesentium vero quam laborum iste ex
temporibus.
</Text>
</View>
);
}
}
图片
Image
// App.js
// rnc
// 图片组件
import React, { Component } from "react";
import { Image, Text, View } from "react-native";
export default class App extends Component {
url =
"https://ss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/b3119313b07eca80ceedb766902397dda0448343.jpg";
render() {
return (
<View>
{/* 注意: uri 不是url */}
{/* 网络图 默认宽高为0, 必须手动指定宽高 */}
<Image source={{ uri: this.url }} style={{ width: 200, height: 200 }} />
{/* 本地图: 需要新建 assets 目录, 用于存放图片 */}
{/* 默认具有宽高, 与原图一致 */}
<Image source={require("./assets/2.png")} />
</View>
);
}
}
背景图+状态栏: 沉浸式体验
ImageBackground
// App.js
// rnc
// 背景图+状态栏
import React, { Component } from "react";
import {
Text,
View,
ImageBackground,
Dimensions,
StatusBar,
} from "react-native";
// 手机端宽高 分两种: screen屏幕 和 window窗口
const { width, height } = Dimensions.get("screen");
export default class App extends Component {
bgUrl =
"https://c-ssl.duitang.com/uploads/item/201510/01/20151001105223_EdQT3.thumb.1000_0.jpeg";
render() {
return (
// 背景图 必须给宽高
<ImageBackground source={{ uri: this.bgUrl }} style={{ width, height }}>
{/* 沉浸式体验 */}
{/* transparent: 透明 */}
{/* translucent: 穿透效果, 背景就会移动到状态栏底部 */}
<StatusBar backgroundColor="transparent" translucent />
{/* 状态栏的位置应该保留下来, 防止内容重叠 */}
<View style={{ height: StatusBar.currentHeight }} />
<Text style={{ fontSize: 60, backgroundColor: "green" }}>
您将会看到本文穿透状态栏
</Text>
</ImageBackground>
);
}
}
按钮
Button
// App.js
// rnc
// 按钮: 按钮分两种-- 系统按钮 和 自定义按钮
import React, { Component } from "react";
import { Text, View, Button, TouchableOpacity } from "react-native";
export default class App extends Component {
render() {
return (
<View style={{ paddingTop: 50, alignItems: "center" }}>
{/* 系统按钮无法自定义样式 */}
<Button title="我是按钮" onPress={() => alert("按钮警告!")} />
{/* 自定义按钮 */}
{/* TouchableOpacity: 一个可以点击, 带有透明度变化动画的容器 */}
{/* Horizontal:横向, 即左右 */}
{/* Vertical: 竖向, 即上下 */}
<TouchableOpacity
onPress={() => alert("密码警告!")}
// 设置 按下时的透明度, 默认0.3
activeOpacity={0.7}
style={{
paddingHorizontal: 20,
paddingVertical: 10,
backgroundColor: "orange",
borderRadius: 6,
}}
>
<Text style={{ fontSize: 40 }}>忘记密码?</Text>
</TouchableOpacity>
</View>
);
}
}
输入框
// rnc
// 输入框
import React, { Component } from "react";
import { Text, View, TextInput, StyleSheet } from "react-native";
export default class App extends Component {
state = { uname: "dong" };
render() {
return (
// View容器默认: 竖向排列 横向拉伸
<View style={{ alignItems: "center" }}>
{/* 输入框的宽度, 默认会随内容发生变化 */}
<TextInput
value={this.state.uname}
placeholder="请输入账号"
style={ss.input}
// onChangeText={this._unameChanged}
onChangeText={(uname) => this.setState({ uname })}
/>
<Text style={{ fontSize: 25 }}>值:{this.state.uname}</Text>
{/* secureTextEntry: 密文 */}
<TextInput placeholder="请输入密码" secureTextEntry style={ss.input} />
</View>
);
}
// 参数名可以随便写, 但是如果是 uname 就能凑语法糖 {uname: uname}
_unameChanged = (uname) => {
console.log(uname); //到服务器看打印
// this.setState({uname: e});
this.setState({ uname });
// 在实际开发时, 往往使用语法糖居多. {xx: xx} 简化成 {xx}
};
}
const ss = StyleSheet.create({
input: {
borderWidth: 1,
fontSize: 30,
color: "black",
width: 400,
borderRadius: 4,
padding: 5,
margin: 5,
},
});
滚动容器
// rnc
// 滚动容器
import React, { Component } from "react";
import { Text, View, ScrollView } from "react-native";
export default class App extends Component {
showMore() {
let arr = [];
for (let i = 0; i < 100; i++) {
arr.push(
<Text key={i} style={{ fontSize: 28, borderWidth: 1, padding: 10 }}>
{i}
</Text>
);
}
return arr;
}
render() {
return <ScrollView>{this.showMore()}</ScrollView>;
}
}
开关
// rnc
// 开关: 是典型的受控组件, 必须通过额外的代码来控制才能有效
import React, { Component } from "react";
import { Text, View, Switch } from "react-native";
export default class App extends Component {
state = { isOpen: true };
_changed = (isOpen) => {
console.log(isOpen);
// this.setState({isOpen: isOpen});
this.setState({ isOpen });
};
render() {
return (
<View>
<Switch value={this.state.isOpen} onValueChange={this._changed} />
<Switch
value={this.state.isOpen}
onValueChange={(isOpen) => this.setState({ isOpen })}
/>
</View>
);
}
}
网络请求
// rnc
import React, { Component } from "react";
import { Dimensions, Image, ScrollView, Text, View } from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
url = "https://api.apiopen.top/getWangYiNews";
state = { result: [] };
componentDidMount() {
fetch(this.url)
.then((res) => res.json())
.then((res) => {
// debug服务: 当后台内容非常多的时候, 则查看时很不方便;
// 官方提供了专业的debug服务
console.log(res);
this.setState({ result: res.result });
});
}
showNews = () =>
this.state.result.map((item, index) => (
<View
key={index}
style={{ flexDirection: "row", padding: rpx(10), borderBottomWidth: 1 }}
>
<Image
style={{
width: rpx(300),
height: rpx(180),
borderRadius: rpx(6),
}}
source={{ uri: item.image }}
/>
<View
style={{
justifyContent: "space-around",
marginLeft: rpx(10),
flex: 1, //宽度与主轴剩余空间一样
}}
>
<Text numberOfLines={2} style={{ fontSize: rpx(35) }}>
{item.title}
</Text>
<Text style={{ fontSize: rpx(27), color: "gray" }}>
{item.passtime}
</Text>
</View>
</View>
));
render() {
return <ScrollView>{this.showNews()}</ScrollView>;
}
}
FlatList
高性能的列表组件
- 下拉刷新和加载更多
- 多列布局
- 横向滚动
- 表头 表尾 分割线…
// rnc
import React, { Component } from "react";
import { Text, View, FlatList } from "react-native";
export default class App extends Component {
data = ["vue", "react", "angular", "vue", "vuex", "sass", "bootstrap"];
render() {
// FlatList: 属于一个智能化组件, 我们只要提供资源: 数据 和 每一条数据对应的界面, 他就会自动生成对应的列表
/**
* 3个 核心属性
* data: 提供数据数组
* renderItem: 每一个数据项 对应的界面
* keyExtractor: 每一项对应的唯一标识
*/
return (
<FlatList
data={this.data}
// index是number, 返回值要求string. 所以通过 +'' 转字符串
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
/>
);
}
// 参数解构写法
_renderItem = ({ index, item }) => (
<Text style={{ fontSize: 80, padding: 40 }}>{item}</Text>
);
// 数组中的每一项 都会依次来询问自身的样式, 参数为数据
_renderItem1 = (data) => {
console.log(data);
const { index, item } = data;
return <Text style={{ fontSize: 80, padding: 40 }}>{item}</Text>;
};
}
// rnc
import React, { Component } from "react";
import { Dimensions, FlatList, Image, Text, View } from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
url = "https://api.apiopen.top/getWangYiNews";
state = { result: [] };
componentDidMount() {
fetch(this.url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result });
});
}
render() {
return (
/**
* 辅助属性:
* ItemSeparatorComponent: 项目之间的分隔
* ListHeaderComponent: 表头
* ListFooterComponent: 表尾
*/
<FlatList
data={this.state.result}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
ItemSeparatorComponent={this._ItemSeparatorComponent}
ListHeaderComponent={this._ListHeaderComponent}
ListFooterComponent={this._ListFooterComponent}
/>
);
}
_ListFooterComponent = () => (
<View style={{ padding: 20, backgroundColor: "lightgray" }}>
<Text style={{ fontSize: 28 }}>加载中...</Text>
</View>
);
_ListHeaderComponent = () => (
<Text
style={{
padding: 20,
fontSize: 27,
color: "white",
backgroundColor: "red",
}}
>
网易新闻
</Text>
);
_ItemSeparatorComponent = () => (
<View style={{ height: 1, backgroundColor: "gray" }} />
);
_renderItem = ({ item, index }) => (
<View style={{ flexDirection: "row", padding: rpx(5) }}>
<Image
source={{ uri: item.image }}
style={{ width: rpx(300), height: rpx(180), borderRadius: rpx(5) }}
/>
<View
style={{
justifyContent: "space-between",
flex: 1,
marginLeft: rpx(5),
}}
>
<Text style={{ fontSize: rpx(34) }}>{item.title}</Text>
<Text style={{ fontSize: rpx(27), color: "gray" }}>
{item.passtime}
</Text>
</View>
</View>
);
}
加载更多 和 下拉刷新
// rnc
import React, { Component } from "react";
import {
ActivityIndicator,
Dimensions,
FlatList,
Image,
Text,
View,
} from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
url = "https://api.apiopen.top/getWangYiNews";
// refreshing: 用于控制 下拉刷新动画的 显示与否
state = { result: [], refreshing: false };
componentDidMount() {
fetch(this.url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result });
});
}
render() {
return (
/**
* 辅助属性:
* ItemSeparatorComponent: 项目之间的分隔
* ListHeaderComponent: 表头
* ListFooterComponent: 表尾
* onEndReached: 检测触底事件
* onEndReachedThreshold: 触底阈值. 代表未显示区域的高度 是 显示区域的 x百分比时, 触发 触底事件
*/
<FlatList
data={this.state.result}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
ItemSeparatorComponent={this._ItemSeparatorComponent}
ListHeaderComponent={this._ListHeaderComponent}
ListFooterComponent={this._ListFooterComponent}
onEndReached={this._onEndReached}
onEndReachedThreshold={0.1}
// 检测下拉刷新事件
onRefresh={this._onRefresh}
// 下拉刷新动画的显示状态
refreshing={this.state.refreshing}
/>
);
}
_onRefresh = () => {
// alert('下拉刷新!');
this.setState({ refreshing: true });
fetch(this.url)
.then((res) => res.json())
.then((res) => {
console.log(res);
// 新数据替换旧数据 && 结束下拉刷新动画
this.setState({ result: res.result, refreshing: false });
// 还原页数
this.page = 1;
});
};
page = 1; // 记录当前页
_onEndReached = () => {
// alert('触底!');
// 获取下一页数据, 需要传递页数参数 page
// 由于 请求的返回值中 没有提供页数, 那么只能在本地记录页数
const url = this.url + "?page=" + (this.page + 1);
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
//新旧数据合并
this.setState({ result: [...this.state.result, ...res.result] });
//变更当前页
this.page++;
});
};
_ListFooterComponent = () => (
<View style={{ padding: 20, backgroundColor: "lightgray" }}>
{/* 旋转提示 */}
<ActivityIndicator size="large" color="green" />
<Text style={{ fontSize: 28, textAlign: "center" }}>加载中...</Text>
</View>
);
_ListHeaderComponent = () => (
<Text
style={{
padding: 20,
fontSize: 27,
color: "white",
backgroundColor: "red",
}}
>
网易新闻
</Text>
);
_ItemSeparatorComponent = () => (
<View style={{ height: 1, backgroundColor: "gray" }} />
);
_renderItem = ({ item, index }) => (
<View style={{ flexDirection: "row", padding: rpx(5) }}>
<Image
source={{ uri: item.image }}
style={{ width: rpx(300), height: rpx(180), borderRadius: rpx(5) }}
/>
<View
style={{
justifyContent: "space-between",
flex: 1,
marginLeft: rpx(5),
}}
>
<Text style={{ fontSize: rpx(34) }}>{item.title}</Text>
<Text style={{ fontSize: rpx(27), color: "gray" }}>
{item.passtime}
</Text>
</View>
</View>
);
}
import React, { Component } from "react";
import {
Dimensions,
FlatList,
Image,
Text,
View,
StyleSheet,
ActivityIndicator,
TouchableOpacity,
} from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
state = { data: null, refreshing: false, showGoTop: false };
url(page) {
return `https://m.douyu.com/api/room/list?page=${page}&type=LOL`;
}
componentDidMount() {
fetch(this.url(1))
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ data: res.data });
});
}
render() {
if (!this.state.data) return <View />;
return (
<View>
<FlatList
// ref: 用于把组件绑定到属性, 此处就是把 FlatList组件绑定给 fl 属性
// fl属性是自定义的, 名称随便写
ref={(el) => (this.fl = el)}
data={this.state.data.list}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
ItemSeparatorComponent={() => <View style={ss.line} />}
onEndReachedThreshold={0.1}
onEndReached={this._onEndReached}
ListFooterComponent={this._ListFooterComponent}
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
// 监听滚动操作
onScroll={this._onScroll}
/>
{this.showGoTop()}
</View>
);
}
showGoTop() {
if (!this.state.showGoTop) return null;
return (
<TouchableOpacity
onPress={this._goTop}
style={{
padding: rpx(20),
borderRadius: 1000,
backgroundColor: "orange",
position: "absolute",
right: rpx(30),
bottom: rpx(30),
}}
>
<Text>回到</Text>
<Text>顶部</Text>
</TouchableOpacity>
);
}
_onScroll = (event) => {
// ReactNative的事件为同步事件,
// 必须调用 event.persist() 之后才能看到打印的值
// event.persist();
// console.log(event);
const y = event.nativeEvent.contentOffset.y;
console.log(y);
this.setState({ showGoTop: y > 3000 });
};
_goTop = () => {
// 调用 FlatList 的 scrollToIndex 方法, 滚动到第一条即可
// 新的知识点: 把变量 绑定给 组件
/**
* 在 vue中: <el ref="popup" />
* 使用: this.$refs.popup
*
* 在 react 中: <el ref={el => this.popup = el} />
* 此处的 el 就代表当前组件, popup是自定义的属性名
*/
this.fl.scrollToIndex({ index: 0 }); //滚动到序号0 的元素
};
_onRefresh = () => {
this.setState({ refreshing: true });
fetch(this.url(1))
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ data: res.data, refreshing: false });
});
};
// 代码复杂 , 就无法使用语法糖
_ListFooterComponent = () => {
const { nowPage, pageCount } = this.state.data;
// 如果 当前页 和 总页数相同, 则显示: 没有更多数据
if (nowPage == pageCount) {
return (
<Text
style={{
backgroundColor: "rgb(243,243,243)",
padding: rpx(20),
fontSize: rpx(30),
textAlign: "center",
}}
>
没有更多数据了
</Text>
);
}
return (
<View style={{ padding: rpx(10) }}>
<ActivityIndicator size="large" />
<Text style={{ fontSize: rpx(28), textAlign: "center" }}>
加载中...
</Text>
</View>
);
};
_onEndReached = () => {
const { nowPage, list, pageCount } = this.state.data;
// 如果当前页 和 总页数一样, 则终止函数执行
if (nowPage == pageCount) return;
fetch(this.url(nowPage + 1))
.then((res) => res.json())
.then((res) => {
console.log(res);
//合并新旧数据
res.data.list = [...list, ...res.data.list];
this.setState({ data: res.data });
});
};
_renderItem = ({ item, index }) => (
<View style={{ padding: rpx(5), flexDirection: "row" }}>
<Image
source={{ uri: item.roomSrc }}
style={{ width: rpx(300), height: rpx(200), borderRadius: rpx(5) }}
/>
<View
style={{ flex: 1, justifyContent: "space-between", marginLeft: rpx(5) }}
>
<Text style={{ fontSize: rpx(30) }}>{item.roomName}</Text>
<Text style={{ fontSize: rpx(30) }}>{item.hn}</Text>
<Text style={{ fontSize: rpx(30) }}>{item.nickname}</Text>
</View>
</View>
);
}
const ss = StyleSheet.create({
line: { height: 1, backgroundColor: "gray" },
});
// rnc
import React, { Component } from "react";
import {
ActivityIndicator,
Dimensions,
FlatList,
Text,
TouchableOpacity,
View,
} from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
state = { result: [], refreshing: false, showGoTop: false };
url(page) {
return `https://api.apiopen.top/getJoke?type=text&page=${page}`;
}
componentDidMount() {
fetch(this.url(1))
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result });
});
}
render() {
return (
<View>
<FlatList
ref={(el) => (this.fl = el)}
data={this.state.result}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
ListFooterComponent={this._ListFooterComponent}
onEndReachedThreshold={0.1}
onEndReached={this._onEndReached}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
onScroll={this._onScroll}
/>
{this.showGoTop()}
</View>
);
}
_onScroll = (event) => {
// event.persist(); // 要在后台中看打印值, 必须调用此方法
// console.log(event);
const y = event.nativeEvent.contentOffset.y;
console.log(y);
this.setState({ showGoTop: y > 3000 });
};
showGoTop() {
// 条件渲染, 利用if判断, 决定返回哪种值
if (this.state.showGoTop == false) return null;
return (
<TouchableOpacity
onPress={() => this.fl.scrollToIndex({ index: 0 })}
style={{
padding: rpx(10),
borderRadius: 4,
backgroundColor: "rgba(142,22,152,0.5)",
position: "absolute",
bottom: rpx(20),
right: rpx(20),
}}
>
<Text style={{ fontSize: rpx(26), color: "white" }}>回到顶部</Text>
</TouchableOpacity>
);
}
_onRefresh = () => {
this.setState({ refreshing: true });
fetch(this.url(1))
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result, refreshing: false });
this.page = 1;
});
};
page = 1;
_onEndReached = () => {
fetch(this.url(this.page + 1))
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: [...this.state.result, ...res.result] });
this.page++;
});
};
_ListFooterComponent = () => (
<View>
<ActivityIndicator size="large" />
<Text style={{ fontSize: rpx(30), textAlign: "center" }}>加载中...</Text>
</View>
);
// 注意参数解构写法
_renderItem = ({ item }) => (
<View
style={{
padding: rpx(10),
borderRadius: rpx(5),
borderWidth: 1,
marginTop: rpx(10),
marginHorizontal: rpx(20),
}}
>
<Text style={{ fontSize: rpx(34), color: "#9cc" }}>
作者: {item.name}
</Text>
<Text style={{ fontSize: rpx(30) }}>{item.text}</Text>
</View>
);
}
网络请求 和 列表展示
- 网络请求:
fetch(url).then(res=>res.json()).then(res=>{})
- 没有跨域: 只有浏览器有同源策略 才会导致跨域
- 调试: 利用模拟器/真机的 debug 功能, 开启 debug 服务, 在浏览器的后台查看打印输出
- FlatList: 高性能的列表组件
- 3 个核心属性
- data: 数据项数组
- keyExtractor: 每个数据项对应的唯一标识
- renderItem: 每个数据项 对应的界面
- 参数解构写法:
_renderItem= ({item,index})=>{}
- 参数解构写法:
- 其它辅助属性
- 分割线
- 表头
- 表尾
- 实现加载更多的提示: 搭配 旋转图标
- 下拉刷新:
- onRefresh
- refreshing
- 加载更多
- onEndReached
- onEndReachedThreshold: 阈值
- onScroll: 监听滚动事件
- 打印: 必须调用
event.persist()
才能看到打印值
- 打印: 必须调用
- 绑定变量给子元素:
<el ref={el => this.xx = el}
- 3 个核心属性
多列布局
// rnc
// 多列布局
import React, { Component } from "react";
import { Dimensions, FlatList, Image, Text, View } from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
state = { result: [] };
url = "https://api.apiopen.top/getWangYiNews";
componentDidMount() {
fetch(this.url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result });
});
}
render() {
return (
<FlatList
data={this.state.result}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
// numColumns: 列数, 默认1
numColumns={2}
/>
);
}
_renderItem = ({ item }) => (
<View style={{ width: rpx(360), marginLeft: rpx(10), marginTop: rpx(10) }}>
{/* 注意导入 Image */}
<Image
source={{ uri: item.image }}
style={{ width: rpx(360), height: rpx(200) }}
/>
<View>
<Text style={{ fontSize: rpx(28) }} numberOfLines={2}>
{item.title}
</Text>
<Text style={{ fontSize: rpx(25), color: "gray" }}>
{item.passtime}
</Text>
</View>
</View>
);
}
横向自动滚动
// rnc
// 横向滚动展示
import React, { Component } from "react";
import { Dimensions, FlatList, Image, Text, View } from "react-native";
const { width, height } = Dimensions.get("screen");
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
banners = [
"http://img/index/banner1.png",
"http://img/index/banner2.png",
"http://img/index/banner3.png",
"http://img/index/banner4.png",
];
// 当前滚动序号
curIndex = 0;
componentDidMount() {
// 注意关闭 debug 模式, 如果在debug模式下 定时器有bug, 会造成滚动间隔不准
setInterval(() => {
//滚动
this.curIndex++;
// 需要如果超限, 则回归0
if (this.curIndex == this.banners.length) this.curIndex = 0;
this.banner.scrollToIndex({ index: this.curIndex });
}, 4000);
}
render() {
return (
<View>
<FlatList
ref={(el) => (this.banner = el)}
data={this.banners}
keyExtractor={(item, index) => index + ""}
renderItem={this._renderItem}
// 横向
horizontal
// 按页滚动
pagingEnabled
onScroll={this._onScroll}
/>
</View>
);
}
_onScroll = (event) => {
// event.persist();
// console.log(event);
// 偏移量x / 屏幕宽 = 当前移动到的序号
const x = event.nativeEvent.contentOffset.x;
const index = Math.round(x / width); //round: 四舍五入
console.log(index);
this.curIndex = index;
};
_renderItem = ({ item }) => (
<Image source={{ uri: item }} style={{ width, height: rpx(340) }} />
);
}
路由系统
安装模块: 在项目目录下执行
npm install @react-navigation/native
npm install react-native-reanimated
react-native-gesture-handler
react-native-screens
react-native-safe-area-context @react-native-community/masked-view
npm install @react-navigation/stack
import * as React from "react";
import { View, Text } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
//引入 A B C 3个页面
import A from "./pages/A";
import B from "./pages/B";
import C from "./pages/C";
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
{/* 此处的配置是全局配置 */}
{/* 文档: https://reactnavigation.org/docs/
stack-navigator#navigationoptions-used-by-stacknavigator */}
<Stack.Navigator
screenOptions={{
headerTitleAlign: "center",
headerStyle: { backgroundColor: "green" },
headerTintColor: "white",
}}
>
{/* 注册所有组件, 被路由所管理, 第一个为首页 */}
<Stack.Screen
name="A"
component={A}
// 单独配置指定页面
options={{
title: "导航栏标题",
headerTitleAlign: "center",
headerStyle: { backgroundColor: "red" },
headerTitleStyle: { color: "white" },
}}
/>
<Stack.Screen name="B" component={B} />
<Stack.Screen name="C" component={C} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
./pages/A
// rnc
import React, { Component } from "react";
import { Text, View, TouchableOpacity } from "react-native";
export default class A extends Component {
render() {
// 路由中的组件, 属性中会被自动注入 navigation 和 route 参数
// navigation: 负责路由操作
// route: 负责路由传参
console.log(this.props);
const { navigation } = this.props;
return (
<View>
<Text style={{ fontSize: 30 }}> A页面 </Text>
<TouchableOpacity
// navigation.push(页面名)
// 注意: 不要开debug模式, 会导致失效
onPress={() => navigation.push("B")}
style={{ backgroundColor: "orange", padding: 10 }}
>
<Text style={{ fontSize: 40 }}>跳转到B页面</Text>
</TouchableOpacity>
<TouchableOpacity
// push(页面名, 参数)
onPress={() => navigation.push("C", { name: "lena", age: 33 })}
>
<Text style={{ fontSize: 40 }}>C: lena</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.push("C", { name: "nancy", age: 30 })}
>
<Text style={{ fontSize: 40 }}>C: nancy</Text>
</TouchableOpacity>
</View>
);
}
}
./pages/B
import React, { Component } from "react";
import { Text, View } from "react-native";
export default class B extends Component {
render() {
return (
<View>
<Text style={{ fontSize: 30 }}>B页面</Text>
</View>
);
}
}
./pages/C
import React, { Component } from "react";
import { Button, Text, View } from "react-native";
export default class C extends Component {
componentDidMount() {
const { navigation } = this.props;
const { name } = this.props.route.params;
navigation.setOptions({
title: name,
// 自定义导航栏右上角按钮
headerRight: () => <Button title="自定义按钮" />,
});
}
render() {
console.log(this.props);
const { name, age } = this.props.route.params;
return (
<View>
<Text style={{ fontSize: 30 }}> C页面 </Text>
<Text style={{ fontSize: 30 }}>name: {name}</Text>
<Text style={{ fontSize: 30 }}>age: {age}</Text>
</View>
);
}
}
网页组件
// rnc
// 网页
import React, { Component } from "react";
import { ScrollView, Text, View } from "react-native";
import WebView from "react-native-webview";
// 自动高度, 高度与网页内容一致
import AHWebView from "react-native-autoheight-webview";
export default class App extends Component {
data = "<h1>Hello World!</h1>";
render() {
// 远程网页
// return <WebView source={{uri: 'https://www.bilibili.com'}} />;
// 本地网页
// return <WebView source={{html: this.data}} />;
// 局部网页
return (
<ScrollView>
<Text style={{ backgroundColor: "orange", fontSize: 40, padding: 10 }}>
头部
</Text>
{/* WebView作为局部使用, 高度默认为0 */}
{/* 最外层父元素必须是 ScrollView */}
<WebView
style={{ height: 300 }}
source={{ uri: "https://www.bilibili.com" }}
/>
<Text style={{ backgroundColor: "orange", fontSize: 40, padding: 10 }}>
尾部
</Text>
{/* 自动高度的 webview, 适合局部展示完整网页 */}
<AHWebView source={{ uri: "https://www.bilibili.com" }} />
</ScrollView>
);
}
}