你早知道的React Native

904 阅读27分钟

1.React Native是什么

React Native官方:一次学习,随处编写。。。 nice 不明觉厉

RN概述

  • 使用React和JavaScript为Android和iOS创建本机应用 React Native将开发的最佳部分与React(用于构建用户界面的一流JavaScript库)结合在一起,可以在现有的Android和iOS项目中使用React Native,也可以从头开始创建一个全新的应用程序。React Native与Objective-C,Java或Swift中的组件可以顺利结合。如果您需要优化应用程序的某几个方面,那么可以简单的使用原生代码。这两者并不冲突。

与Flutter优缺对比

优势

  • 热重装=快速编码 热重载允许开发人员将新代码直接注入正在运行的应用程序中,从而加快了开发过程。因此,开发人员可以立即看到更改,而无需重建应用程序。
  • 一个代码库,两个移动平台 与Flutter完全相同:编写单个代码库为2个应用程序提供动力-涵盖Android和iOS平台。 更妙的是,JavaScript通过与Web应用程序共享代码,在编写跨平台应用程序时为您提供了帮助。这是通过创建可编译为目标平台的抽象组件来完成的。
  • 它使用一种流行的语言-JavaScript。 React Native使用JavaScript:许多开发人员都熟悉的一种编程语言(尽管Dart仍然不那么广为人知或使用)。而且,如果您是喜欢静态类型编程语言的开发人员,则甚至可以使用TypeScript(JavaScript子集)。
  • 开发者的选择自由 React Native 允许开发人员精确地决定他们要使用什么解决方案。两者都根据项目的要求以及开发人员的喜好而定。 假设,如果开发人员需要决定如何处理全局状态(如何存储和管理应用程序中许多组件中使用的数据),选择路由器库或在JavaScript和TypeScript之间进行选择–他们可以决定是否d倾向于使用自定义的UI库,或者自己编写。
  • 相对成熟度。 官方的React Native版本是4年前发布的,因此Facebook团队有足够的时间来稳定API,并专注于解决问题和解决问题。现在,他们正在努力进行一些令人兴奋的改进-例如减小应用程序的大小。
  • 活跃而广阔的社区。 React Native有庞大的开发者社区。不仅如此,还有无数的教程,库和UI框架使学习该技术变得容易,并且可以快速,轻松地进行开发。
  • 对React开发人员易于学习 我们列表中的这一优势非常针对React开发人员。如果您具有Web开发的背景并且已经使用了流行的React解决方案,则可以轻松地使用React Native,而无需学习新的库。您可以使用相同的库,工具和模式。

缺点

  • 不是原生的UI。 像任何跨平台解决方案一样,UI体验和性能都不会与本机应用程序中的完全相同,而是与之接近。但是,与Flutter相比,使用React Native更容易获得“自然感觉”。如果您希望Flutter应用程序具有本机组件,则需要进行其他工作。
  • 组件少。 React Native仅支持开箱即用的基本组件(其中许多组件适用于开箱即用的平台,例如按钮,加载指示器或滑块)。Flutter旨在支持开箱即用的Material Design,因此该框架支持更多的小部件。
  • 脆弱的用户界面。 React Native在后台使用本机组件这一事实应该使您充满信心,每次OS UI更新后,您的应用程序组件也将立即升级。 就是说– 这可能会破坏应用程序的用户界面,但这种情况很少发生。
  • 应用比本地应用更大。 用React Native编写的应用程序必须能够运行Javascript代码(JavaScript虚拟机)。Android默认情况下不具有此功能-意味着应用程序需要包括一个支持JavaScript代码的库,从而导致应用程序比其本机Android同类产品更大。 使用React Native制作的iOS应用程序不会出现此问题,但它们通常仍比本地应用程序大。

2.环境搭建

安装依赖

必须安装的依赖有:Node、Watchman、Xcode 和 CocoaPods。 虽然可以使用任何编辑器来开发应用,但你仍然必须安装 Xcode 来获得编译 iOS 应用所需的工具和环境。

使用Homebrew安装Node 和 Watchman

brew install node
brew install watchman

在 Xcode 中安装 iOS 模拟器#

安装模拟器只需打开 Xcode > Preferences... 菜单,然后选择 Components 选项,即可看到各种可供安装的不同的 iOS 版本的模拟器。 image.png

CocoaPods

CocoaPods是用 Ruby 编写的包管理器

brew install cocoapods

创建新项目

使用 React Native 内建的命令行工具来创建一个名为"AwesomeProject"的新项目。这个命令行工具不需要安装,可以直接用 node 自带的npx命令来使用(注意 init 命令默认会创建最新的版本):

npx react-native init AwesomeProject

可选参数 你可以使用--version参数创建指定版本的项目。注意版本号必须精确到两个小数点。

npx react-native init AwesomeProject --version X.XX.X

还可以使用--template来使用一些社区提供的模板,例如带有TypeScript配置的:

npx react-native init AwesomeTSProject --template react-native-template-typescript

编译并运行 React Native 应用

cd AwesomeProject
yarn ios
# 或者
yarn react-native run-ios

核心组件和API

  • React Native 提供了一些内置的核心组件供你使用。

需要说明的是,你不会被局限在这些内置组件上。React Native 是大开源社区的作品,所以你还可以在 github 或是 npm 上搜索到带有react native关键字的大量的第三方组件。

基础组件

View

作为创建 UI 时最基础的组件,View 是一个支持 Flexbox 布局、样式、触摸响应、和一些无障碍功能的容器。

View 在设计上是可以嵌套使用的,也可以有任意多个任意类型的子视图。

import React from "react";
import { View, Text } from "react-native";
const ViewBoxesWithColorAndText = () => {
  return (
    <View
      style={{
        flexDirection: "row",
        height: 100,
        padding: 20
      }}
    >
      <View style={{ backgroundColor: "blue", flex: 0.3 }} />
      <View style={{ backgroundColor: "red", flex: 0.5 }} />
      <Text>Hello World!</Text>
    </View>
  );
};
export default ViewBoxesWithColorAndText;

image.png

合成点击事件

用于 View 响应属性 (例如, onResponderMove)

  • nativeEvent changedTouches - 从上一次事件以来的触摸事件数组。

identifier - 触摸事件的 ID。

locationX - 触摸事件相对元素位置的 X 坐标。

locationY - 触摸事件相对元素位置的 Y 坐标。

pageX - 触摸事件相对根元素位置的 X 坐标。

pageY - 触摸事件相对根元素位置的 Y 坐标。

target - 接收触摸事件的元素 ID.

timestamp - 触摸事件的时间标记,用来计算速度.

touches - 屏幕上所有当前触摸事件的数组.

