写点什么

React Native UI 界面还原,组件布局与动画效果

作者:zhoulujun
  • 2023-04-09
    广东
  • 本文字数:5565 字

    阅读完需:约 18 分钟

写 React Native UI 和写 Android XML layout 布局 ,个人感觉是大同小异

在《ReactJS到React-Native,架构原理概述》里面提过

web 环境中,React 框架,JSX 源码通过 React 框架最终渲染到了浏览器的真实 DOM 中

在 React Native 框架中,JSX 源码通过 React Native 框架编译后,通过对应平台的 Bridge 实现了与原生框架的通信。如果我们在程序中调用了 React Native 提供的 API,那么 React Native 框架就通过 Bridge 调用原生框架中的方法。 因为 React Native 的底层为 React 框架,所以如果是 UI 层的变更,那么就映射为虚拟 DOM 后进行 diff 算法,diff 算法计算出变动后的 JSON 映射文件,最终由 Native 层将此 JSON 文件映射渲染到原生 App 的页面元素上,最终实现了在项目中只需要控制 state 以及 props 的变更来引起 iOS 与 Android 平台的 UI 变更。 编写的 React Native 代码最终会打包生成一个 main.bundle.js 文件供 App 加载,此文件可以在 App 设备本地,也可以存放于服务器上供 App 下载更新

Yoga

Yoga C 语言写的一个 CSS3/Flexbox 的跨平台 实现的 Flexbox 布局引擎, 意在打造一个跨 iOS、Android、Windows 平台在内的布局引擎,兼容 Flexbox 布局方式,让界面布局更加简单。

Yoga 通过实现许多设计师熟悉的 API 并在不同平台上向开发人员开放。

利用 YOGA 我们可以:

  • 只写一次布局,就可以得到在不同端上的布局展示。

  • 动态更改 view 的布局

目前已经被用于在 React Native 和 Weex 等开源项目中

但是 Yoga 只实现了 W3C 标准的一个子集,所以样式方面,也只有随着 Yoga 了

其根由还是 yoga FlexBox 布局引擎,https://yogalayout.com/

UI 组件

核心组件和 API:https://www.reactnative.cn/docs/components-and-apis


React Native 框架构成


A diagram showing React Native's Core Components are a subset of React Components that ship with React Native.


UI 样式

为了给 React-Native 组件加上样式,你需要在 JavaScript 中添加样式表。

<View style={styles.container}>    <Text style={styles.intro}>Hello world!</Text></View>
复制代码

Flexbox 构建响应式 App 的最佳选择——CSS 中的表现不太一致,React-Native 并不是为 web 元素而生,不能像 web 应用在 html 里面使用 CSS

这里还是体现了 Weex 优势

React 和宿主平台之间的桥接包含了一个缩减版 CSS 子集的实现。这个 CSS 子集主要通过 flexbox 进行布局,做到了尽量简单化,而不是去实现所有的 CSS 规则

React Native 也坚持使用内联样式,通过 JavaScript 对象进行样式组织。React 团队先前也提倡在 Web 环境的 React 中使用内联样式。

相对于样式表来说,使用样式对象可能需要一些思维上的调整,从而改变你编写样式的方法。然而,在 React Native 中,这是一个实用的转变。

当样式复杂时,建议使用 StyleSheet.create 来集中定义组件的样式。

const styles = StyleSheet.create({  blueText: {    color: 'blue',    fontSize: 30,  }});
复制代码

StyleSheet.create 可以弥补编写复杂样式时,不能使用 css 的不便。

宽高单位与布局调整

RN 中宽高可以直接通过 style 指定,与 web 不同的是,RN 中尺寸是无单位的,表示与设备像素无关的逻辑像素点

在组件样式中使用 flex 可以使其在可利用的空间中动态地扩张或收缩。flex 布局,跟 Android  LinearLayout layout_weight——值越大,组件获取剩余空间的比例越多,类似。不同的是,LinearLayout 可以设置 android:weightSum 属性,其子元素可以设置 android:layout_weight 属性,用于等分的效果。与 android 类似,flex 的优先级是高于 width 的

