react-native 实现转场动画(非react-native-router-flux)

react

前言

因为目前用车是三方(android、ios、web)适配, 用的路由是react-router-native

react-native-router-flux路由自带转场动画效果, 转web报错, 想要做到兼容, 更改成本大

考虑成本, 只能自己实现一个转场动画

转场动画原理

动画开始前, 当前显示页面是b

页面index层级渲染结果
01页面b
10空白

如果是

  1. 确定下一个渲染页面的index, latestStackIndex=1, 当前页面index, oldStackIndex=0

  2. 判断下一个页面, 是新页面还是旧页面, 确定动画的初始值和终值

    1. 如果是新页面, 动画开始值从右往左, startLeft = screenWidthendLeft = 0

    2. 如果是回退到历史页面, 动画开始值从左往右, startLeft = 0endLeft = screenWidth

    3. 如果存在页面不需要动画, 直接设置新页面到动画的终态, startLeft = 0endLeft = 0, 销毁旧页面b

  3. 确定动画页面, 同时动画页面在另一个页面之上, 如果是新的路由页面animatedIndex = latestStackIndex, 如果是回退到历史页面, animatedIndex = oldStackIndex;

  4. 设置动画开始的初始值, 开始动画, 一直更改动画页面的位置

  5. 动画过程中, 就页面b不应该触发渲染

  6. 在动画结束后, 将旧页面b, 销毁

动画结果后

页面index层级渲染结果
00新渲染的页面
11空白

具体实现

1) 路由作为转场动画组建的children

<TransitionAnimation location={location}>

<RenderRoutes routes={routes} />

</TransitionAnimation>

2) RenderRoutes 路由

    1. Route, Switch 用react-router-dom中的, 而不是react-router-native

    1. Switch中参数location可以将路由受控, 而不是全局中的location

import * as React from 'react';

import { Route, Switch } from 'react-router-dom';

import sendEnterRouter from 'Common/buriedPoint';

export default function renderRoutes(props: { routes: []; extraProps: {}; switchProps: {} }) {

const { routes = [], extraProps = {}, switchProps = {} } = props;

return routes ? (

<Switch {...switchProps}>

{routes.map(

(

route: {

key?: string;

path: string;

exact: boolean;

strict?: boolean;

render?: Function;

component?: Function;

},

i: number

) => {

return route ? (

<Route

key={route.key || i}

path={route.path}

exact={route.exact}

strict={route.strict}

render={(props: object) => {

sendEnterRouter.enterRouter(route.path);

return route.render ? (

route.render({ ...props, ...extraProps, route })

) : (

<route.component {...props} {...extraProps} route={route} />

);

}}

/>

) : null;

}

)}

</Switch>

) : null;

}

3) 转场动画组建TransitionAnimation

    1. Animated.timing动画中指定参数useNativeDriver: false, 因为android会报黄色警告

    1. 样式属性elevation, zIndex 都是控制层级的

    1. shouldComponentUpdate方法, 控制是否重新渲染组建

import React from 'react';

import { Animated, View, StyleSheet } from 'react-native';

import utils from 'Common/utils';

interface IProps {

location: {

pathname: string;

};

}

interface IState {

animatedIndex: number;

pathList: Array<string>;

sceneList: Array<ISceneItem>;

}

interface ISceneItem {

pathname: string;

latest: boolean;

destory: boolean;

}

const width = utils.deviceWidthDp;

const defaultSceneItem = {

pathname: '',

latest: false,

destory: false

};