示例
{
    changedTouches: [PressEvent],
    identifier: 1,
    locationX: 8,
    locationY: 4.5,
    pageX: 24,
    pageY: 49.5,
    target: 1127,
    timestamp: 85131876.58868201,
    touches: []
}

Text

一个用于显示文本的 React 组件,并且它也支持嵌套、样式,以及触摸处理。

import React, { useState } from "react";
import { Text, StyleSheet } from "react-native";
const TextInANest = () => {
  const [titleText, setTitleText] = useState("Bird's Nest");
  const bodyText = useState("This is not really a bird nest.");
  const onPressTitle = () => {
    setTitleText("Bird's Nest [pressed]");
  };
  return (
    <Text style={styles.baseText}>
      <Text style={styles.titleText} onPress={onPressTitle}>
        {titleText}
        {"\n"}
        {"\n"}
      </Text>
      <Text numberOfLines={5}>{bodyText}</Text>
    </Text>
  );
};
const styles = StyleSheet.create({
  baseText: {
    fontFamily: "Cochin"
  },
  titleText: {
    fontSize: 20,
    fontWeight: "bold"
  }
});
export default TextInANest;

image.png

嵌套文本
import React from 'react';
import { Text, StyleSheet } from 'react-native';
const BoldAndBeautiful = () => {
  return (
    <Text style={styles.baseText}>
      I am bold
      <Text style={styles.innerText}> and red</Text>
    </Text>
  );
};
const styles = StyleSheet.create({
  baseText: {
    fontWeight: 'bold'
  },
  innerText: {
    color: 'red'
  }
});
export default BoldAndBeautiful;

image.png

嵌套视图(仅限 iOS)
import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class BlueIsCool extends Component {
  render() {
    return (
      <Text>
        There is a blue square
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
        in between my text.
      </Text>
    );
  }
}

image.png

Image

用于显示多种不同类型图片的 React 组件,包括网络图片、静态资源、临时的本地图片、以及本地磁盘上的图片(如相册)等。

下面的例子分别演示了如何显示从本地缓存、网络甚至是以'data:'的 base64 uri 形式提供的图片

mport React from 'react';
import { View, Image, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
  container: {
    paddingTop: 50,
  },
  tinyLogo: {
    width: 50,
    height: 50,
  },
  logo: {
    width: 66,
    height: 58,
  },
});
const DisplayAnImage = () => {
  return (
    <View style={styles.container}>
      <Image
        style={styles.tinyLogo}
        source={require('@expo/snack-static/react-native-logo.png')}
      />
      <Image
        style={styles.tinyLogo}
        source={{
          uri: 'https://reactnative.dev/img/tiny_logo.png',
        }}
      />
      <Image
        style={styles.logo}
        source={{
          uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==',
        }}
      />
    </View>
  );
}
export default DisplayAnImage;

image.png

在 Android 上支持 GIF 和 WebP 格式图片

默认情况下 Android 是不支持 GIF 和 WebP 格式的。你需要在android/app/build.gradle文件中根据需要手动添加以下模块:

dependencies {
  // 如果你需要支持Android4.0(API level 14)之前的版本
  implementation 'com.facebook.fresco:animated-base-support:1.3.0'
  // 如果你需要支持GIF动图
  implementation 'com.facebook.fresco:animated-gif:2.0.0'
  // 如果你需要支持WebP格式,包括WebP动图
  implementation 'com.facebook.fresco:animated-webp:2.1.0'
  implementation 'com.facebook.fresco:webpsupport:2.0.0'
  // 如果只需要支持WebP格式而不需要动图
  implementation 'com.facebook.fresco:webpsupport:2.0.0'
}
resizeMode

决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小。默认值为cover。

cover: 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸(如果容器有 padding 内衬的话,则相应减去)。译注:这样图片完全覆盖甚至超出容器,容器中不留任何空白。

contain: 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸(如果容器有 padding 内衬的话,则相应减去)。译注:这样图片完全被包裹在容器中,容器中可能留有空白。

stretch: 拉伸图片且不维持宽高比,直到宽高都刚好填满容器。

repeat: 重复平铺图片直到填满容器。图片会维持原始尺寸,但是当尺寸超过容器时会在保持宽高比的前提下缩放到能被容器包裹。

center: 居中不拉伸

source

图片源数据(远程 URL 地址或本地数据)。

方法
Image.abortPrefetch(requestId);

中断预加载操作。

requestId:number=prefetch()返回的id

Image.getSize(uri, success, [failure]);

在显示图片前获取图片的宽高(以像素为单位)。如果图片地址不正确或下载失败,此方法也会失败。

要获取图片的尺寸,首先需要加载或下载图片(同时会被缓存起来)。这意味着理论上你可以用这个方法来预加载图片,虽然此方法并没有针对这一用法进行优化,而且将来可能会换一些实现方案使得并不需要完整下载图片即可获取尺寸。所以更好的预加载方案是使用下面那个专门的预加载方法。

此方法不能用于静态图片资源。

uri:string = 图片地址

success:function=成功的回调函数,返回图片宽高数据

failure:function=失败的回调函数

Image.prefetch(url);

预加载一个远程图片(将其下载到本地磁盘缓存)。

url:string = 图片的远程地址

Image.queryCache(urls);

查询图片缓存状态。根据图片 URL 地址返回缓存状态,比如"disk"(缓存在磁盘文件中)或是"memory"(缓存在内存中)。

urls:array=要查询缓存状态的图片 URL 数组。

TextInput

TextInput 是一个允许用户在应用中通过键盘输入文本的基本组件。本组件的属性提供了多种特性的配置,譬如自动完成、自动大小写、占位文字,以及多种不同的键盘类型(如纯数字键盘)等等。

最简单的用法就是丢一个TextInput到应用里,然后订阅它的onChangeText事件来读取用户的输入。注意,从 TextInput 里取值这就是目前唯一的做法!也就是使用在onChangeText中用setState把用户的输入写入到 state 中,然后在需要取值的地方从 this.state 中取出值。它还有一些其它的事件,譬如onSubmitEditing和onFocus。一个简单的例子如下:

import React, { Component } from 'react';
import { TextInput } from 'react-native';
const UselessTextInput = () => {
  const [value, onChangeText] = React.useState('Useless Placeholder');
  return (
    <TextInput
      style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
      onChangeText={text => onChangeText(text)}
      value={value}
    />
  );
}
export default UselessTextInput;

image.png

方法

focus(); Makes the native input request focus.

blur();

Makes the native input lose focus.

clear();

清空输入框的内容。

isFocused();

返回值表明当前输入框是否获得了焦点。

ScrollView

一个封装了平台的 ScrollView(滚动视图)的组件,同时还集成了触摸锁定的“响应者”系统。

