React04
ReactNative的执行流程:
命令行
-
生成项目包
react-native init 包名
-
编译为apk 并 运行服务器
- apk: 为了安装到模拟器上
- 服务器: 为了把你修改的代码 实时传递给模拟器 : 热更新
npm run android
-
ip方式: 规避了编译过程
- 直接使用其他人的apk 安装到模拟器上
- 启动项目后, 利用ip地址 和 项目服务器关联在一起, 实现热更新
# 获取ip --- 重启电脑 有可能会导致ip变化!!!! ipconfig # 启动项目服务 npm run start
复习
样式书写
-
内联样式
style={{样式名: 值}}
-
非CSS样式, 非层叠样式: 父组件的样式无法继承给子
-
内部样式: StyleSheet. 把样式写在 JSX 的外部.
-
弹性盒子样式: 手机端布局最重要的方式
- flexDirection: 主轴的局部方向
- alignItems: 交叉轴的布局方式
- justifyContent: 主轴的布局方式
- flex: 主轴上的份数
- alignSelf: 子元素在交叉轴上的布局
- flexWrap: 换行
-
rpx单位: 专门用来做适配
- 原理: 把宽度按照 效果图 分成指定的份数
运行方式:
- 正常的编译方式
- 启动 adb 监测服务:
adb devices
- 启动模拟器
- 查看是否检测到设备:
adb devices
- 编译+运行:
npm run android
- 启动 adb 监测服务:
- ip方式
- 查ip地址:
ipconfig
- 启动服务器:
npm run start
- 打开App 并 配置 地址:
ip:8081
- 查ip地址:
今日内容
Text文本组件
https://www.reactnative.cn/docs/text
// rnc
// Text 文本组件
import React, {Component} from 'react';
import {Text, View} from 'react-native';
export default class App extends Component {
render() {
return (
<View>
{/*
selectable: 可选
selectionColor: 选中的颜色
*/}
<Text style={{fontSize: 30}} selectable selectionColor="red">
选中事件
</Text>
{/* 点击事件 */}
{/* HTML中是 onClick, App中是onPress */}
<Text
onPress={() => alert('点击事件')}
onLongPress={() => alert('长按事件')}
style={{fontSize: 40}}>
点击事件
</Text>
{/* 行数 */}
{/* numberOfLines: 值是数字类型, 所以必须写在{}中, "2"是字符串 */}
<Text style={{fontSize: 40, color: 'green'}} numberOfLines={2}>
阿斯蒂芬挨罚阿斯蒂芬阿斯蒂芬阿萨德发送到发生大法师地方阿斯蒂芬阿萨德发送到发送到发地方撒旦法阿斯蒂芬阿萨德父
</Text>
</View>
);
}
}
Image+ScrollView
https://www.reactnative.cn/docs/image
https://www.reactnative.cn/docs/scrollview
// rnc
// 图片组件
import React, {Component} from 'react';
//切记: 使用组件时 必须引入
import {Image, ScrollView, Text, View} from 'react-native';
/**
* 图片分两种:
* 本地: 必须用 require 关键词引入 -- 与webpack打包工具有关
* 网络: 必须手动指定宽高
*/
export default class App extends Component {
imgUrl =
'https://tse1-mm.cn.bing.net/th/id/OIP.vsrsNr8eVEYubc2_hG5FAQHaNK?w=187&h=333&c=7&o=5&dpr=1.5&pid=1.7';
render() {
return (
// ScrollView: 滚动视图. 当内容超长, 自动滚动
<ScrollView>
{/* source: 源. 是src的全称 */}
{/* uri: 是url的升级版; 更多信息 可以度娘 */}
{/* 网络图片: 必须指定宽高. 默认为 0,0 */}
{/* resizeMode: 显示比例不正确时 的处理方案
center: 保持比例 居中
stretch: 拉伸充满, 不保持比例
repeat: 重复平铺
contain: 显示完整
cover: 保持比例 充满. 默认值, 会截取
*/}
<Image
source={{uri: this.imgUrl}}
style={{width: 400, height: 200, backgroundColor: 'red'}}
resizeMode="cover"
/>
{/* blurRadius: 模糊度; 数字越大越模糊 */}
<Image
source={{uri: this.imgUrl}}
style={{width: 100, height: 200}}
blurRadius={5}
/>
{/* 本地图片: 需要手动创建 assets 文件夹存放本地资源 */}
{/* 注意事项: 必须用require关键词引入 */}
<Image source={require('./assets/3.jpg')} />
{/* 有时候 热更新服务器会卡, 导致本地图不出现: 此时往往需要重启 或 重载r */}
</ScrollView>
);
}
}
背景图 和 状态栏
https://www.reactnative.cn/docs/statusbar
https://www.reactnative.cn/docs/imagebackground
// 背景图
// rnc
import React, {Component} from 'react';
import {ImageBackground, StatusBar, Text, View} from 'react-native';
export default class App extends Component {
render() {
return (
// 网络图片 {uri: 地址}
// 本地图片 require(路径)
// 背景图:必须给 宽和高
<ImageBackground
source={require('./assets/3.jpg')}
style={{width: '100%', height: '100%'}}
resizeMode="repeat"
blurRadius={5}>
{/* 状态栏, 电池条 */}
{/* 沉浸式体验: 背景图穿透到状态栏下方 */}
{/* backgroundColor: 背景色透明 */}
{/* translucent: 允许穿透 */}
<StatusBar backgroundColor="rgba(0,0,0,0)" translucent />
<Text style={{fontSize: 50, color: 'red'}}> textInComponent </Text>
</ImageBackground>
);
}
}
按钮
https://www.reactnative.cn/docs/button
// rnc
// 按钮
import React, {Component} from 'react';
import {Button, Text, View} from 'react-native';
export default class App extends Component {
render() {
return (
<View style={{alignItems: 'center'}}>
{/* 按钮没有style属性: 样式是固定的 */}
<Button title="这是个按钮" onPress={() => alert('点击事件')} />
<Button title="不可用的按钮" disabled />
<Button title="带颜色的按钮" color="green" />
</View>
);
}
}
自定义按钮
https://www.reactnative.cn/docs/touchableopacity
// 自定义按钮
// rnc
import React, {Component} from 'react';
import {Image, Text, TouchableOpacity, View} from 'react-native';
export default class App extends Component {
render() {
return (
<View>
{/* 纯文本按钮 */}
{/* TouchableOpacity: 本质是一个带有透明度的遮罩 */}
<TouchableOpacity>
{/* 文本只能写在文本标签里 */}
<Text style={{fontSize: 30}}>注册账号</Text>
</TouchableOpacity>
{/* 图片按钮 */}
{/* activeOpacity: 设置点击时的透明度, 0透明 1不透明 */}
<TouchableOpacity activeOpacity={0.8}>
<Image source={require('./assets/3.jpg')} />
</TouchableOpacity>
</View>
);
}
}
网络请求
// 网络请求
// rnc
import React, {Component} from 'react';
import {
Dimensions,
Image,
ScrollView,
Text,
TouchableOpacity,
View,
} from 'react-native';
//固定写法:rpx
const {width, height} = Dimensions.get('screen');
function rpx(fen_shu) {
return (width / 750) * fen_shu;
}
export default class App extends Component {
// 网络请求的数据 是 从无 到有 ; 会影响UI
state = {res: null};
// RN自带网络请求操作 fetch -- 有自动代理proxy 解决跨域
componentDidMount() {
//生命周期: 挂载时
let url = 'https://api.apiopen.top/getImages?page=7';
// axios.get(url).then(res=> {})
// RN: fetch(url).then(res=>res.json()).then(res=>{})
// fetch:抓取/获取 res.json(): 默认读取的是响应的整体, 需要json() 读取返回值部分
fetch(url)
.then((res) => res.json())
.then((res) => {
// 默认: 看 cmd 查看打印的数据; 但是此方式 格式不容易看
// 官方提供了 debug 模式. 在服务器上按 d 然后模拟器上会有弹窗 选 debug 就会自动在浏览器上弹出debug窗口 F12 看后台即可
// 注意: 不推荐经常开 debug模式, 比较卡, 代码偶尔出现一些不可控的错误
console.log(res);
// state的值要更新-> setState
this.setState({res}); // {res: res} -> {res}
});
}
// 列表渲染
showImages = () =>
this.state.res.result.map((item, index) => {
let space = 20; //间距
let img_w = (750 - space * 3) / 2;
let img_h = img_w * 1.5;
return (
<Image
key={index}
source={{uri: item.img}}
style={{
width: rpx(img_w),
height: rpx(img_h),
marginTop: rpx(space),
marginLeft: rpx(space),
borderRadius: rpx(6),
}}
/>
);
});
render() {
// 来自网络请求的数据 使用前要判定
if (!this.state.res) return <View />;
return (
// 滚动视图 不接受 flex 属性, 所以必须在里面放个view
<ScrollView>
<View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
{this.showImages()}
</View>
{/* Vertical: 竖向 代表上下 paddingVertical就是 上下内边距 */}
<TouchableOpacity
style={{
alignItems: 'center',
paddingVertical: 10,
backgroundColor: 'green',
borderRadius: 6,
margin: rpx(20),
}}
// onPress={} 点击触发方法, 获取更多数据, 需要页数标识和 合并新数据
>
<Text style={{fontSize: rpx(40), color: 'white'}}>
LuFei Need More!!
</Text>
</TouchableOpacity>
</ScrollView>
);
}
}
练习: 新闻
接口:
https://api.apiopen.top/getWangYiNews?page=1
// rnc
import React, {Component} from 'react';
import {Button, Dimensions, Image, ScrollView, Text, View} from 'react-native';
// rpx
const {width, height} = Dimensions.get('screen');
function rpx(fen_shu) {
return (width / 750) * fen_shu;
}
export default class App extends Component {
state = {res: null}; //满足两个条件才能存state: 会变化 && 显示在界面
page = 1;
loadMore() {
let url = 'https://api.apiopen.top/getWangYiNews?page=' + (this.page + 1);
fetch(url)
.then((res) => res.json())
.then((res) => {
this.state.res.result = this.state.res.result.concat(res.result);
this.setState({});
this.page++;
});
}
componentDidMount() {
let url = 'https://api.apiopen.top/getWangYiNews?page=1';
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({res});
});
}
// 把数组内容显示在界面上: 列表渲染
showNews = () =>
this.state.res.result.map((item, index) => {
let space = 20;
let box_w = (750 - 3 * 20) / 2;
// 宽x高 = 140 x88
let img_h = (box_w * 88) / 140;
return (
<View
key={index}
style={{
width: rpx(box_w),
marginLeft: rpx(space),
marginTop: rpx(space),
backgroundColor: 'rgb(235,235,235)',
}}>
<Image
source={{uri: item.image}}
style={{width: '100%', height: rpx(img_h)}}
/>
<View style={{padding: 6}}>
<Text
style={{
fontSize: rpx(30),
}}>
{item.title}
</Text>
<Text style={{fontSize: rpx(26), color: 'gray'}}>
{item.passtime}
</Text>
</View>
</View>
);
});
render() {
if (!this.state.res) return <View />;
return (
<ScrollView>
<View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
{this.showNews()}
<View
style={{
alignItems: 'stretch',
width: '100%',
// Horizontal: 横向 代表左右
paddingHorizontal: rpx(20),
marginVertical: rpx(20),
}}>
<Button title="加载更多" onPress={() => this.loadMore()} />
</View>
</View>
</ScrollView>
);
}
}
开关 和 活动提示器
https://reactnative.cn/docs/switch
https://reactnative.cn/docs/activityindicator
// 开关
// rnc
import React, {Component} from 'react';
import {ActivityIndicator, Switch, Text, View} from 'react-native';
export default class App extends Component {
state = {isOpen: false};
_changed = (isOpen) => {
// 学会凑语法糖: isOpen
this.setState({isOpen});
};
render() {
return (
<View>
{/* 活动提示器 */}
<ActivityIndicator color="green" />
<ActivityIndicator color="green" size="large" />
<ActivityIndicator color="green" size={60} />
{/* 典型的受控组件: 必须通过代码来控制 */}
<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 {Switch, Text, TextInput, View} from 'react-native';
export default class App extends Component {
state = {hidePwd: false};
render() {
return (
<View style={{alignItems: 'center'}}>
{/* 默认设定: 如果不指定宽度, 输入框的宽度随内容变宽 */}
<TextInput
placeholder="请输入用户名"
style={{fontSize: 30, color: 'black', borderWidth: 1}}
/>
<TextInput
placeholder="请输入用户名"
style={{fontSize: 30, color: 'black', borderWidth: 1, width: 300}}
/>
{/* 密码框 secureTextEntry 密文 */}
<TextInput
placeholder="密码"
secureTextEntry={this.state.hidePwd}
style={{fontSize: 30}}
/>
<Switch
value={this.state.hidePwd}
onValueChange={(hidePwd) => this.setState({hidePwd})}
/>
</View>
);
}
}
登录页面制作
// 手机登录页面
// rncs
import React, {Component} from 'react';
import {
Text,
StyleSheet,
View,
ImageBackground,
StatusBar,
Dimensions,
Image,
TextInput,
TouchableOpacity,
} from 'react-native';
const {width, height} = Dimensions.get('screen');
function rpx(fs) {
return (width / 750) * fs;
}
export default class App extends Component {
bg =
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1607598758999&di=0d1286b14f025270640768c686306bd6&imgtype=0&src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F07%2F20140907233240_VYNvH.jpeg';
render() {
return (
<ImageBackground
source={{uri: this.bg}}
style={{width: '100%', height: '100%'}}
blurRadius={8}>
{/* 状态栏的 沉浸式 */}
<StatusBar backgroundColor="rgba(0,0,0,0)" translucent />
<View style={ss.content}>
<Text style={{fontSize: rpx(40), color: 'white'}}>
让阅读充满乐趣
</Text>
<Text style={{fontSize: rpx(22), color: 'white', marginTop: rpx(20)}}>
登录后更多精彩
</Text>
{/* 输入框 */}
<View style={{margin: rpx(100), width: '100%'}}>
<View style={ss.input_area}>
<Image source={require('./assets/user.png')} />
<TextInput
placeholder="请输入您的账号"
placeholderTextColor="rgba(255,255,255,0.6)"
style={ss.input}
/>
</View>
{/* 多样式: 用数组 */}
<View style={[ss.input_area, {marginTop: rpx(30)}]}>
<Image source={require('./assets/passwd.png')} />
<TextInput
placeholder="请输入您的密码"
placeholderTextColor="rgba(255,255,255,0.6)"
secureTextEntry
style={ss.input}
/>
</View>
</View>
{/* 按钮 */}
<View style={{alignItems: 'stretch', width: '100%'}}>
<TouchableOpacity style={ss.btn} activeOpacity={0.7}>
<Text style={ss.btn_title}>登录</Text>
</TouchableOpacity>
<TouchableOpacity
style={[ss.btn, {backgroundColor: 'white', marginTop: rpx(30)}]}
activeOpacity={0.7}>
<Text style={[ss.btn_title, {color: 'gray'}]}>注册</Text>
</TouchableOpacity>
<TouchableOpacity style={{marginTop: rpx(20), marginLeft: 'auto'}}>
<Text style={{color: 'white', opacity: 0.7}}>忘记密码?</Text>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
);
}
}
const ss = StyleSheet.create({
btn_title: {
color: 'white',
fontSize: rpx(35),
paddingVertical: rpx(10), //上下: vertical
},
btn: {
backgroundColor: '#a2cb01',
alignItems: 'center',
borderRadius: 8,
},
content: {
// backgroundColor: 'lightgray',
marginHorizontal: rpx(100),
marginTop: rpx(150),
height: 500,
alignItems: 'center',
},
// 输入框区域
input_area: {
flexDirection: 'row',
borderBottomColor: 'white',
borderBottomWidth: 1,
alignItems: 'center',
},
input: {
fontSize: rpx(35),
color: 'white',
// borderWidth: 1,
flex: 1, //占据剩余大小的 份数
marginLeft: rpx(10),
},
});