export default class TransitionAnimation extends React.Component<IProps, IState> {

state = {

animatedIndex: 1,

animatedLeft: new Animated.Value(width),

pathList: [],

sceneList: [1, 2].map(() => ({ ...defaultSceneItem }))

};

shouldComponentUpdate(nextProps: IProps, nextState: IState) {

const pathname = this.props.location.pathname;

const nextPathname = nextProps.location.pathname;

const isUpdate = pathname != nextPathname || this.state !== nextState;

if (pathname != nextPathname) {

let { animatedLeft, pathList, sceneList } = this.state;

let oldStackIndex = sceneList.findIndex((item) => item.pathname === pathname); // 旧页面的堆栈下标

const latestStackIndex = oldStackIndex === 1 ? 0 : 1; // 新页面的堆栈下标

oldStackIndex = latestStackIndex === 1 ? 0 : 1; // 保证旧页面的堆栈下标不为-1

const existPageIndex = pathList.findIndex((item) => item === nextPathname); // 渲染过的下标

const isExistPage = existPageIndex > -1; // 是否渲染过

let startLeft: number, // 动画起始

endLeft: number, // 动画终值

animatedIndex: number; // 动画的堆栈下标

if (isExistPage) {

startLeft = 0;

endLeft = width;

animatedIndex = oldStackIndex;

pathList.splice(existPageIndex + 1);

} else {

startLeft = width;

endLeft = 0;

animatedIndex = latestStackIndex;

// 如果是第一个新增页面, 不需要动画

if (pathList.length === 0) {

startLeft = 0;

}

pathList.push(nextPathname);

}

// 不需要动画的页面

if (

['/chooseCar', '/waitCar'].includes(nextPathname) ||

(['/callCar', '/chooseCar', '/waitCar'].includes(pathname) &&

['/callCar', '/chooseCar', '/waitCar'].includes(nextPathname))

) {

sceneList[oldStackIndex] = { pathname: nextPathname, latest: true, destory: false };

sceneList[latestStackIndex] = { pathname: '', latest: false, destory: true };

this.setState({

pathList: [...pathList],

sceneList: [...sceneList]

});

animatedLeft.setValue(0);

return isUpdate;

}

// 更新堆栈

sceneList.forEach((item) => {

item.latest = false;

item.destory = false;

});

sceneList[latestStackIndex] = {

...sceneList[latestStackIndex],

latest: true,

pathname: nextPathname

};

// 开始动画

const newState = {

animatedIndex,

pathList: [...pathList],

sceneList: [...sceneList]

};

animatedLeft.setValue(startLeft);

this.setState(newState, () => {

const { animatedLeft, sceneList } = this.state;

Animated.timing(animatedLeft, {

useNativeDriver: false,

toValue: endLeft,

duration: 300 // 让动画持续一段时间

} as Animated.TimingAnimationConfig).start(({ finished }) => {

if (finished) {

sceneList[oldStackIndex].destory = true;

this.setState({

sceneList: [...sceneList]

});

}

});

});

}

return isUpdate;

}

render() {

const { location, children } = this.props;

const { animatedIndex, animatedLeft, sceneList } = this.state;

return (

<View style={[styles.view]} pointerEvents="box-none">

{sceneList.map((item, index) => {

const isAnimatedIndex = animatedIndex === index;

return (

<Animated.View

key={index}

style={[

styles.animatedView,

{

left: isAnimatedIndex ? animatedLeft : 0,

elevation: isAnimatedIndex ? 1 : 0,

zIndex: isAnimatedIndex ? 1 : 0

}

]}

pointerEvents="box-none">

{!item.destory && (

<Scene index={index} location={location} {...item}>

{children}

</Scene>

)}

</Animated.View>

);

})}

</View>

);

}

}

interface ISceneProps extends ISceneItem {

location: object;

index: number;

}

class Scene extends React.Component<ISceneProps> {

shouldComponentUpdate(nextProps: ISceneProps) {

const { latest, pathname } = this.props;

const { latest: nextLatest, pathname: nextPathname } = nextProps;

const changeLatest = !latest && nextLatest;

const changePathname = latest && nextLatest && pathname !== nextPathname;

return !!(changeLatest || changePathname);

}

render() {

const { children, location } = this.props;

return React.cloneElement(children, {

switchProps: {

location

}

});

}

}

const styles = StyleSheet.create({

view: {

width: '100%',

height: '100%',

overflow: 'hidden'

},

animatedView: {

width: '100%',

height: '100%',

top: 0,

left: 0,

position: 'absolute'

}

});

以上是 react-native 实现转场动画(非react-native-router-flux) 的全部内容, 来源链接: utcz.com/z/382014.html

回到顶部