记住 ScrollView 必须有一个确定的高度才能正常工作,因为它实际上所做的就是将一系列不确定高度的子组件装进一个确定高度的容器(通过滚动操作)。要给 ScrollView 一个确定的高度的话,要么直接给它设置高度(不建议),要么确定所有的父容器都有确定的高度。一般来说我们会给 ScrollView 设置flex: 1以使其自动填充父容器的空余空间,但前提条件是所有的父容器本身也设置了 flex 或者指定了高度,否则就会导致无法正常滚动,你可以使用元素查看器来查找具体哪一层高度不正确。

ScrollView 内部的其他响应者尚无法阻止 ScrollView 本身成为响应者。

ScrollView和FlatList应该如何选择?ScrollView 会简单粗暴地把所有子元素一次性全部渲染出来。其原理浅显易懂,使用上自然也最简单。然而这样简单的渲染逻辑自然带来了性能上的不足。想象一下你有一个特别长的列表需要显示,可能有好几屏的高度。创建和渲染那些屏幕以外的 JS 组件和原生视图,显然对于渲染性能和内存占用都是一种极大的拖累和浪费。

这就是为什么我们还有专门的FlatList组件。FlatList会惰性渲染子元素,只在它们将要出现在屏幕中时开始渲染。这种惰性渲染逻辑要复杂很多,因而 API 在使用上也更为繁琐。除非你要渲染的数据特别少,否则你都应该尽量使用FlatList,哪怕它们用起来更麻烦。

此外FlatList还可以方便地渲染行间分隔线,支持多列布局,无限滚动加载等等。

import React from 'react';
import { StyleSheet, Text, SafeAreaView, ScrollView, StatusBar } from 'react-native';
const App = () => {
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
          minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat. Duis aute irure dolor in
          reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
          pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
          culpa qui officia deserunt mollit anim id est laborum.
        </Text>
      </ScrollView>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: StatusBar.currentHeight,
  },
  scrollView: {
    backgroundColor: 'pink',
    marginHorizontal: 20,
  },
  text: {
    fontSize: 42,
  },
});
export default App;

image.png

方法

scrollTo(
  ([y]: number),
  object,
  ([x]: number),
  ([animated]: boolean),
  ([duration]: number)
);

滚动到指定的 x, y 偏移处。第三个参数为是否启用平滑滚动动画。还有一个 duration 参数则是仅限 android 可以使用的滚动持续时间。

scrollTo({x: 0, y: 0, animated: true})

指定滚动持续时间的示例(仅限 Android):

scrollTo({x: 0, y: 0, duration: 500})

crollToEnd(([options]: { animated: boolean, duration: number }));

滚动到视图底部(水平方向的视图则滚动到最右边)。

加上动画参数scrollToEnd({animated: true})则启用平滑滚动动画,或是调用scrollToEnd({animated: false})来立即跳转。

flashScrollIndicators()

短暂地显示滚动指示器。

StyleSheet

StyleSheet 提供了一种类似 CSS 样式表的抽象。

import React from "react";
import { StyleSheet, Text, View } from "react-native";

const App = () => (
  <View style={styles.container}>
    <Text style={styles.title}>React Native</Text>
  </View>
);
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: "#eaeaea"
  },
  title: {
    marginTop: 16,
    paddingVertical: 8,
    borderWidth: 4,
    borderColor: "#20232a",
    borderRadius: 6,
    backgroundColor: "#61dafb",
    color: "#20232a",
    textAlign: "center",
    fontSize: 30,
    fontWeight: "bold"
  }
});
export default App;

image.png

方法

static compose(style1: object, style2: object): object | array<object>

Combines two styles such that style2 will override any styles in style1. If either style is falsy, the other one is returned without allocating an array, saving allocations and maintaining reference equality for PureComponent checks.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const App = () => (
  <View style={container}>
    <Text style={text}>React Native</Text>
  </View>
);
const page = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: '#fff',
  },
  text: {
    fontSize: 30,
    color: '#000'
  },
});
const lists = StyleSheet.create({
  listContainer: {
    flex: 1,
    backgroundColor: '#61dafb',
  },
  listItem: {
    fontStyle: 'italic',
    fontWeight: 'bold'
  },
});
const container = StyleSheet.compose(page.container, lists.listContainer);
const text = StyleSheet.compose(page.text, lists.listItem);
export default App;
static create(obj: object): object

reates a StyleSheet style reference from the given object.

 static flatten(style: array<object>): object

Flattens an array of style objects, into one aggregated style object. Alternatively, this method can be used to lookup IDs, returned by StyleSheet.register.

import React from "react";
import { StyleSheet, Text, View } from "react-native";
const App = () => (
  <View style={page.container}>
    <Text style={flattenStyle}>React Native</Text>
    <Text>Flatten Style</Text>
    <Text style={page.code}>
      {JSON.stringify(flattenStyle, null, 2)}
    </Text>
  </View>
);
const page = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    alignItems: "center"
  },
  text: {
    color: "#000",
    fontSize: 14,
    fontWeight: "bold"
  },
  code: {
    marginTop: 12,
    padding: 12,
    borderRadius: 8,
    color: "#666",
    backgroundColor: "#eaeaea"
  }
});
const typography = StyleSheet.create({
  header: {
    color: "#61dafb",
    fontSize: 30,
    marginBottom: 36
  }
});
const flattenStyle = StyleSheet.flatten([
  page.text,
  typography.header
]);
export default App;

image.png

absoluteFill

绝对填充可以用于方便并减少重复这些重复的样式。如果需要,absoluteFill 可用于在样式表中创建自定义条目

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const App = () => (
  <View style={styles.container}>
    <View style={styles.box1}>
      <Text style={styles.text}>1</Text>
    </View>
    <View style={styles.box2}>
      <Text style={styles.text}>2</Text>
    </View>
    <View style={styles.box3}>
      <Text style={styles.text}>3</Text>
    </View>
  </View>
);
const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  box1: {
    position: 'absolute',
    top: 40,
    left: 40,
    width: 100,
    height: 100,
    backgroundColor: 'red'
  },
  box2: {
    ...StyleSheet.absoluteFill,
    width: 100,
    height: 100,
    backgroundColor: 'blue'
  },
  box3: {
    position: 'absolute',
    top: 120,
    left: 120,
    width: 100,
    height: 100,
    backgroundColor: 'green'
  },
  text: {
    color: '#FFF',
    fontSize: 80
  }
});
export default App;

image.png

交互控件

Button

