React05
作业1: 斗鱼
// rnc
import React, {Component} from 'react';
import {
Dimensions,
Image,
ImageBackground,
ScrollView,
Text,
TouchableOpacity,
View,
} from 'react-native';
const {width, height} = Dimensions.get('screen');
function rpx(fs) {
return (width / 750) * fs;
}
export default class App extends Component {
state = {data: []};
loadMore() {
// 分页的处理方式: 不同服务器不同, 斗鱼的服务器是 传递已有数据的个数
let url =
'http://capi.douyucdn.cn/api/v1/live?limit=20&offset=' +
this.state.data.length;
fetch(url)
.then((res) => res.json())
.then((res) => {
this.state.data = this.state.data.concat(res.data);
this.setState({});
});
}
componentDidMount() {
// 生命周期: 挂载时
let url = 'http://capi.douyucdn.cn/api/v1/live?limit=20&offset=0';
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({data: res.data});
});
}
show() {
return this.state.data.map((item, index) => {
// 比例:320x180
let space = 10;
let box_w = (750 - space * 3) / 2;
// 按比例算图片高
let img_h = (box_w * 180) / 320;
// online: 过万的要转
let online = item.online;
if (online >= 10000) {
online /= 10000;
online = online.toFixed(1) + '万';
}
return (
<View
key={index}
style={{
width: rpx(box_w),
marginLeft: rpx(space),
marginTop: rpx(space),
}}>
<ImageBackground
source={{uri: item.room_src}}
style={{width: '100%', height: rpx(img_h)}}>
<View
style={{
backgroundColor: 'rgba(0,0,0,0.5)',
padding: rpx(5),
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 'auto',
}}>
<Text style={{color: 'white'}}>{item.nickname}</Text>
<Text style={{color: 'white'}}>{online}</Text>
</View>
</ImageBackground>
<Text style={{backgroundColor: 'lightgray', padding: rpx(10)}}>
{item.room_name}
</Text>
</View>
);
});
}
render() {
return (
<ScrollView>
<View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
{this.show()}
<TouchableOpacity
onPress={() => this.loadMore()}
style={{
margin: rpx(10),
alignItems: 'center',
paddingVertical: rpx(10),
backgroundColor: 'green',
width: rpx(750 - 20),
borderRadius: rpx(10),
}}>
<Text style={{color: 'white', fontSize: rpx(32)}}>加载更多</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
}
}
作业2:
// 布局练习
// rncs
import React, {Component} from 'react';
import {
Text,
StyleSheet,
View,
ImageBackground,
StatusBar,
Dimensions,
TouchableOpacity,
Image,
TextInput,
} from 'react-native';
// height: 屏幕高
const {width, height} = Dimensions.get('screen');
// 宽度的适配用rpx. 高度和宽度有比例关系的 用rpx
function rpx(fs) {
return (width / 750) * fs;
}
export default class App extends Component {
render() {
return (
<ImageBackground
source={require('./assets/bg.jpg')}
// 高度不要写100% 会因为弹出键盘 父元素屏幕高度缩小 而变更
// 直接写死高度 为屏幕高 可以防止键盘弹出
style={{width: '100%', height}}
blurRadius={8}>
{/* 沉浸式 */}
<StatusBar backgroundColor="rgba(0,0,0,0)" translucent />
<TouchableOpacity style={ss.left_arrow}>
<Image source={require('./assets/left.png')} />
</TouchableOpacity>
<Text style={ss.title}>飞车</Text>
<View style={ss.content}>
<TextInput placeholder="邮箱/手机号" style={ss.input} />
<TextInput
placeholder="密码"
secureTextEntry
style={[ss.input, {marginVertical: 20}]}
/>
<TouchableOpacity style={ss.login_btn}>
<Text style={{fontSize: rpx(40)}}>登录</Text>
</TouchableOpacity>
<View style={ss.account}>
<TouchableOpacity>
<Text style={{color: 'white', fontSize: rpx(30)}}>忘记密码?</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text style={{color: 'white', fontSize: rpx(30)}}>手机注册</Text>
</TouchableOpacity>
</View>
</View>
<View style={{position: 'absolute', bottom: 20, left: 0, right: 0}}>
<Text
style={{
color: 'white',
textAlign: 'center',
marginBottom: 40,
fontSize: rpx(25),
}}>
第三方账号直接登录
</Text>
<View style={{flexDirection: 'row', justifyContent: 'center'}}>
<TouchableOpacity style={ss.icon}>
<Image
source={require('./assets/qq.png')}
style={{width: rpx(70), height: rpx(70)}}
/>
</TouchableOpacity>
<TouchableOpacity style={[ss.icon, {marginHorizontal: rpx(40)}]}>
<Image
source={require('./assets/webo.png')}
style={{width: rpx(70), height: rpx(70)}}
/>
</TouchableOpacity>
<TouchableOpacity style={ss.icon}>
<Image
source={require('./assets/wexin.png')}
style={{width: rpx(70), height: rpx(70)}}
/>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
);
}
}
const ss = StyleSheet.create({
icon: {
backgroundColor: 'white',
padding: rpx(20),
borderRadius: rpx(55),
},
account: {
marginTop: 30,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: rpx(20),
},
login_btn: {
backgroundColor: 'yellow',
borderRadius: rpx(10),
paddingVertical: rpx(13),
alignItems: 'center',
},
input: {
fontSize: rpx(35),
backgroundColor: 'white',
borderRadius: rpx(10),
paddingHorizontal: rpx(15),
color: 'black',
},
content: {
marginHorizontal: rpx(50),
marginTop: 80,
// borderWidth: 1,
},
title: {
fontSize: rpx(80),
color: 'white',
alignSelf: 'center',
marginTop: 50,
},
left_arrow: {
marginTop: 30,
marginLeft: rpx(30),
alignSelf: 'flex-start',
},
});
FlatList
高性能的列表组件:
- 完全跨平台。
- 支持水平布局模式。
- 行组件显示或隐藏时可配置回调事件。
- 支持单独的头部组件。
- 支持单独的尾部组件。
- 支持自定义行间分隔线。
- 支持下拉刷新。
- 支持上拉加载。
- 支持跳转到指定行(ScrollToIndex)。
- 支持多列布局。
// FlatList: 高性能的列表组件
// rnc
import React, {Component} from 'react';
import {FlatList, Text, View} from 'react-native';
export default class App extends Component {
names = ['lucy', 'jjjj', '东东', '亮亮', '然然', '小新', 'shirley', 'kk'];
render() {
/**
* 列表渲染的做法 目前分两种:
* 1. 手动: 我们自己写循环遍历操作, 反馈样式
* 2. 自动: FlatList 相当一个小助手 -- 只需要告知相关的信息, 就会自动形成列表
*/
// 3个核心属性:
// 1. data: 用于设置要循环的数据数组
// 2. renderItem: 用于设置每个数据项对应的UI
// 3. keyExtractor: 每条UI 对应的唯一标识
return (
<FlatList
data={this.names}
renderItem={this._renderItem}
// 返回值要求是 string; index是number类型, 必须转字符串
// ()=> {return xxx;} 语法糖: ()=> xxx
keyExtractor={(item, index) => index + ''}
/>
);
}
// data是 数组中的每个元素
_renderItem = (data) => {
// data: {index:序号, item:值}
console.log(data);
return (
<View style={{backgroundColor: data.index % 2 == 0 ? 'green' : 'orange'}}>
<Text style={{fontSize: 40}}>{data.item}</Text>
</View>
);
};
}
网易新闻
// 新闻列表
// rnc
import React, {Component} from 'react';
import {ActivityIndicator, FlatList, Image, Text, View} from 'react-native';
export default class App extends Component {
state = {result: [], refreshing: false};
componentDidMount() {
let url = 'https://api.apiopen.top/getWangYiNews?page=1';
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({result: res.result});
});
}
render() {
/**
* 核心属性:
* data: 要显示的数据数组
* renderItem: 每条数据的样子
* keyExtractor: 每一条的唯一标识
*
* 其它属性:
* ItemSeparatorComponent: 元素间的分割组件
* ListHeaderComponent: 表头
* ListFooterComponent: 表尾
* onEndReachedThreshold: 触底阈值. 当剩余未显示区域的高度 占据显示区域高度百分比. 例如0.1 剩余高度 是显示区域高度 10% 大小时, 触发 onEndReached 方法
* onEndReached: 触底时触发
* refreshing: 下拉动画是否显示
* onRefresh: 下拉时触发的方法
*/
return (
<FlatList
data={this.state.result}
renderItem={this._renderItem}
keyExtractor={(item, index) => index + ''}
ItemSeparatorComponent={() => (
<View style={{height: 2, backgroundColor: 'gray'}} />
)}
ListHeaderComponent={this._ListHeaderComponent}
ListFooterComponent={this._ListFooterComponent}
onEndReached={this._onEndReached}
onEndReachedThreshold={0.1}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
/>
);
}
_onRefresh = () => {
this.setState({refreshing: true});
let url = 'https://api.apiopen.top/getWangYiNews?page=1';
fetch(url)
.then((res) => res.json())
.then((res) => {
this.setState({result: res.result, refreshing: false});
this.page = 1;
});
};
page = 1;
_onEndReached = () => {
// alert('侦测到触底操作');
let url = 'https://api.apiopen.top/getWangYiNews?page=' + (this.page + 1);
fetch(url)
.then((res) => res.json())
.then((res) => {
this.state.result = this.state.result.concat(res.result); //合并
this.setState({}); //刷新
this.page++; //页数更新成当前页
});
};
_ListFooterComponent = () => (
<View
style={{
alignItems: 'center',
paddingVertical: 10,
backgroundColor: 'lightgray',
}}>
<ActivityIndicator color="red" size="large" />
<Text style={{fontSize: 20}}>加载中...</Text>
</View>
);
_ListHeaderComponent = () => (
<Text
style={{
backgroundColor: 'red',
color: 'white',
textAlign: 'center',
fontSize: 40,
}}>
网易新闻
</Text>
);
// 箭头函数 可以规避 this 问题
// 超级简化: 参数直接解包
_renderItem = ({index, item}) => {
if (!item.image) return <View />; //容错, 有不存在的, 就不显示
// console.log(data);
//data: {index:序号, item: 数据}
// 快速解包
// let {index, item} = data;
return (
<View style={{padding: 4, flexDirection: 'row'}}>
<Image source={{uri: item.image}} style={{width: 140, height: 88}} />
{/* flex: 份数; 代表宽度为剩余大小 */}
<View style={{flex: 1, justifyContent: 'space-evenly', marginLeft: 10}}>
<Text style={{fontSize: 20}}>{item.title}</Text>
<Text style={{color: 'gray', fontSize: 18}}>{item.passtime}</Text>
</View>
</View>
);
};
}
美图浏览
// 美图浏览器
// rnc
import React, {Component} from 'react';
import {
ActivityIndicator,
Dimensions,
FlatList,
Image,
Modal,
Text,
TouchableOpacity,
View,
} from 'react-native';
const {width, height} = Dimensions.get('screen');
function rpx(fs) {
return (width / 750) * fs;
}
export default class App extends Component {
// showModal: 控制模态窗口的可见性
state = {result: [], refreshing: false, showModal: false, bigImg: ''};
componentDidMount() {
let url = 'https://api.apiopen.top/getImages?page=7';
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({result: res.result});
});
}
render() {
// numColumns: 列数
return (
<View>
{/* 新的组件: Modal 模态窗口 -- 浮在已有内容上 */}
{/* visible: 可见 可视 */}
<Modal visible={this.state.showModal}>
<TouchableOpacity
onPress={() => this.setState({showModal: false})}
activeOpacity={0.8}
style={{backgroundColor: 'green', height: '100%'}}>
<Image
style={{width: '100%', height: '100%'}}
source={{uri: this.state.bigImg}}
/>
</TouchableOpacity>
</Modal>
<FlatList
data={this.state.result}
keyExtractor={(item, index) => index + ''}
renderItem={this._renderItem}
numColumns={3}
ListFooterComponent={this._ListFooterComponent}
onEndReachedThreshold={0.1}
onEndReached={this._onEndReached}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
/>
</View>
);
}
_onRefresh = () => {
this.setState({refreshing: true});
let url = 'https://api.apiopen.top/getImages?page=7';
fetch(url)
.then((res) => res.json())
.then((res) => {
this.setState({result: res.result, refreshing: false});
this.page = 7;
});
};
page = 7;
_onEndReached = () => {
let url = 'https://api.apiopen.top/getImages?page=' + (this.page + 1);
fetch(url)
.then((res) => res.json())
.then((res) => {
this.state.result = this.state.result.concat(res.result);
this.setState({});
this.page++;
});
};
_ListFooterComponent = () => {
return (
<View style={{alignItems: 'center', paddingVertical: rpx(16)}}>
<ActivityIndicator color="green" size="large" />
<Text style={{fontSize: rpx(30)}}>加载中...</Text>
</View>
);
};
_renderItem = ({item, index}) => {
let space = 15;
let box_w = (750 - 4 * space) / 3;
let box_h = box_w * 1.5;
return (
<TouchableOpacity
onPress={() => this.setState({showModal: true, bigImg: item.img})}
activeOpacity={0.8}
style={{
marginLeft: rpx(space),
width: rpx(box_w),
height: rpx(box_h),
marginTop: rpx(space),
}}>
<Image
source={{uri: item.img}}
style={{width: '100%', height: '100%', borderRadius: rpx(10)}}
/>
</TouchableOpacity>
);
};
}
打包产品版apk
只有产品版的apk 才能分享给其它用户安装.
https://www.reactnative.cn/docs/signed-apk-android
-
添加 JAVA 的 bin 文件夹到 环境变量中
C:\Program Files\Java\jdk1.8.0_202\bin
win7: 双击之后把下方内容添加到 已有内容的末尾即可
;C:\Program Files\Java\jdk1.8.0_202\bin
-
在项目的 android/app 目录下生成秘钥, 执行下方命令行
如果提示 非内部 或 外部命令, 就是上方的 环境变量 没有正确添加
keytool -genkeypair -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
如果生成有问题的, 可以删除下方文件 再次生成即可!
-
桌面图标
-
从Android 9 开始, 真机必须使用 https 请求
必须人为设置: 允许 http 才可以!
-
打包命令: 必须在 项目的 android 目录下执行
gradlew assembleRelease
-
打包的apk 就可以发给其它人安装了
如果生成有问题的, 可以删除下方文件 再次生成即可!
-
桌面图标
-
从Android 9 开始, 真机必须使用 https 请求
必须人为设置: 允许 http 才可以!
-
打包命令: 必须在 项目的 android 目录下执行
gradlew assembleRelease
-
打包的apk 就可以发给其它人安装了