useNativeDriver 优化 Animated 的技巧

2,971 阅读3分钟

「本文已参与低调务实优秀中国好青年前端社群的写作活动」

前言

上一篇,主要介绍了如何编写简单动画,以及复杂的动画的思想。今天这篇,主要聊聊如何通过useNativeDriver 这个属性优化 Animated 动画。

useNativeDriver

还记得上一篇中,我们介绍了,我们最常用的动画更新函数就是 timing()timing()的配config选项中有一个选项叫做 useNativeDriver(使用原生驱动)。

Animated 的 API 是可序列化的(可转化为字符串以便通信和存储)。开启useNativeDriver可以在动画开始前就把所有的配置信息都发送到原生端,然后利用原生代码在 UI 线程上执行动画,而不用每一帧都在两端间来回沟通。这样动画完全脱离了 JS 线程,即便 JS 线程被卡住,也不会影响到动画了。

所以想要对动画进行优化,我们只需要在动画配置中加上useNativeDriver:true即可:

const animatedValue = useRef(new Animated.Value(0)).current;
//timing
Animated.timing(animatedValue, {
  toValue: 1,
  duration: 300,
  useNativeDriver: true // <--timing()方法在这里添加即可
}).start();
//event
<Animated.ScrollView
  scrollEventThrottle={1}
  onScroll={Animated.event(
    [
      {
        nativeEvent: {
          contentOffset: { y: animatedValue }
        }
      }
    ],
    { useNativeDriver: true } // <-- event()方法在这里添加
  )}>
  {content}
</Animated.ScrollView>

我们来讲上一节的进度条的代码中的useNativeDriver改成 true


const App = () => {
    //使用 ref 来保存 Animated.Value 动画值
    const widthX = useRef(new Animated.Value(0)).current;
    useEffect(() => {
        //表示进入页面后三秒钟时间内 widthX 变量从 0 变化到 375,记住最后一定要有start()来启动动画。
        Animated.timing(widthX, {
            toValue: 375,
            duration: 3000,
            useNativeDriver: true, //<-- 这里改成 true 会报错。
        }).start();
    }, []);

    return (
        <View style={styles.container}>
            <Animated.View
                style={{
                    height: 20,
                    width: widthX,
                    //这里用到了interpolate方法来进行插值计算,一个动画值对应了两个属性
                    backgroundColor: widthX.interpolate({
                        inputRange: [0, 375],
                        outputRange: ['green', 'red'],
                    }),
                }}
            >
            </Animated.View>
        </View>
    );
};
const styles = StyleSheet.create({
    container: {
        backgroundColor: '#ffffff',
        flex: 1,
        justifyContent: 'center',
    },
});

会报出如下错误:

image.png

报错信息翻译过来:样式属性width不被 animated 模块所支持。

由此可知,useNativeDriver 只能应用于非布局属性,如**transform,opacity**,而不能应用非布局属性上。

对于布局属性,我们如何进行优化呢?答案是将布局属性动画转换成非布局属性动画

布局属性转换技巧

  1. 元素颜色变化:比如元素的颜色由绿到红,我们可以创建两个相同大小的兄弟元素并分别设置为绿色和红色(红色元素在上层),并使用absolute布局让二者叠加。利用 opacity 来控制上层元素的不透明度由0变为1 即可。
  2. 宽度/高度的变化:外层使用一个相同大小的父元素设置overflow:hidden属性作为可见的窗口,利用 translate来移动内部元素,使得父元素展示的子元素大小发生变化。

接下来,我们就使用上面两个方法来对上面代码进行改造:

const App = () => {
    const widthX = useRef(new Animated.Value(0)).current;
    useEffect(() => {
        Animated.timing(widthX, {
            toValue: 1,
            duration: 3000,
            useNativeDriver: true,
        }).start();
    }, []);

    return (
        <View style={styles.container}>
            <View style={styles.parent}>
                <Animated.View
                    style={[
                        styles.brother,
                        {
                            transform: [{
                                translateX: widthX.interpolate({
                                    inputRange: [0, 1],
                                    outputRange: [0, 375],
                                }),
                            }],
                        },
                    ]}
                >
                </Animated.View>
                <Animated.View
                    style={[
                        styles.processBar,
                        {
                            transform: [{
                                translateX: widthX.interpolate({
                                    inputRange: [0, 1],
                                    outputRange: [0, 375],
                                }),
                            }],
                            opacity: widthX,
                        }]}
                >
                </Animated.View>
            </View>
        </View>
    );
};
const styles = StyleSheet.create({
    container: {
        backgroundColor: '#ffffff',
        flex: 1,
        justifyContent: 'center',
    },
    parent: {
        height: 20,
        width: 375,
        overflow: 'hidden',
    },
    brother: {
        position: 'absolute',
        height: 20,
        width: 375,
        left: -375,
        backgroundColor: 'green',
    },
    processBar: {
        position: 'absolute',
        height: 20,
        width: 375,
        left: -375,
        backgroundColor: 'red',
    },
});

看下效果,很完美:

2022-05-21_17-07-32 (1).gif

总结

在React Native中,使用 Animated 时,要尽可能使用useNativeDriver 来提升动画性能,但useNativeDriver只能用于非布局属性。对于布局属性,我们可以通过一些技巧将其转换成布局属性的动画。