import React from 'react';
import { StyleSheet, Button, View, SafeAreaView, Text, Alert } from 'react-native';
import Constants from 'expo-constants';
const Separator = () => {
  return <View style={styles.separator} />;
}
const App = () => {
  return (
    <SafeAreaView style={styles.container}>
      <View>
        <Text style={styles.title}>
          The title and onPress handler are required. It is recommended to set
          accessibilityLabel to help make your app usable by everyone.
        </Text>
        <Button
          title="Press me"
          onPress={() => Alert.alert('Simple Button pressed')}
        />
      </View>
      <Separator />
      <View>
        <Text style={styles.title}>
          Adjust the color in a way that looks standard on each platform. On
          iOS, the color prop controls the color of the text. On Android, the
          color adjusts the background color of the button.
        </Text>
        <Button
          title="Press me"
          color="#f194ff"
          onPress={() => Alert.alert('Button with adjusted color pressed')}
        />
      </View>
      <Separator />
      <View>
        <Text style={styles.title}>
          All interaction for the component are disabled.
        </Text>
        <Button
          title="Press me"
          disabled
          onPress={() => Alert.alert('Cannot press this one')}
        />
      </View>
      <Separator />
      <View>
        <Text style={styles.title}>
          This layout strategy lets the title define the width of the button.
        </Text>
        <View style={styles.fixToText}>
          <Button
            title="Left button"
            onPress={() => Alert.alert('Left button pressed')}
          />
          <Button
            title="Right button"
            onPress={() => Alert.alert('Right button pressed')}
          />
        </View>
      </View>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: Constants.statusBarHeight,
    marginHorizontal: 16,
  },
  title: {
    textAlign: 'center',
    marginVertical: 8,
  },
  fixToText: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  separator: {
    marginVertical: 8,
    borderBottomColor: '#737373',
    borderBottomWidth: StyleSheet.hairlineWidth,
  },
});
export default App;

image.png

picker

 yarn add @react-native-picker/picker
import {Picker} from '@react-native-picker/picker'

const [selectedLanguage, setSelectedLanguage] = useState();
<Picker
  selectedValue={selectedLanguage}
  onValueChange={(itemValue, itemIndex) =>
    setSelectedLanguage(itemValue)
  }>
  <Picker.Item label="Java" value="java" />
  <Picker.Item label="JavaScript" value="js" />
</Picker>

image.png

slider

yarn add @react-native-community/slider
import Slider from '@react-native-community/slider'

 <Slider
    style={{width: 200, height: 40}}
    minimumValue={0}
    maximumValue={1}
    minimumTrackTintColor="#FFFFFF"
    maximumTrackTintColor="#000000"
  />

image.png

Switch

import React, { useState } from "react";
import { View, Switch, StyleSheet } from "react-native";
const App = () => {
  const [isEnabled, setIsEnabled] = useState(false);
  const toggleSwitch = () => setIsEnabled(previousState => !previousState);
  return (
    <View style={styles.container}>
      <Switch
        trackColor={{ false: "#767577", true: "#81b0ff" }}
        thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"}
        ios_backgroundColor="#3e3e3e"
        onValueChange={toggleSwitch}
        value={isEnabled}
      />
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  }
});
export default App;

image.png

列表视图

FlatList

  • 高性能的简单列表组件,支持下面这些常用的功能: 完全跨平台。

支持水平布局模式。

行组件显示或隐藏时可配置回调事件。

支持单独的头部组件。

支持单独的尾部组件。

支持自定义行间分隔线。

支持下拉刷新。

支持上拉加载。

支持跳转到指定行(ScrollToIndex)。

支持多列布局。

如果需要分组/类/区(section),请使用SectionList.

import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const DATA = [
  {
    id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
    title: 'First Item',
  },
  {
    id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
    title: 'Second Item',
  },
  {
    id: '58694a0f-3da1-471f-bd96-145571e29d72',
    title: 'Third Item',
  },
];
const Item = ({ title }) => {
  return (
    <View style={styles.item}>
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}
const App = () => {
  const renderItem = ({ item }) => (
    <Item title={item.title} />
  );
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
  },
});
export default App;

image.png

SectionList

  • 高性能的分组(section)列表组件,支持下面这些常用的功能: 完全跨平台。

行组件显示或隐藏时可配置回调事件。

支持单独的头部组件。

支持单独的尾部组件。

支持自定义行间分隔线

支持分组的头部组件。

支持分组的分隔线。

支持多种数据源结构

支持下拉刷新。

支持上拉加载。

如果你的列表不需要分组(section),那么可以使用结构更简单的<FlatList>

import React from "react";
import { StyleSheet, Text, View, SafeAreaView, SectionList, StatusBar } from "react-native";

const DATA = [
  {
    title: "Main dishes",
    data: ["Pizza", "Burger", "Risotto"]
  },
  {
    title: "Sides",
    data: ["French Fries", "Onion Rings", "Fried Shrimps"]
  },
  {
    title: "Drinks",
    data: ["Water", "Coke", "Beer"]
  },
  {
    title: "Desserts",
    data: ["Cheese Cake", "Ice Cream"]
  }
];
const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);
const App = () => (
  <SafeAreaView style={styles.container}>
    <SectionList
      sections={DATA}
      keyExtractor={(item, index) => item + index}
      renderItem={({ item }) => <Item title={item} />}
      renderSectionHeader={({ section: { title } }) => (
        <Text style={styles.header}>{title}</Text>
      )}
    />
  </SafeAreaView>
);
const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: StatusBar.currentHeight,
    marginHorizontal: 16
  },
  item: {
    backgroundColor: "#f9c2ff",
    padding: 20,
    marginVertical: 8
  },
  header: {
    fontSize: 32,
    backgroundColor: "#fff"
  },
  title: {
    fontSize: 24
  }
});
export default App;

image.png

iOS 独有组件 ActionSheetIOS

import React, { useState } from "react";
import { ActionSheetIOS, Button, StyleSheet, Text, View } from "react-native";
const App = () => {
  const [result, setResult] = useState("🔮");
  const onPress = () =>
    ActionSheetIOS.showActionSheetWithOptions(
      {
        options: ["Cancel", "Generate number", "Reset"],
        destructiveButtonIndex: 2,
        cancelButtonIndex: 0,
        userInterfaceStyle: 'dark'
      },
      buttonIndex => {
        if (buttonIndex === 0) {
          // cancel action
        } else if (buttonIndex === 1) {
          setResult(Math.floor(Math.random() * 100) + 1);
        } else if (buttonIndex === 2) {
          setResult("🔮");
        }
      }
    );
  return (
    <View style={styles.container}>
      <Text style={styles.result}>{result}</Text>
      <Button onPress={onPress} title="Show Action Sheet" />
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center"
  },
  result: {
    fontSize: 64,
    textAlign: "center"
  }
});
export default App;

image.png image.png

方法showActionSheetWithOptions

