React Native小计

2 篇文章 0 订阅
1 篇文章 0 订阅
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}

多列布局

// 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>
    );
  }
}













  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值