推荐阅读《Android XML与HTML5 排布布局分析与对比—style的异同

动画

React Native 提供了两个互补的动画系统:用于创建精细的交互控制的动画 Animated 和用于全局的布局动画 LayoutAnimation

Animated

Animated 旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用 start/stop 方法来控制动画按顺序执行。 Animated 仅封装了 6 个可以动画化的组件:View、Text、Image、ScrollView、FlatList 和 SectionList,不过你也可以使用 Animated.createAnimatedComponent()来封装你自己的组件。

import React, { useRef, useEffect } from 'react';import { Animated, Text, View } from 'react-native';//在FadeInView的构造函数里,创建了一个名为fadeAnim的Animated.Value,并放在state中const FadeInView = (props) => {  const fadeAnim = useRef(new Animated.Value(0)).current  // 透明度初始值设为0  React.useEffect(() => {    Animated.timing(                  // 随时间变化而执行动画      fadeAnim,                       // 动画中的变量值      {        toValue: 1,                   // 透明度最终变为1,即完全不透明        duration: 10000,              // 让动画持续一段时间      }    ).start();                        // 开始执行动画  }, [fadeAnim])
  return (    <Animated.View                 // 使用专门的可动画化的View组件      style={{        ...props.style,        opacity: fadeAnim,         // 将透明度绑定到动画变量值      }}    >      {props.children}    </Animated.View>  );}// 然后你就可以在组件中像使用`View`那样去使用`FadeInView`了export default () => {  return (    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>      <FadeInView style={{width: 250, height: 50, backgroundColor: 'powderblue'}}>        <Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text>      </FadeInView>    </View>  )}
复制代码

这一过程经过特别优化,执行效率会远高于反复调用 setState 和多次重渲染。

因为这一过程是纯声明式的,因此还有进一步优化的空间,比如我们可以把这些声明的配置序列化后发送到一个高优先级的线程上运行。

配置动画

动画拥有非常灵活的配置项。自定义的或预定义的 easing 函数、延迟、持续时间、衰减系数、弹性常数等都可以在对应类型的动画中进行配置。

Animated.timing(this.state.xPosition, {  toValue: 100,  easing: Easing.back(),  duration: 2000}).start();
复制代码

https://www.reactnative.cn/docs/animated#配置动画

组合动画

多个动画可以通过 parallel(同时执行)、sequence(顺序执行)、stagger 和 delay 来组合使用。它们中的每一个都接受一个要执行的动画数组,并且自动在适当的时候调用 start/stop。

Animated.sequence([  // decay, then spring to start and twirl  Animated.decay(position, {    // coast to a stop    velocity: { x: gestureState.vx, y: gestureState.vy }, // velocity from gesture release    deceleration: 0.997  }),  Animated.parallel([    // after decay, in parallel:    Animated.spring(position, {      toValue: { x: 0, y: 0 } // return to start    }),    Animated.timing(twirl, {      // and twirl      toValue: 360    })  ])]).start(); // start the sequence group
复制代码

默认情况下,如果任何一个动画被停止或中断了,组内所有其它的动画也会被停止。Parallel 有一个 stopTogether 属性,如果设置为 false,可以禁用自动停止。

在 Animated 文档的组合动画一节中列出了所有的组合方法。

合成动画值

const a = new Animated.Value(1);const b = Animated.divide(1, a);
Animated.spring(a, {  toValue: 2}).start();
复制代码

可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值

插值

每个动画属性都可以设置值变化区间

style={{    opacity: this.state.fadeAnim, // Binds directly    transform: [{      translateY: this.state.fadeAnim.interpolate({        inputRange:  [-300, -100, 0, 100, 101],        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0      }),    }],  }}
复制代码

interpolate()还支持定义多个区间段落,常用来定义静止区间等。举个例子,要让输入在接近-300 时取相反值,然后在输入接近-100 时到达 0,然后在输入接近 0 时又回到 1,接着一直到输入到 100 的过程中逐步回到 0,最后形成一个始终为 0 的静止区间,对于任何大于 100 的输入都返回 0。

跟踪动态值

动画中所设的值还可以通过跟踪别的值得到。你只要把 toValue 设置成另一个动态值而不是一个普通数字就行了。

Animated.spring(follower, { toValue: leader }).start();Animated.timing(opacity, {  toValue: pan.x.interpolate({    inputRange: [0, 300],    outputRange: [1, 0]  })}).start();
复制代码

比如在上面的代码片段中,leader 和 follower 可以同时为 valueXY 类型,这样 x 和 y 的值都会被跟踪。

启用原生动画驱动

通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。

Animated.timing(this.state.animatedValue, {  toValue: 1,  duration: 500,  useNativeDriver: true // <-- 加上这一行}).start();
复制代码

动画值在不同的驱动方式之间是不能兼容的。因此如果你在某个动画中启用了原生驱动,那么所有和此动画依赖相同动画值的其他动画也必须启用原生驱动。

原生驱动还可以在 Animated.event 中使用。 

<Animated.ScrollView // <-- 使用可动画化的ScrollView组件  scrollEventThrottle={1} // <-- 设为1以确保滚动事件的触发频率足够密集  onScroll={Animated.event(    [      {        nativeEvent: {          contentOffset: { y: this.state.animatedValue }        }      }    ],    { useNativeDriver: true } // <-- 加上这一行  )}>  {content}</Animated.ScrollView>
复制代码

其他这里省略了

LayoutAnimation API

LayoutAnimation 允许你在全局范围内创建和更新动画,这些动画会在下一次渲染或布局周期运行。它常用来更新 flexbox 布局,因为它可以无需测量或者计算特定属性就能直接产生动画。尤其是当布局变化可能影响到父节点(譬如“查看更多”展开动画既增加父节点的尺寸又会将位于本行之下的所有行向下推动)时,如果不使用 LayoutAnimation,可能就需要显式声明组件的坐标,才能使得所有受影响的组件能够同步运行动画。

注意尽管 LayoutAnimation 非常强大且有用,但它对动画本身的控制没有 Animated 或者其它动画库那样方便,所以如果你使用 LayoutAnimation 无法实现一个效果,那可能还是要考虑其他的方案。

另外,如果要在 Android 上使用 LayoutAnimation,那么目前还需要在 UIManager 中启用::

// 在执行任何动画代码之前,比如在入口文件App.js中执行UIManager.setLayoutAnimationEnabledExperimental &&  UIManager.setLayoutAnimationEnabledExperimental(true);
复制代码

在需要的地方

import React from 'react';import {  NativeModules,  LayoutAnimation,  Text,  TouchableOpacity,  StyleSheet,  View,} from 'react-native';
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&  UIManager.setLayoutAnimationEnabledExperimental(true);
export default class App extends React.Component {  state = {    w: 100,    h: 100,  };
  _onPress = () => {    // Animate the update    LayoutAnimation.spring();    this.setState({w: this.state.w + 15, h: this.state.h + 15})  }
  render() {    return (      <View style={styles.container}>        <View style={[styles.box, {width: this.state.w, height: this.state.h}]} />        <TouchableOpacity onPress={this._onPress}>          <View style={styles.button}>            <Text style={styles.buttonText}>Press me!</Text>          </View>        </TouchableOpacity>      </View>    );  }}
const styles = StyleSheet.create({  container: {    flex: 1,    alignItems: 'center',    justifyContent: 'center',  },  box: {    width: 200,    height: 200,    backgroundColor: 'red',  },  button: {    backgroundColor: 'black',    paddingHorizontal: 20,    paddingVertical: 15,    marginTop: 15,  },  buttonText: {    color: '#fff',    fontWeight: 'bold',  },});
复制代码

https://github.com/facebook/react-native/blob/master/Libraries/LayoutAnimation/LayoutAnimation.js


转载本站文章《React Native UI界面还原,组件布局与动画效果》,请注明出处:https://www.zhoulujun.cn/html/webfront/AppDev/ReactNative/8482.html

用户头像

zhoulujun

关注

还未添加个人签名 2021-06-25 加入

15年草根站长,尽在:zhoulujun.cn

评论

发布
暂无评论
React Native UI界面还原,组件布局与动画效果_zhoulujun_InfoQ写作社区