static showActionSheetWithOptions(options, callback)
  • 在 iOS 设备上显示一个 ActionSheet 弹出框,其中options参数为一个对象,其属性必须包含以下一项或多项: options (字符串数组) - 一组按钮的文字(必选)

cancelButtonIndex (整型) - 取消性质的按钮在options中的位置(索引)

destructiveButtonIndex (整型) - 删除性质的按钮在options中的位置(索引)

title (字符串) - 弹出框顶部的标题

message (字符串) - 弹出框顶部标题下方的信息

anchor (number) - the node to which the action sheet should be anchored (used for iPad)

tintColor (字符串) - 指定删除性质的按钮的文字的颜色

disabledButtonIndices (array of numbers) - a list of button indices which should be disabled

userInterfaceStyle (string) - the interface style used for the action sheet, can be set to light or dark, otherwise the default system style will be used

eg:

ActionSheetIOS.showActionSheetWithOptions({
  options: ['取消', '删除'],
  destructiveButtonIndex: 1,
  cancelButtonIndex: 0,
},
(buttonIndex) => {
if (buttonIndex === 1) { /* 当接收到的索引为1,即点击了删除按钮时,执行对应操作 */ }
});

showShareActionSheetWithOptions

static showShareActionSheetWithOptions(options, failureCallback, successCallback)
  • 在 iOS 设备上显示一个分享弹出框,其中options参数为一个对象,其属性包含以下几项(必须至少有 message 或 url):

url (字符串) - 要分享的 URL 地址

message (字符串) - 要分享的信息

subject (字符串) - 要分享的信息主题

excludedActivityTypes (数组) - 指定在 actionsheet 中不显示的活动

注:如果url指向本地文件,或者是一个 base64 编码的 url,则会直接读取并分享相应的文件。你可以用这样的方式来分享图片、视频以及 PDF 文件等。

  • 'successCallback'函数接受两个参数: 表示成功与否的布尔值

成功的话返回一个表示分享方式的字符串

Android 独有组件

BackHandler

BackHandler API 用于监听设备上的后退按钮事件,可以调用你自己的函数来处理后退行为。此 API 仅能在 Android 上使用。

  • 回调函数是倒序执行的(即后添加的函数先执行)。 如果某一个函数返回 true,则后续的函数都不会被调用。

如果没有添加任何监听函数,或者所有的监听函数都返回 false,则会执行默认行为,退出应用。

import React, { useEffect } from "react";
import { Text, View, StyleSheet, BackHandler, Alert } from "react-native";
const App = () => {
  useEffect(() => {
    const backAction = () => {
      Alert.alert("Hold on!", "Are you sure you want to go back?", [
        {
          text: "Cancel",
          onPress: () => null,
          style: "cancel"
        },
        { text: "YES", onPress: () => BackHandler.exitApp() }
      ]);
      return true;
    };
    const backHandler = BackHandler.addEventListener(
      "hardwareBackPress",
      backAction
    );
    return () => backHandler.remove();
  }, []);
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Click Back button!</Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  text: {
    fontSize: 18,
    fontWeight: "bold"
  }
});
export default App;

image.png image.png

import React, { useEffect } from "react";
import { Text, View, StyleSheet, BackHandler, Alert } from "react-native";
const App = () => {
  const backAction = () => {
    Alert.alert("Hold on!", "Are you sure you want to go back?", [
      {
        text: "Cancel",
        onPress: () => null,
        style: "cancel"
      },
      { text: "YES", onPress: () => BackHandler.exitApp() }
    ]);
    return true;
  };
  useEffect(() => {
   BackHandler.addEventListener("hardwareBackPress", backAction);
    return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
  }, []);
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Click Back button!</Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  text: {
    fontSize: 18,
    fontWeight: "bold"
  }
});
export default App;

image.png

DrawerLayoutAndroid

封装了 Android 平台DrawerLayout的 React 组件。抽屉(通常用于导航切换)是通过renderNavigationView方法渲染的,并且 DrawerLayoutAndroid 的直接子视图会成为主视图(用于放置内容)。

import React, { useState } from "react";
import { Button, DrawerLayoutAndroid, Text, StyleSheet, View } from "react-native";
const App = () => {
  const [drawerPosition, setDrawerPosition] = useState("left");
  const changeDrawerPosition = () => {
    if (drawerPosition === "left") {
      setDrawerPosition("right");
    } else {
      setDrawerPosition("left");
    }
  };
  const navigationView = (
    <View style={styles.navigationContainer}>
      <Text style={{ margin: 10, fontSize: 15 }}>I'm in the Drawer!</Text>
    </View>
  );
  return (
    <DrawerLayoutAndroid
      drawerWidth={300}
      drawerPosition={drawerPosition}
      renderNavigationView={() => navigationView}
    >
      <View style={styles.container}>
        <Text style={{ margin: 10, fontSize: 15 }}>
          DrawerLayoutAndroid example
        </Text>
        <Button
          title="Change Drawer Position"
          onPress={() => changeDrawerPosition()}
        />
        <Text style={{ margin: 10, fontSize: 15 }}>
          Drawer on the {drawerPosition}! Swipe from the side to see!
        </Text>
      </View>
    </DrawerLayoutAndroid>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    paddingTop: 50,
    backgroundColor: "#ecf0f1",
    padding: 8
  },
  navigationContainer: {
    flex: 1,
    paddingTop: 50,
    backgroundColor: "#fff",
    padding: 8
  }
});
export default App;

image.png

PermissionsAndroid

PermissionsAndroid 可以访问 Android M(也就是 6.0)开始提供的权限模型。有一些权限写在AndroidManifest.xml就可以在安装时自动获得,但有一些“危险”的权限则需要弹出提示框供用户选择。本 API 即用于后一种情形。

import React from "react";
import { StyleSheet, Text, View, SafeAreaView, PermissionsAndroid, Button } from "react-native";
import Constants from "expo-constants";
const requestCameraPermission = async () => {
  try {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.CAMERA,
      {
        title: "Cool Photo App Camera Permission",
        message:
          "Cool Photo App needs access to your camera " +
          "so you can take awesome pictures.",
        buttonNeutral: "Ask Me Later",
        buttonNegative: "Cancel",
        buttonPositive: "OK"
      }
    );
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      console.log("You can use the camera");
    } else {
      console.log("Camera permission denied");
    }
  } catch (err) {
    console.warn(err);
  }
};
const App = () => (
  <View style={styles.container}>
    <Text style={styles.item}>Try permissions</Text>
    <Button title="request permissions" onPress={requestCameraPermission} />
  </View>
);
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    paddingTop: Constants.statusBarHeight,
    backgroundColor: "#ecf0f1",
    padding: 8
  },
  item: {
    margin: 24,
    fontSize: 18,
    fontWeight: "bold",
    textAlign: "center"
  }
});
export default App;

