• home > webfront > AppDev > ReactNative >

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

    Author:zhoulujun Date:

    react native还原UI界面跟写web react 差不多,布局及样式有css基础在StyleSheet里面写一样。其实跟Android写xml大同小异而已。只是react native动画方面,设置起来还是风格迥异

    写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组件

    React NativeAndroid ViewiOS ViewWeb AnalogDescription
    <View><ViewGroup><UIView>A non-scrollling

    <div>

    A container that supports layout with flexbox, style, some touch handling, and accessibility controls
    <Text><TextView><UITextView><p>Displays, styles, and nests strings of text and even handles touch events
    <Image><ImageView><UIImageView><img>Displays different types of images
    <ScrollView><ScrollView><UIScrollView><div>A generic scrolling container that can contain multiple components and views
    <TextInput><EditText><UITextField><input type="text">Allows the user to enter text

    核心组件和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