image.png image.png

ToastAndroid

本模块将原生的 ToastAndroid 模块导出为一个 JS 模块,用于在 Android 设备上显示一个悬浮的提示信息。本模块包含一个show方法接受以下的参数:

  • message 一个字符串,表示将要显示的文本内容。
  • duration 提示信息持续显示的时间。可以是ToastAndroid.SHORT或者ToastAndroid.LONG
mport React from "react";
import { View, StyleSheet, ToastAndroid, Button, StatusBar } from "react-native";
const App = () => {
  const showToast = () => {
    ToastAndroid.show("A pikachu appeared nearby !", ToastAndroid.SHORT);
  };
  const showToastWithGravity = () => {
    ToastAndroid.showWithGravity(
      "All Your Base Are Belong To Us",
      ToastAndroid.SHORT,
      ToastAndroid.CENTER
    );
  };
  const showToastWithGravityAndOffset = () => {
    ToastAndroid.showWithGravityAndOffset(
      "A wild toast appeared!",
      ToastAndroid.LONG,
      ToastAndroid.BOTTOM,
      25,
      50
    );
  };
  return (
    <View style={styles.container}>
      <Button title="Toggle Toast" onPress={() => showToast()} />
      <Button
        title="Toggle Toast With Gravity"
        onPress={() => showToastWithGravity()}
      />
      <Button
        title="Toggle Toast With Gravity & Offset"
        onPress={() => showToastWithGravityAndOffset()}
      />
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    paddingTop: StatusBar.currentHeight,
    backgroundColor: "#888888",
    padding: 8
  }
});
export default App;

image.png

Imperative hack
import React, { useState, useEffect } from "react";
import { View, StyleSheet, ToastAndroid, Button, StatusBar } from "react-native";
const Toast = ({ visible, message }) => {
  if (visible) {
    ToastAndroid.showWithGravityAndOffset(
      message,
      ToastAndroid.LONG,
      ToastAndroid.BOTTOM,
      25,
      50
    );
    return null;
  }
  return null;
};
const App = () => {
  const [visibleToast, setvisibleToast] = useState(false);
  useEffect(() => setvisibleToast(false), [visibleToast]);
  const handleButtonPress = () => {
    setvisibleToast(true);
  };
  return (
    <View style={styles.container}>
      <Toast visible={visibleToast} message="Example" />
      <Button title="Toggle Toast" onPress={() => handleButtonPress()} />
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    paddingTop: StatusBar.currentHeight,
    backgroundColor: "#888888",
    padding: 8
  }
});
export default App;

image.png

方法
static show(message, duration)
static showWithGravity(message, duration, gravity)
static showWithGravityAndOffset(message, duration, gravity, xOffset, yOffset)
属性

Indicates the duration on the screen.

ToastAndroid.SHORT;

Indicates the duration on the screen.

ToastAndroid.LONG;

Indicates the position on the screen.

ToastAndroid.TOP;

其他

ActivityIndicator

显示一个圆形的 loading 提示符号。

import React from "react";
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
const App = () => (
  <View style={[styles.container, styles.horizontal]}>
    <ActivityIndicator />
    <ActivityIndicator size="large" />
    <ActivityIndicator size="small" color="#0000ff" />
    <ActivityIndicator size="large" color="#00ff00" />
  </View>
);
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center"
  },
  horizontal: {
    flexDirection: "row",
    justifyContent: "space-around",
    padding: 10
  }
});
export default App;

image.png

Alert

启动一个提示对话框,包含对应的标题和信息。

import React, { useState } from "react";
import { View, StyleSheet, Button, Alert } from "react-native";
const App = () => {
  const createTwoButtonAlert = () =>
    Alert.alert(
      "Alert Title",
      "My Alert Msg",
      [
        {
          text: "Cancel",
          onPress: () => console.log("Cancel Pressed"),
          style: "cancel"
        },
        { text: "OK", onPress: () => console.log("OK Pressed") }
      ]
    );

  const createThreeButtonAlert = () =>
    Alert.alert(
      "Alert Title",
      "My Alert Msg",
      [
        {
          text: "Ask me later",
          onPress: () => console.log("Ask me later pressed")
        },
        {
          text: "Cancel",
          onPress: () => console.log("Cancel Pressed"),
          style: "cancel"
        },
        { text: "OK", onPress: () => console.log("OK Pressed") }
      ]
    );
  return (
    <View style={styles.container}>
      <Button title={"2-Button Alert"} onPress={createTwoButtonAlert} />
      <Button title={"3-Button Alert"} onPress={createThreeButtonAlert} />
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "space-around",
    alignItems: "center"
  }
});
export default App;

image.png image.png

方法
static alert(title, message?, buttons?, options?)

title:string The dialog's title. Passing null or empty string will hide the title.

message:string An optional message that appears below the dialog's title.

buttons:Buttons An optional array containg buttons configuration.

options:Options-Android An optional Alert configuration for the Android.

static prompt(title, message?, callbackOrButtons?, type?, defaultValue?, keyboardType?)

title string Required The dialog's title.

message string An optional message that appears above the text input.

callbackOrButtons:function|Buttons If passed a function, it will be called with the prompt's value (text: string) => void, when the user taps 'OK'. If passed an array, buttons will be configured based on the array content.

类型:AlertType This configures the text input.

defaultValue:string The default text in text input.

keyboardType:string The keyboard type of first text field (if exists). One of TextInput keyboardTypes.

Animated

Animated库旨在使动画变得流畅,强大并易于构建和维护。Animated侧重于输入和输出之间的声明性关系,以及两者之间的可配置变换,此外还提供了简单的 start/stop方法来控制基于时间的动画执行。

请不要直接修改动画值!你可以用useRef Hook来返回一个可修改的 ref 引用。ref 对象的current属性在初始化时被赋予给定的动画值,且在组件的生命周期内保存不被销毁。

import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button } from "react-native";

const App = () => {
  // fadeAnim will be used as the value for opacity. Initial Value: 0
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const fadeIn = () => {
    // Will change fadeAnim value to 1 in 5 seconds
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 5000
    }).start();
  };
  const fadeOut = () => {
    // Will change fadeAnim value to 0 in 5 seconds
    Animated.timing(fadeAnim, {
      toValue: 0,
      duration: 5000
    }).start();
  };

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.fadingContainer,
          {
            opacity: fadeAnim // Bind opacity to animated value
          }
        ]}
      >
        <Text style={styles.fadingText}>Fading View!</Text>
      </Animated.View>
      <View style={styles.buttonRow}>
        <Button title="Fade In" onPress={fadeIn} />
        <Button title="Fade Out" onPress={fadeOut} />
      </View>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  fadingContainer: {
    paddingVertical: 8,
    paddingHorizontal: 16,
    backgroundColor: "powderblue"
  },
  fadingText: {
    fontSize: 28,
    textAlign: "center",
    margin: 10
  },
  buttonRow: {
    flexDirection: "row",
    marginVertical: 16
  }
});
export default App;

image.png

配置动画

Animated提供了三种动画类型。每种动画类型都提供了特定的函数曲线,用于控制动画值从初始值变化到最终值的变化过程:

  • Animated.decay()以指定的初始速度开始变化,然后变化速度越来越慢直至停下。
  • Animated.spring()提供了一个基础的弹簧物理模型.
  • Animated.timing()使用easing 函数让数值随时间动起来。
使用动画

通过在动画上调用start()来启动动画。 start()可以传入一个回调函数,以便在动画完成时得到通知调用。如果动画运行正常,则完成回调收到的值为{finished:true}。如果动画是因为调用了stop()而结束(例如,因为它被手势或其他动画中断),则它会收到{finished:false}。

Animated.timing({}).start(({ finished }) => {
  /* 动画完成的回调函数 */
});
组合动画

动画还可以使用组合函数以复杂的方式进行组合:

  • imated.delay()在给定延迟后开始动画。
  • imated.parallel()同时启动多个动画。
  • imated.sequence()按顺序启动动画,等待每一个动画完成后再开始下一个动画。
  • mated.stagger()按照给定的延时间隔,顺序并行的启动动画。
事件

手势,如平移或滚动,以及其他事件可以使用Animated.event()直接映射到动画值。这是通过结构化映射语法完成的,以便可以从复杂的事件对象中提取值。第一层参数是一个数组,你可以在其中指定多个参数映射,这种映射可以是嵌套的对象。

例如,在使用水平滚动手势时,为了将event.nativeEvent.contentOffset.x映射到scrollX(Animated.Value),您需要执行以下操作:

onScroll={Animated.event(
   // scrollX = e.nativeEvent.contentOffset.x
   [{ nativeEvent: {
        contentOffset: {
          x: scrollX
        }
      }
    }]
 )}

Dimensions

本模块用于获取设备屏幕的宽高。

import { Dimensions } from 'react-native';
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;

KeyboardAvoidingView

本组件用于解决一个常见的尴尬问题:手机上弹出的键盘常常会挡住当前的视图。本组件可以自动根据键盘的高度,调整自身的 height 或底部的 padding,以避免被遮挡。

import React from 'react';
import { View, KeyboardAvoidingView, TextInput, StyleSheet, Text, Platform, TouchableWithoutFeedback, Button, Keyboard  } from 'react-native';
const KeyboardAvoidingComponent = () => {
  return (
    <KeyboardAvoidingView
      behavior={Platform.OS == "ios" ? "padding" : "height"}
      style={styles.container}
    >
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <View style={styles.inner}>
          <Text style={styles.header}>Header</Text>
          <TextInput placeholder="Username" style={styles.textInput} />
          <View style={styles.btnContainer}>
            <Button title="Submit" onPress={() => null} />
          </View>
        </View>
      </TouchableWithoutFeedback>
    </KeyboardAvoidingView>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  inner: {
    padding: 24,
    flex: 1,
    justifyContent: "space-around"
  },
  header: {
    fontSize: 36,
    marginBottom: 48
  },
  textInput: {
    height: 40,
    borderColor: "#000000",
    borderBottomWidth: 1,
    marginBottom: 36
  },
  btnContainer: {
    backgroundColor: "white",
    marginTop: 12
  }
});
export default KeyboardAvoidingComponent;

image.png

Linking

Linking提供了一个通用的接口来与传入和传出的 App 链接进行交互。

Open Links and Deep Links (Universal Links)

import React, { useCallback } from "react";
import { Alert, Button, Linking, StyleSheet, View } from "react-native";
const supportedURL = "https://google.com";
const unsupportedURL = "slack://open?team=123456";
const OpenURLButton = ({ url, children }) => {
  const handlePress = useCallback(async () => {
    // Checking if the link is supported for links with custom URL scheme.
    const supported = await Linking.canOpenURL(url);

    if (supported) {
      // Opening the link with some app, if the URL scheme is "http" the web link should be opened
      // by some browser in the mobile
      await Linking.openURL(url);
    } else {
      Alert.alert(`Don't know how to open this URL: ${url}`);
    }
  }, [url]);
  return <Button title={children} onPress={handlePress} />;
};
const App = () => {
  return (
    <View style={styles.container}>
      <OpenURLButton url={supportedURL}>Open Supported URL</OpenURLButton>
      <OpenURLButton url={unsupportedURL}>Open Unsupported URL</OpenURLButton>
    </View>
  );
};
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default App;

image.png

image.png

image.png

Open Custom Settings

import React, { useCallback } from "react";
import { Button, Linking, StyleSheet, View } from "react-native";
const OpenSettingsButton = ({ children }) => {
  const handlePress = useCallback(async () => {
    // Open the custom settings if the app has one
    await Linking.openSettings();
  }, []);
  return <Button title={children} onPress={handlePress} />;
};
const App = () => {
  return (
    <View style={styles.container}>
      <OpenSettingsButton>Open Settings</OpenSettingsButton>
    </View>
  );
};
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default App;

image.png

image.png

获取 Deep Link

import React, { useState, useEffect } from "react";
import { Linking, StyleSheet, Text, View } from "react-native";
const useMount = func => useEffect(() => func(), []);
const useInitialURL = () => {
  const [url, setUrl] = useState(null);
  const [processing, setProcessing] = useState(true);
  useMount(() => {
    const getUrlAsync = async () => {
      // Get the deep link used to open the app
      const initialUrl = await Linking.getInitialURL();
      // The setTimeout is just for testing purpose
      setTimeout(() => {
        setUrl(initialUrl);
        setProcessing(false);
      }, 1000);
    };
    getUrlAsync();
  });
  return { url, processing };
};
const App = () => {
  const { url: initialUrl, processing } = useInitialURL();
  return (
    <View style={styles.container}>
      <Text>
        {processing
          ? `Processing the initial url from a deep link`
          : `The deep link is: ${initialUrl || "None"}`}
      </Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default App;

image.png

发送 Intents (Android)

import React, { useCallback } from "react";
import { Alert, Button, Linking, StyleSheet, View } from "react-native";
const SendIntentButton = ({ action, extras, children }) => {
  const handlePress = useCallback(async () => {
    try {
      await Linking.sendIntent(action, extras);
    } catch (e) {
      Alert.alert(e.message);
    }
  }, [action, extras]);

  return <Button title={children} onPress={handlePress} />;
};
const App = () => {
  return (
    <View style={styles.container}>
      <SendIntentButton action="android.intent.action.POWER_USAGE_SUMMARY">
        Power Usage Summary
      </SendIntentButton>
      <SendIntentButton
        action="android.settings.APP_NOTIFICATION_SETTINGS"
        extras={[
          { "android.provider.extra.APP_PACKAGE": "com.facebook.katana" },
        ]}
      >
        App Notification Settings
      </SendIntentButton>
    </View>
  );
};
const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default App;

Modal

Modal 组件是一种简单的覆盖在其他视图之上显示内容的方式。

import React, { useState } from "react";
import {
  Alert,
  Modal,
  StyleSheet,
  Text,
  TouchableHighlight,
  View
} from "react-native";
const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  return (
    <View style={styles.centeredView}>
      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => {
          Alert.alert("Modal has been closed.");
          setModalVisible(!modalVisible);
        }}
      >
        <View style={styles.centeredView}>
          <View style={styles.modalView}>
            <Text style={styles.modalText}>Hello World!</Text>
            <TouchableHighlight
              style={{ ...styles.openButton, backgroundColor: "#2196F3" }}
              onPress={() => {
                setModalVisible(!modalVisible);
              }}
            >
              <Text style={styles.textStyle}>Hide Modal</Text>
            </TouchableHighlight>
          </View>
        </View>
      </Modal>

      <TouchableHighlight
        style={styles.openButton}
        onPress={() => {
          setModalVisible(true);
        }}
      >
        <Text style={styles.textStyle}>Show Modal</Text>
      </TouchableHighlight>
    </View>
  );
};
const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },
  modalView: {
    margin: 20,
    backgroundColor: "white",
    borderRadius: 20,
    padding: 35,
    alignItems: "center",
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5
  },
  openButton: {
    backgroundColor: "#F194FF",
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },
  modalText: {
    marginBottom: 15,
    textAlign: "center"
  }
});
export default App;

image.png

image.png

PixelRatio

PixelRatio 可以获取到设备的像素密度和字体缩放比。

import React from "react";
import { Image, PixelRatio, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";
const size = 50;
const cat = {
  uri: "https://reactnative.dev/docs/assets/p_cat1.png",
  width: size,
  height: size
};
const App = () => (
  <ScrollView style={styles.scrollContainer}>
    <View style={styles.container}>
      <Text>Current Pixel Ratio is:</Text>
      <Text style={styles.value}>{PixelRatio.get()}</Text>
    </View>
    <View style={styles.container}>
      <Text>Current Font Scale is:</Text>
      <Text style={styles.value}>{PixelRatio.getFontScale()}</Text>
    </View>
    <View style={styles.container}>
      <Text>On this device images with a layout width of</Text>
      <Text style={styles.value}>{size} px</Text>
      <Image source={cat} />
    </View>
    <View style={styles.container}>
      <Text>require images with a pixel width of</Text>
      <Text style={styles.value}>
        {PixelRatio.getPixelSizeForLayoutSize(size)} px
      </Text>
      <Image
        source={cat}
        style={{
          width: PixelRatio.getPixelSizeForLayoutSize(size),
          height: PixelRatio.getPixelSizeForLayoutSize(size)
        }}
      />
    </View>
  </ScrollView>
);
const styles = StyleSheet.create({
  scrollContainer: {
    flext: 1,
    marginTop: "2em",
    justifyContent: "center",
  },
  container: {
    justifyContent: "center",
    alignItems: "center"
  },
  value: {
    fontSize: 24,
    marginBottom: 12,
    marginTop: 4
  }
});
export default App;

image.png

RefreshControl

这一组件可以用在 ScrollView 或 FlatList 内部,为其添加下拉刷新的功能。当 ScrollView 处于竖直方向的起点位置(scrollY: 0),此时下拉会触发一个onRefresh事件。

import React from 'react';
import {
  ScrollView,
  RefreshControl,
  StyleSheet,
  Text,
  SafeAreaView,
} from 'react-native';
import Constants from 'expo-constants';
const wait = (timeout) => {
  return new Promise(resolve => {
    setTimeout(resolve, timeout);
  });
}
const App = () => {
  const [refreshing, setRefreshing] = React.useState(false);

  const onRefresh = React.useCallback(() => {
    setRefreshing(true);

    wait(2000).then(() => setRefreshing(false));
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView
        contentContainerStyle={styles.scrollView}
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }
      >
        <Text>Pull down to see RefreshControl indicator</Text>
      </ScrollView>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: Constants.statusBarHeight,
  },
  scrollView: {
    flex: 1,
    backgroundColor: 'pink',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
export default App;

image.png 注意: refreshing是一个受控属性, 所以必须在onRefresh函数中设置为 true,否则 loading 指示器会立即停止。

StatusBar

控制应用状态栏的组件。

和导航器一起使用的注意事项#

由于StatusBar可以在任意视图中加载,可以放置多个且后加载的会覆盖先加载的。因此在配合导航器使用时,请务必考虑清楚StatusBar的放置顺序。

import React, { useState } from "react";
import { Button, Text, StyleSheet, StatusBar, View } from "react-native";
import Constants from "expo-constants";
const App = () => {
  const styleTypes = ['default','dark-content', 'light-content'];
  const [visibleStatusBar, setVisibleStatusBar] = useState(false);
  const [styleStatusBar, setStyleStatusBar] = useState(styleTypes[0]);
  const changeVisibilityStatusBar = () => {
    setVisibleStatusBar(!visibleStatusBar);
  };
  const changeStyleStatusBar = () => {
    const styleId = styleTypes.indexOf(styleStatusBar) + 1;

    if(styleId === styleTypes.length){
      return setStyleStatusBar(styleTypes[0]);
    }
    return setStyleStatusBar(styleTypes[styleId]);
  };
  return (
    <View style={styles.container}>
      <View>
        <Text style={styles.textStyle}>StatusBar Style: {styleStatusBar}</Text>
        <Text style={styles.textStyle}>StatusBar Visibility: {!visibleStatusBar ? 'Visible': 'Hidden'}</Text>
      </View>
      <StatusBar backgroundColor="blue" barStyle={styleStatusBar} />
      <View>
        <StatusBar hidden={visibleStatusBar} />
      </View>
      <View style={styles.buttonContainer}>
        <Button title="Toggle StatusBar" onPress={() => changeVisibilityStatusBar()} />
      </View>
      <View style={styles.buttonContainer}>
        <Button title="Change StatusBar Style" onPress={() => changeStyleStatusBar()} />
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ECF0F1',
    padding: 8
  },
  buttonContainer:{
    padding: 10
  },
  textStyle:{
    textAlign: 'center'
  }
});
export default App;

image.png