├── .circleci
└── config.yml
├── .eslintrc
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── index.js
├── package.json
├── release.config.js
└── src
├── AnimatedNumber.js
├── AnimatedText.js
├── Opacity.js
├── Scale.js
├── ScaleAndOpacity.js
├── Shake.js
├── SharedElement.js
├── SharedElementRenderer.js
├── TranslateX.js
├── TranslateXY.js
├── TranslateY.js
└── TranslateYAndOpacity.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 |
7 | # Used Workspace
8 | workspace: &workspace
9 | ~/mui
10 |
11 | # Config for node
12 | node_config: &node_config
13 | working_directory: *workspace
14 | docker:
15 | - image: circleci/node:8
16 |
17 | # Key to cache gradle dependecies
18 | yarn_key: &yarn_key
19 | yarn-cache-{{ checksum "yarn.lock" }}
20 |
21 |
22 | jobs:
23 | install-dependencies:
24 | <<: *node_config
25 | steps:
26 | - checkout
27 | - attach_workspace:
28 | at: *workspace
29 | - restore_cache:
30 | key: *yarn_key
31 | - run:
32 | name: Intal yarn dependencies
33 | command: yarn
34 | - save_cache:
35 | key: *yarn_key
36 | paths: node_modules
37 | - persist_to_workspace:
38 | root: .
39 | paths: .
40 |
41 | lint:
42 | <<: *node_config
43 | steps:
44 | - attach_workspace:
45 | at: *workspace
46 | - run:
47 | name: Lint
48 | command: yarn lint
49 |
50 | test:
51 | <<: *node_config
52 | steps:
53 | - attach_workspace:
54 | at: *workspace
55 | - run:
56 | name: Jest
57 | command: yarn test
58 |
59 | publish:
60 | <<: *node_config
61 | steps:
62 | - attach_workspace:
63 | at: *workspace
64 | - run:
65 | name: Publish
66 | command: yarn semantic-release
67 |
68 |
69 | workflows:
70 | version: 2
71 | test-and-deploy:
72 | jobs:
73 | - install-dependencies
74 | - lint:
75 | requires:
76 | - install-dependencies
77 | - publish:
78 | requires:
79 | - lint
80 | filters:
81 | branches:
82 | only: 'master'
83 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "jest": true
8 | },
9 | "parserOptions": {
10 | "sourceType": "module",
11 | "ecmaFeatures": {
12 | "jsx": true
13 | }
14 | },
15 | "extends": ["airbnb", "prettier"],
16 | "plugins": ["import", "react", "prettier", "react-native"],
17 | "rules": {
18 | "react/prop-types": "off",
19 | "coma-dangle": "always",
20 | "curly": ["error", "all"],
21 | "import/order": [
22 | "error",
23 | {
24 | "groups": [
25 | "builtin",
26 | "external",
27 | "internal",
28 | "parent",
29 | "sibling",
30 | "index"
31 | ]
32 | }
33 | ],
34 | "no-use-before-define": "off",
35 | "prettier/prettier": "error",
36 | "lines-between-class-members": ["error", "never"],
37 | "react/jsx-wrap-multilines": [
38 | "error",
39 | { "declaration": false, "assignment": false }
40 | ],
41 | "react/jsx-one-expression-per-line": "off",
42 | "react/default-props-match-prop-types": [
43 | "error",
44 | { "allowRequiredDefaults": true }
45 | ],
46 | "react/jsx-filename-extension": [
47 | "error",
48 | { "extensions": [".js", ".jsx"] }
49 | ],
50 | "react/require-default-props": "off"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store
32 | .DS_Store?
33 | ._*
34 | .Spotlight-V100
35 | .Trashes
36 | ehthumbs.db
37 | Thumbs.db
38 | fastlane/README*
39 |
40 | # Directories #
41 | ###############
42 | .gradle
43 | coverage
44 | build
45 | node_modules
46 | xcuserdata
47 | android/app/src/main/res
48 | log.android
49 | .idea
50 | webpack-assets.json
51 | *report*
52 |
53 | # Files #
54 | #########
55 | yarn.lock
56 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.yml
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2016 React Native Material Design
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Motion
2 | Change your application from the left one to the right one! Animate it! **Easily!** [Animated Transition Article](http://bit.ly/animated-transition) or [Animated Graph Article](http://bit.ly/animated-graph)
3 |
4 | 
5 |
6 | ### Getting Started
7 | ```bash
8 | $ yarn add react-native-motion
9 | ```
10 |
11 | ### Usage of SharedElement
12 | We need to specify source and destination for shared element. This library then will move the shared element from source position to destination position.
13 |
14 | ```js
15 | class Main extends Component {
16 | render() {
17 | return (
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 | ```
25 | ```js
26 | // List items page with source of SharedElement
27 | import { SharedElement } from 'react-native-motion';
28 |
29 | class ListPage extends Component {
30 | render() {
31 | return (
32 |
33 | {listItemNode}
34 |
35 | );
36 | }
37 | }
38 | ```
39 | ```js
40 | // Detail page with a destination shared element
41 | import { SharedElement } from 'react-native-motion';
42 |
43 | class DetailPage extends Component {
44 | render() {
45 | return (
46 |
47 | {listItemNode}
48 |
49 | );
50 | }
51 | }
52 | ```
53 | ### Playground for **react-native-motion** library
54 |
55 | - [react-native-motion-playground](https://github.com/xotahal/react-native-motion-playground) repository
56 | - [expo app](https://expo.io/@xotahal/react-native-motion-example)
57 |
58 | ## Author
59 | - [Let me help you](http://link.xotahal.cz/research) with animations in React Native 🤙
60 | - [Facebook Group](https://www.facebook.com/groups/react.native.motion/) about animation in React Native
61 | - [Publication](https://medium.com/react-native-motion) about animation in React Native 🚗
62 | - Personal [Medium Account](https://medium.com/@xotahal) about programming 😍
63 | - Subscribe a [blog](https://blog.xotahal.cz) 📝
64 | - Follow me on [Twitter](http://bit.ly/t-xotahal) 🐦
65 |
66 |
67 | | Jiri Otahal |
68 | | -------------------------------------------------------------------------------------------------------------------------------------- |
69 | | [
](http://bit.ly/t-xotahal) |
70 |
71 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // Shared element
2 | export { default as SharedElement } from './src/SharedElement';
3 | export { default as SharedElementRenderer } from './src/SharedElementRenderer';
4 | // Attentions
5 | export { default as Shake } from './src/Shake';
6 | // Available Animations
7 | export { default as AnimatedNumber } from './src/AnimatedNumber';
8 | export { default as AnimatedText } from './src/AnimatedText';
9 | export { default as ScaleAndOpacity } from './src/ScaleAndOpacity';
10 | export { default as Scale } from './src/Scale';
11 | export { default as TranslateYAndOpacity } from './src/TranslateYAndOpacity';
12 | export { default as TranslateY } from './src/TranslateY';
13 | export { default as TranslateX } from './src/TranslateX';
14 | export { default as TranslateXY } from './src/TranslateXY';
15 | export { default as Opacity } from './src/Opacity';
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-motion",
3 | "version": "0.0.1",
4 | "description": "React Native Motion",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "test:watch": "jest --watch",
9 | "coverage": "rimraf coverage && jest --coverage",
10 | "lint": "eslint ./"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/xotahal/react-native-motion"
15 | },
16 | "jest": {
17 | "preset": "react-native",
18 | "coverageDirectory": "./coverage/",
19 | "collectCoverage": true,
20 | "testMatch": [
21 | "/__tests__/**/*.test.js?(x)",
22 | "/src/**/*.test.js"
23 | ]
24 | },
25 | "keywords": [
26 | "react-native",
27 | "animation",
28 | "motion",
29 | "ui",
30 | "ux",
31 | "ios",
32 | "android"
33 | ],
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/xotahal/react-native-motion/issues"
37 | },
38 | "homepage": "https://github.com/xotahal/react-native-motion",
39 | "peerDependencies": {
40 | "react": "*",
41 | "react-native": "*"
42 | },
43 | "dependencies": {
44 | "prop-types": "^15.5.10"
45 | },
46 | "devDependencies": {
47 | "babel-core": "^6.26.3",
48 | "babel-eslint": "10.0.1",
49 | "babel-polyfill": "^6.23.0",
50 | "babel-preset-es2015": "^6.24.1",
51 | "babel-preset-react-native": "4.0.1",
52 | "eslint": "5.8.0",
53 | "eslint-config-airbnb": "17.1.0",
54 | "eslint-config-prettier": "^3.1.0",
55 | "eslint-plugin-import": "^2.14.0",
56 | "eslint-plugin-jsx-a11y": "6.1.2",
57 | "eslint-plugin-prettier": "^3.0.0",
58 | "eslint-plugin-react": "^7.11.1",
59 | "eslint-plugin-react-native": "^3.5.0",
60 | "prettier": "^1.14.3",
61 | "react": "16.4.1",
62 | "react-native": "^0.57.4",
63 | "semantic-release": "^15.13.3"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branch: 'master',
3 | };
4 |
--------------------------------------------------------------------------------
/src/AnimatedNumber.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | value: PropTypes.number,
8 | initialValue: PropTypes.number,
9 | animateOnDidMount: PropTypes.bool,
10 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
11 | };
12 | const defaultProps = {
13 | type: 'timing',
14 | value: 0,
15 | initialValue: null,
16 | animateOnDidMount: false,
17 | useNativeDriver: false,
18 | };
19 | /**
20 | */
21 | class AnimatedNumber extends React.PureComponent {
22 | constructor(props) {
23 | super(props);
24 | const { initialValue } = props;
25 | const firstValue = initialValue !== null ? initialValue : props.value;
26 | const animatedValue = new Animated.Value(firstValue);
27 | animatedValue.addListener(this.onValueChanged);
28 |
29 | this.state = {
30 | animatedValue,
31 | value: firstValue,
32 | };
33 | }
34 | componentDidMount() {
35 | const { animateOnDidMount } = this.props;
36 | if (animateOnDidMount) {
37 | InteractionManager.runAfterInteractions().then(() => {
38 | this.move(this.props);
39 | });
40 | }
41 | }
42 | componentDidUpdate(prevProps) {
43 | const { value } = this.props;
44 |
45 | if (prevProps.value !== value) {
46 | this.move(this.props);
47 | }
48 | }
49 | onValueChanged = e => {
50 | this.setState({
51 | value: e.value,
52 | });
53 | };
54 | move = props => {
55 | const { value, style, type, ...rest } = props;
56 | const { animatedValue } = this.state;
57 |
58 | Animated[type](animatedValue, {
59 | toValue: value,
60 | ...rest,
61 | }).start();
62 | };
63 | render() {
64 | const { renderValue } = this.props;
65 | const { value } = this.state;
66 |
67 | return renderValue(value);
68 | }
69 | }
70 |
71 | AnimatedNumber.propTypes = propTypes;
72 | AnimatedNumber.defaultProps = defaultProps;
73 |
74 | export default AnimatedNumber;
75 |
--------------------------------------------------------------------------------
/src/AnimatedText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Animated } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
8 | };
9 | const defaultProps = {
10 | type: 'timing',
11 | duration: 500,
12 | };
13 | /**
14 | */
15 | class AnimatedText extends React.PureComponent {
16 | constructor(props) {
17 | super(props);
18 |
19 | const animatedValue = new Animated.Value(1);
20 | animatedValue.addListener(this.onValueChanged);
21 |
22 | const { value } = props;
23 |
24 | this.state = {
25 | animatedValue,
26 | value,
27 | originValue: value,
28 | originLength: value.length,
29 | };
30 | }
31 | componentDidUpdate(prevProps) {
32 | const { value } = this.props;
33 |
34 | if (prevProps.value !== value) {
35 | this.change(this.props);
36 | }
37 | }
38 | onValueChanged = e => {
39 | const { originValue, originLength } = this.state;
40 |
41 | const sub = Math.ceil(originLength * e.value);
42 |
43 | this.setState({
44 | value: originValue.substr(0, Math.max(sub, 1)),
45 | });
46 | };
47 | change = props => {
48 | const { value, style, type, ...rest } = props;
49 | const { animatedValue } = this.state;
50 |
51 | Animated[type](animatedValue, {
52 | toValue: 0,
53 | ...rest,
54 | }).start(() => {
55 | this.setState({
56 | originValue: value,
57 | originLength: value.length,
58 | });
59 |
60 | Animated[type](animatedValue, {
61 | toValue: 1,
62 | ...rest,
63 | }).start();
64 | });
65 | };
66 | render() {
67 | const { renderValue } = this.props;
68 | const { value } = this.state;
69 |
70 | return renderValue(value);
71 | }
72 | }
73 |
74 | AnimatedText.propTypes = propTypes;
75 | AnimatedText.defaultProps = defaultProps;
76 |
77 | export default AnimatedText;
78 |
--------------------------------------------------------------------------------
/src/Opacity.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string,
7 | };
8 | const defaultProps = {
9 | type: 'timing',
10 | };
11 |
12 | class Opacity extends PureComponent {
13 | constructor(props) {
14 | super(props);
15 |
16 | const { value } = props;
17 |
18 | this.state = {
19 | opacityValue: new Animated.Value(value),
20 | };
21 | }
22 | componentDidUpdate(prevProps) {
23 | const { value } = this.props;
24 |
25 | if (prevProps.value !== value) {
26 | this.move(this.props);
27 | }
28 | }
29 | move = props => {
30 | const { value, style, type, ...rest } = props;
31 | const { opacityValue } = this.state;
32 |
33 | Animated[type](opacityValue, {
34 | toValue: value,
35 | ...rest,
36 | }).start();
37 | };
38 | render() {
39 | const { style, children, ...rest } = this.props;
40 | const { opacityValue } = this.state;
41 |
42 | const animatedStyle = {
43 | opacity: opacityValue,
44 | };
45 |
46 | return (
47 |
48 | {children}
49 |
50 | );
51 | }
52 | }
53 |
54 | Opacity.propTypes = propTypes;
55 | Opacity.defaultProps = defaultProps;
56 |
57 | export default Opacity;
58 |
--------------------------------------------------------------------------------
/src/Scale.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | animateOnDidMount: PropTypes.bool,
8 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
9 | };
10 | const defaultProps = {
11 | type: 'timing',
12 | animateOnDidMount: false,
13 | useNativeDriver: true,
14 | };
15 |
16 | class Scale extends PureComponent {
17 | constructor(props) {
18 | super(props);
19 |
20 | const { value, initValue } = props;
21 |
22 | this.interaction = null;
23 |
24 | this.state = {
25 | scaleValue: new Animated.Value(initValue || value),
26 | };
27 | }
28 | componentDidMount() {
29 | const { animateOnDidMount } = this.props;
30 |
31 | if (animateOnDidMount) {
32 | InteractionManager.runAfterInteractions().then(() => {
33 | this.move(this.props);
34 | });
35 | }
36 | }
37 | componentDidUpdate(prevProps) {
38 | const { value, runAfterInteractions } = this.props;
39 |
40 | if (prevProps.value !== value) {
41 | if (this.interaction) {
42 | this.interaction.cancel();
43 | }
44 |
45 | if (runAfterInteractions) {
46 | this.interaction = InteractionManager.runAfterInteractions(() => {
47 | this.interaction = null;
48 | this.move(this.props);
49 | });
50 | } else {
51 | this.move(this.props);
52 | }
53 | }
54 | }
55 | move = props => {
56 | const { value, type, onShowComplete, ...rest } = props;
57 | const { scaleValue } = this.state;
58 |
59 | Animated[type](scaleValue, {
60 | toValue: value,
61 | ...rest,
62 | }).start(() => {
63 | if (onShowComplete) {
64 | onShowComplete(props);
65 | }
66 | });
67 | };
68 | render() {
69 | const { style, children } = this.props;
70 | const { scaleValue } = this.state;
71 |
72 | const animatedStyle = {
73 | transform: [{ scale: scaleValue }],
74 | };
75 |
76 | return (
77 | {children}
78 | );
79 | }
80 | }
81 |
82 | Scale.propTypes = propTypes;
83 | Scale.defaultProps = defaultProps;
84 |
85 | export default Scale;
86 |
--------------------------------------------------------------------------------
/src/ScaleAndOpacity.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | opacityMin: PropTypes.number,
8 | scaleMin: PropTypes.number,
9 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
10 | animateOnDidMount: PropTypes.bool,
11 | isHidden: PropTypes.bool,
12 | delay: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
13 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
14 | };
15 | const defaultProps = {
16 | type: 'timing',
17 | opacityMin: 0,
18 | scaleMin: 0.8,
19 | duration: 500,
20 | animateOnDidMount: false,
21 | isHidden: true,
22 | delay: 0,
23 | useNativeDriver: false,
24 | };
25 |
26 | class ScaleAndOpacity extends PureComponent {
27 | constructor(props) {
28 | super(props);
29 |
30 | const { opacityMin, scaleMin, isHidden } = props;
31 |
32 | this.state = {
33 | opacityValue: new Animated.Value(isHidden ? opacityMin : 1),
34 | scaleValue: new Animated.Value(isHidden ? scaleMin : 1),
35 | };
36 | }
37 | componentDidMount() {
38 | const { animateOnDidMount } = this.props;
39 |
40 | if (animateOnDidMount) {
41 | InteractionManager.runAfterInteractions().then(() => {
42 | this.show(this.props);
43 | });
44 | }
45 | }
46 | componentDidUpdate(prevProps) {
47 | const { isHidden } = this.props;
48 |
49 | if (!prevProps.isHidden && isHidden) {
50 | this.hide(this.props);
51 | }
52 | if (prevProps.isHidden && !isHidden) {
53 | this.show(this.props);
54 | }
55 | }
56 | hide = props => {
57 | const { scaleValue, opacityValue } = this.state;
58 | const { type, scaleMin, opacityMin, onHideComplete, ...rest } = props;
59 |
60 | Animated.parallel([
61 | Animated[type](scaleValue, {
62 | toValue: scaleMin,
63 | ...rest,
64 | }),
65 | Animated[type](opacityValue, {
66 | toValue: opacityMin,
67 | ...rest,
68 | }),
69 | ]).start(() => {
70 | if (onHideComplete) {
71 | onHideComplete(props);
72 | }
73 | });
74 | };
75 | show = props => {
76 | const { scaleValue, opacityValue } = this.state;
77 | const { type, onShowComplete, ...rest } = props;
78 |
79 | Animated.parallel([
80 | Animated[type](scaleValue, {
81 | toValue: 1,
82 | ...rest,
83 | }),
84 | Animated[type](opacityValue, {
85 | toValue: 1,
86 | ...rest,
87 | }),
88 | ]).start(() => {
89 | if (onShowComplete) {
90 | onShowComplete(props);
91 | }
92 | });
93 | };
94 | render() {
95 | const { style, children } = this.props;
96 | const { opacityValue, scaleValue } = this.state;
97 |
98 | const animatedStyle = {
99 | opacity: opacityValue,
100 | transform: [
101 | {
102 | scale: scaleValue,
103 | },
104 | ],
105 | };
106 |
107 | return (
108 | {children}
109 | );
110 | }
111 | }
112 |
113 | ScaleAndOpacity.propTypes = propTypes;
114 | ScaleAndOpacity.defaultProps = defaultProps;
115 |
116 | export default ScaleAndOpacity;
117 |
--------------------------------------------------------------------------------
/src/Shake.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string,
7 | };
8 | const defaultProps = {
9 | type: 'timing',
10 | };
11 |
12 | class Shake extends PureComponent {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.currentValue = 0;
17 |
18 | this.state = {
19 | animatedValue: new Animated.Value(this.currentValue),
20 | };
21 | }
22 | componentDidUpdate(prevProps) {
23 | const { value } = this.props;
24 |
25 | // Perform the shake if our `value` prop has been changed and is
26 | // being changed to a truthy value.
27 | if (prevProps.value !== value && !!value) {
28 | this.move(this.props);
29 | }
30 | }
31 | move = props => {
32 | const { value, style, type, ...rest } = props;
33 | const { animatedValue } = this.state;
34 |
35 | Animated[type](animatedValue, {
36 | toValue: this.currentValue === 0 ? 1 : 0,
37 | ...rest,
38 | }).start(() => {
39 | this.currentValue = this.currentValue === 0 ? 1 : 0;
40 | });
41 | };
42 | render() {
43 | const { animatedValue } = this.state;
44 | const { style, children, ...rest } = this.props;
45 |
46 | const translateX = animatedValue.interpolate({
47 | inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1],
48 | outputRange: [0, -10, 10, -10, 10, 0],
49 | });
50 |
51 | const animatedStyle = {
52 | transform: [{ translateX }],
53 | };
54 |
55 | return (
56 |
57 | {children}
58 |
59 | );
60 | }
61 | }
62 |
63 | Shake.propTypes = propTypes;
64 | Shake.defaultProps = defaultProps;
65 |
66 | export default Shake;
67 |
--------------------------------------------------------------------------------
/src/SharedElement.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const propTypes = {
5 | // One of these are required
6 | id: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | sourceId: PropTypes.string,
8 | // Set to true if we want to start with animation immediately when a
9 | // destination element is mounted
10 | startOnDestinationDidMount: PropTypes.bool,
11 | // Set to true if we want to start with animation immediately when a
12 | // destination element is unmounted
13 | startOnDestinationWillUnmount: PropTypes.bool,
14 | };
15 | const defaultProps = {
16 | startOnDestinationDidMount: false,
17 | startOnDestinationWillUnmount: false,
18 | };
19 | const contextTypes = {
20 | moveSharedElement: PropTypes.func.isRequired,
21 | };
22 |
23 | const elements = {};
24 | // Test if the shred element is destination or source
25 | const isDestination = props => !!props.sourceId;
26 | // Destination element has id as a sourceId and source element has an id as an
27 | // id prop
28 | const getKey = props => props.id || props.sourceId;
29 | // Create a element with provided id
30 | const initElement = props => {
31 | const { id, sourceId } = props;
32 | const key = id || sourceId;
33 |
34 | elements[key] = elements[key] || {
35 | id: key,
36 | // source element of this shared element
37 | source: {
38 | // we want to keep ref to measure position on screen
39 | ref: null,
40 | // to be able fire events
41 | props: null,
42 | // last known position of source - when we lost ref (element was unmounted)
43 | // we are still able to show animation even when the element is already
44 | // doesn't exist, because we actualy will create new one for animation
45 | // anyway, if the source element was part of scrollview for example and
46 | // his position was changed since last measure, it will start in bad
47 | // position - we need to solve this somehow
48 | position: null,
49 | },
50 | // destination element of this shared element
51 | destination: {
52 | // it's the same like source, only for destination
53 | ref: null,
54 | props: null,
55 | position: null,
56 | },
57 | };
58 | };
59 | // To store props of source and destination. We want to fire event even for
60 | // source element if the destination was animated.
61 | const setProps = props => {
62 | const key = getKey(props);
63 |
64 | if (isDestination(props)) {
65 | elements[key].destination.props = props;
66 | } else {
67 | elements[key].source.props = props;
68 | }
69 | };
70 | // To store references of elements.
71 | const setRef = (props, node) => {
72 | const key = getKey(props);
73 |
74 | if (isDestination(props)) {
75 | elements[key].destination.ref = node;
76 | } else {
77 | elements[key].source.ref = node;
78 | }
79 | };
80 | // Node which will be animated. We are trying to observe element as a clone of
81 | // children. But if we want to do something with that children (for example
82 | // set an opacity once it is moved, it is better to provide getNode)
83 | const setNode = props => {
84 | const key = getKey(props);
85 |
86 | // if we already have a node
87 | if (elements[key].node) {
88 | return;
89 | }
90 |
91 | if (props.getNode) {
92 | elements[key].node = props.getNode(props);
93 | } else {
94 | // this node will be animated
95 | elements[key].node = React.cloneElement(props.children);
96 | }
97 | };
98 | const setSourcePosition = (props, position) => {
99 | if (!position) {
100 | return;
101 | }
102 |
103 | set(props, 'waitingForSource', false);
104 |
105 | const key = getKey(props);
106 | elements[key].source.position = position;
107 | };
108 | const setDestinationPosition = (props, position) => {
109 | if (!position) {
110 | return;
111 | }
112 |
113 | set(props, 'waitingForDestination', false);
114 |
115 | const key = getKey(props);
116 | elements[key].destination.position = position;
117 | };
118 | const set = (props, propName, value) => {
119 | const key = getKey(props);
120 | elements[key][propName] = value;
121 | };
122 | const get = (props, propName) => {
123 | const key = getKey(props);
124 | return elements[key][propName];
125 | };
126 | const getElement = props => {
127 | const key = getKey(props);
128 | return elements[key];
129 | };
130 | const getAnimationConfig = props => {
131 | // get only animation config
132 | const {
133 | id,
134 | sourceId,
135 | children,
136 | onMoveToSourceWillStart,
137 | onMoveToSourceDidFinish,
138 | onMoveToDestinationWillStart,
139 | onMoveToDestinationDidFinish,
140 | startOnDestinationDidMount,
141 | startOnDestinationWillUnmount,
142 | // should contains only animation config
143 | ...animationConfig
144 | } = props;
145 |
146 | return animationConfig;
147 | };
148 | const getAnimationConfigOfSource = props => {
149 | const element = getElement(props);
150 | return getAnimationConfig(element.source.props);
151 | };
152 | const getAnimationConfigOfDestination = props => {
153 | const element = getElement(props);
154 | return getAnimationConfig(element.destination.props);
155 | };
156 | const fireEvent = (props, name) => {
157 | if (props[name]) {
158 | props[name]();
159 | }
160 | };
161 |
162 | class SharedElement extends PureComponent {
163 | constructor(props) {
164 | super(props);
165 | // just create an object for this shared element
166 | initElement(props);
167 | }
168 | componentDidMount() {
169 | setProps(this.props);
170 | }
171 | componentDidUpdate() {
172 | setProps(this.props);
173 | }
174 | componentWillUnmount() {
175 | const { startOnDestinationWillUnmount } = this.props;
176 |
177 | if (startOnDestinationWillUnmount && isDestination(this.props)) {
178 | this.moveToSource();
179 | }
180 | }
181 | setRef = node => {
182 | setRef(this.props, node);
183 |
184 | const { children } = this.props;
185 | // Call the original ref, if there is any
186 | const { ref } = children;
187 | if (typeof ref === 'function') {
188 | ref(node);
189 | }
190 | };
191 | measure = (ref, callback) => {
192 | if (!ref) {
193 | callback(null);
194 | }
195 |
196 | ref.measure((x, y, width, height, pageX, pageY) => {
197 | const position = { x, y, width, height, pageX, pageY };
198 | callback(position);
199 | });
200 | };
201 | moveToDestination = () => {
202 | const { moveSharedElement } = this.context;
203 |
204 | setNode(this.props);
205 | const element = getElement(this.props);
206 |
207 | if (!element.destination.position) {
208 | set(this.props, 'waitingForDestination', true);
209 | } else {
210 | moveSharedElement({
211 | animationConfig: getAnimationConfigOfSource(this.props),
212 | element: getElement(this.props),
213 | onMoveWillStart: this.onMoveToDestinationWillStart,
214 | onMoveDidComplete: this.onMoveToDestinationDidFinish,
215 | });
216 | }
217 | };
218 | moveToSource = () => {
219 | const { moveSharedElement } = this.context;
220 |
221 | setNode(this.props);
222 | const element = getElement(this.props);
223 |
224 | if (!element.source.position) {
225 | set(this.props, 'waitingForSource', true);
226 | } else {
227 | moveSharedElement({
228 | animationConfig: getAnimationConfigOfDestination(this.props),
229 | element: {
230 | ...element,
231 | destination: element.source,
232 | source: element.destination,
233 | },
234 | onMoveWillStart: this.onMoveToSourceWillStart,
235 | onMoveDidComplete: this.onMoveToSourceDidFinish,
236 | });
237 | }
238 | };
239 | onMoveToDestinationWillStart = () => {
240 | const { source, destination } = getElement(this.props);
241 |
242 | fireEvent(source.props, 'onMoveToDestinationWillStart');
243 | fireEvent(destination.props, 'onMoveToDestinationWillStart');
244 | };
245 | onMoveToDestinationDidFinish = () => {
246 | const { source, destination } = getElement(this.props);
247 |
248 | // will get the node again later when we need it - we need always current
249 | // node beucase it could be changed during
250 | set(this.props, 'node', null);
251 |
252 | fireEvent(source.props, 'onMoveToDestinationDidFinish');
253 | fireEvent(destination.props, 'onMoveToDestinationDidFinish');
254 | };
255 | onMoveToSourceWillStart = () => {
256 | const { source, destination } = getElement(this.props);
257 |
258 | fireEvent(destination.props, 'onMoveToSourceWillStart');
259 | fireEvent(source.props, 'onMoveToSourceWillStart');
260 | };
261 | onMoveToSourceDidFinish = () => {
262 | const { source, destination } = getElement(this.props);
263 |
264 | // will get the node again later when we need it - we need always current
265 | // node beucase it could be changed during
266 | set(this.props, 'node', null);
267 |
268 | fireEvent(destination.props, 'onMoveToSourceDidFinish');
269 | fireEvent(source.props, 'onMoveToSourceDidFinish');
270 | };
271 | onSourceLayout = data => {
272 | const element = getElement(this.props);
273 |
274 | const { ref } = element.source;
275 | this.measure(ref, position => {
276 | const startAnimation = get(this.props, 'waitingForSource');
277 | setSourcePosition(this.props, position);
278 |
279 | // if the user wanted to move to destination but there wasn't source yet
280 | if (startAnimation) {
281 | this.moveToSource();
282 | }
283 | });
284 |
285 | // Call original if any
286 | const { children } = this.props;
287 | const { onLayout } = children;
288 | if (typeof onLayout === 'function') {
289 | onLayout(data);
290 | }
291 | };
292 | onDestinationLayout = data => {
293 | const { startOnDestinationDidMount } = this.props;
294 | const { source, destination } = getElement(this.props);
295 |
296 | this.measure(source.ref, sourcePosition => {
297 | setSourcePosition(this.props, sourcePosition);
298 |
299 | this.measure(destination.ref, destinationPosition => {
300 | const startAnimation = get(this.props, 'waitingForDestination');
301 | setDestinationPosition(this.props, destinationPosition);
302 |
303 | if (startAnimation || startOnDestinationDidMount) {
304 | this.moveToDestination();
305 | }
306 | });
307 | });
308 |
309 | // Call original if any
310 | const { children } = this.props;
311 | const { onLayout } = children;
312 | if (typeof onLayout === 'function') {
313 | onLayout(data);
314 | }
315 | };
316 | renderSource() {
317 | const { children } = this.props;
318 |
319 | return React.cloneElement(children, {
320 | ref: this.setRef,
321 | onLayout: this.onSourceLayout,
322 | });
323 | }
324 | renderDestination() {
325 | const { children } = this.props;
326 |
327 | return React.cloneElement(children, {
328 | ref: this.setRef,
329 | onLayout: this.onDestinationLayout,
330 | });
331 | }
332 | render() {
333 | const { sourceId } = this.props;
334 |
335 | if (!sourceId) {
336 | return this.renderSource();
337 | }
338 |
339 | return this.renderDestination();
340 | }
341 | }
342 |
343 | SharedElement.propTypes = propTypes;
344 | SharedElement.defaultProps = defaultProps;
345 | SharedElement.contextTypes = contextTypes;
346 |
347 | export default SharedElement;
348 |
--------------------------------------------------------------------------------
/src/SharedElementRenderer.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, View, StyleSheet } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const childContextTypes = {
6 | moveSharedElement: PropTypes.func.isRequired,
7 | };
8 |
9 | // We need shared element to be rendered after the whole application because it
10 | // be on the screen with position absolute and will cover everything on screen
11 | class SharedElementRenderer extends PureComponent {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.isRunning = {};
16 | this.state = {
17 | config: null,
18 | };
19 | }
20 | getChildContext() {
21 | return {
22 | moveSharedElement: this.moveSharedElement,
23 | };
24 | }
25 | onMoveWillStart = () => {
26 | const { config } = this.state;
27 | const { onMoveWillStart, element } = config;
28 | const { id } = element;
29 |
30 | this.isRunning[id] = true;
31 |
32 | if (onMoveWillStart) {
33 | onMoveWillStart(config);
34 | }
35 | };
36 | onMoveDidComplete = () => {
37 | const { config } = this.state;
38 | const { onMoveDidComplete, element } = config;
39 | const { id } = element;
40 |
41 | this.isRunning[id] = false;
42 |
43 | if (onMoveDidComplete) {
44 | onMoveDidComplete(config);
45 | }
46 |
47 | this.reset();
48 | };
49 | reset = () => {
50 | this.setState({ config: null });
51 | };
52 | // This method will compute animations. Position and scale.
53 | getAnimations = config => {
54 | const { element, animationConfig } = config;
55 | const { source, destination } = element;
56 |
57 | const animations = [];
58 | let translateYValue = 0;
59 |
60 | if (!Number.isNaN(source.position.pageY)) {
61 | translateYValue = new Animated.Value(source.position.pageY);
62 | }
63 |
64 | if (source.position.pageY !== destination.position.pageY) {
65 | this.setState({ translateYValue });
66 |
67 | animations.push(
68 | Animated.timing(translateYValue, {
69 | toValue: destination.position.pageY,
70 | useNativeDriver: true,
71 | ...animationConfig,
72 | }),
73 | );
74 | }
75 |
76 | return animations;
77 | };
78 | moveSharedElement = config => {
79 | const { id } = config.element;
80 | // animation was already started
81 | if (this.isRunning[id]) {
82 | return;
83 | }
84 |
85 | const animations = this.getAnimations(config);
86 |
87 | this.setState({
88 | config,
89 | });
90 |
91 | setTimeout(() => {
92 | this.onMoveWillStart();
93 | Animated.parallel(animations).start(this.onMoveDidComplete);
94 | }, 0);
95 | };
96 | renderSharedElement() {
97 | const { config, translateYValue } = this.state;
98 | const { element } = config || {};
99 | const { source, node } = element || {};
100 | const { position } = source || {};
101 | const { height, width } = position || {};
102 |
103 | if (!config) {
104 | return null;
105 | }
106 |
107 | const transform = [];
108 |
109 | if (translateYValue) {
110 | transform.push({ translateY: translateYValue });
111 | }
112 |
113 | const animatedStyle = {
114 | height,
115 | width,
116 | transform,
117 | };
118 |
119 | return (
120 |
121 |
122 | {node}
123 |
124 |
125 | );
126 | }
127 | render() {
128 | const { children } = this.props;
129 |
130 | return (
131 |
132 | {children}
133 | {this.renderSharedElement()}
134 |
135 | );
136 | }
137 | }
138 |
139 | const styles = StyleSheet.create({
140 | flexContainer: {
141 | flex: 1,
142 | },
143 | container: {
144 | ...StyleSheet.absoluteFillObject,
145 | backgroundColor: 'transparent',
146 | },
147 | positionContainer: {
148 | position: 'absolute',
149 | },
150 | });
151 |
152 | SharedElementRenderer.childContextTypes = childContextTypes;
153 |
154 | export default SharedElementRenderer;
155 |
--------------------------------------------------------------------------------
/src/TranslateX.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string,
7 | value: PropTypes.number,
8 | initialValue: PropTypes.number,
9 | animateOnDidMount: PropTypes.bool,
10 | useNativeDriver: PropTypes.bool,
11 | };
12 | const defaultProps = {
13 | type: 'timing',
14 | value: 0,
15 | initialValue: 0,
16 | animateOnDidMount: false,
17 | useNativeDriver: true,
18 | };
19 |
20 | class TranslateX extends PureComponent {
21 | constructor(props) {
22 | super(props);
23 |
24 | const { value, initialValue } = props;
25 |
26 | this.state = {
27 | translateXValue: new Animated.Value(initialValue || value),
28 | };
29 | }
30 | componentDidMount() {
31 | const { animateOnDidMount, value } = this.props;
32 | if (animateOnDidMount) {
33 | InteractionManager.runAfterInteractions().then(() => {
34 | this.move(value);
35 | });
36 | }
37 | }
38 | componentDidUpdate(prevProps) {
39 | const { value } = this.props;
40 |
41 | if (prevProps.value !== value) {
42 | this.move(value);
43 | }
44 | }
45 | move = toValue => {
46 | const { style, onMoveDidFinish, type, ...rest } = this.props;
47 | const { translateXValue } = this.state;
48 |
49 | Animated[type](translateXValue, {
50 | toValue,
51 | ...rest,
52 | }).start(onMoveDidFinish);
53 | };
54 | render() {
55 | const { style, children } = this.props;
56 | const { translateXValue } = this.state;
57 |
58 | const animatedStyle = {
59 | transform: [{ translateX: translateXValue }],
60 | };
61 |
62 | return (
63 | {children}
64 | );
65 | }
66 | }
67 |
68 | TranslateX.propTypes = propTypes;
69 | TranslateX.defaultProps = defaultProps;
70 |
71 | export default TranslateX;
72 |
--------------------------------------------------------------------------------
/src/TranslateXY.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
7 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
8 | x: PropTypes.number,
9 | y: PropTypes.number,
10 | };
11 | const defaultProps = {
12 | type: 'timing',
13 | useNativeDriver: true,
14 | x: 0,
15 | y: 0,
16 | };
17 |
18 | class TranslateXY extends PureComponent {
19 | constructor(props) {
20 | super(props);
21 |
22 | const { x, y } = props;
23 |
24 | this.state = {
25 | translateValue: new Animated.ValueXY({ x, y }),
26 | };
27 | }
28 | componentDidUpdate(prevProps) {
29 | const { x, y } = this.props;
30 |
31 | if (prevProps.x !== x || prevProps.y !== y) {
32 | this.move(this.props);
33 | }
34 | }
35 | move = props => {
36 | const { translateValue } = this.state;
37 | const { style, type, x, y, ...rest } = props;
38 |
39 | Animated[type](translateValue, {
40 | toValue: { x, y },
41 | ...rest,
42 | }).start();
43 | };
44 | render() {
45 | const { translateValue } = this.state;
46 | const { style, children } = this.props;
47 |
48 | const animatedStyle = {
49 | transform: translateValue.getTranslateTransform(),
50 | };
51 |
52 | return (
53 | {children}
54 | );
55 | }
56 | }
57 |
58 | TranslateXY.propTypes = propTypes;
59 | TranslateXY.defaultProps = defaultProps;
60 |
61 | export default TranslateXY;
62 |
--------------------------------------------------------------------------------
/src/TranslateY.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | type: PropTypes.string,
7 | value: PropTypes.number,
8 | initialValue: PropTypes.number,
9 | startOnDidMount: PropTypes.bool,
10 | useNativeDriver: PropTypes.bool,
11 | };
12 | const defaultProps = {
13 | type: 'timing',
14 | value: 0,
15 | initialValue: null,
16 | startOnDidMount: false,
17 | useNativeDriver: true,
18 | };
19 |
20 | class TranslateY extends PureComponent {
21 | constructor(props) {
22 | super(props);
23 |
24 | const { value, initialValue } = props;
25 |
26 | this.state = {
27 | translateYValue: new Animated.Value(
28 | initialValue !== null ? initialValue : value,
29 | ),
30 | };
31 | }
32 | componentDidMount() {
33 | const { startOnDidMount, value } = this.props;
34 | if (startOnDidMount) {
35 | InteractionManager.runAfterInteractions().then(() => {
36 | this.move(value);
37 | });
38 | }
39 | }
40 | componentDidUpdate(prevProps) {
41 | const { value } = this.props;
42 |
43 | if (prevProps.value !== value) {
44 | this.move(value);
45 | }
46 | }
47 | move = toValue => {
48 | const { style, type, pointerEvents, ...rest } = this.props;
49 | const { translateYValue } = this.state;
50 |
51 | Animated[type](translateYValue, {
52 | toValue,
53 | ...rest,
54 | }).start();
55 | };
56 | render() {
57 | const { style, children, pointerEvents } = this.props;
58 | const { translateYValue } = this.state;
59 |
60 | const animatedStyle = {
61 | transform: [{ translateY: translateYValue }],
62 | };
63 |
64 | return (
65 |
69 | {children}
70 |
71 | );
72 | }
73 | }
74 |
75 | TranslateY.propTypes = propTypes;
76 | TranslateY.defaultProps = defaultProps;
77 |
78 | export default TranslateY;
79 |
--------------------------------------------------------------------------------
/src/TranslateYAndOpacity.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Animated, InteractionManager } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | opacityMin: PropTypes.number,
7 | translateYMin: PropTypes.number,
8 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
9 | animateOnDidMount: PropTypes.bool,
10 | delay: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
11 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
12 | };
13 | const defaultProps = {
14 | opacityMin: 0,
15 | translateYMin: -4,
16 | duration: 500,
17 | animateOnDidMount: false,
18 | delay: 0,
19 | useNativeDriver: false,
20 | };
21 |
22 | class TranslateYAndOpacity extends PureComponent {
23 | constructor(props) {
24 | super(props);
25 |
26 | const { opacityMin, translateYMin } = props;
27 |
28 | this.state = {
29 | opacityValue: new Animated.Value(opacityMin),
30 | translateYValue: new Animated.Value(translateYMin),
31 | };
32 | }
33 | componentDidMount() {
34 | const { animateOnDidMount } = this.props;
35 |
36 | if (animateOnDidMount) {
37 | InteractionManager.runAfterInteractions().then(() => {
38 | this.show(this.props);
39 | });
40 | }
41 | }
42 | componentDidUpdate(prevProps) {
43 | const { isHidden } = this.props;
44 |
45 | if (!prevProps.isHidden && isHidden) {
46 | this.hide(this.props);
47 | }
48 | if (prevProps.isHidden && !isHidden) {
49 | this.show(this.props);
50 | }
51 | }
52 | show(props) {
53 | const { opacityValue, translateYValue } = this.state;
54 | const { onShowDidFinish, ...rest } = props;
55 |
56 | Animated.parallel([
57 | Animated.timing(opacityValue, {
58 | toValue: 1,
59 | ...rest,
60 | }),
61 | Animated.timing(translateYValue, {
62 | toValue: 0,
63 | ...rest,
64 | }),
65 | ]).start(() => {
66 | if (onShowDidFinish) {
67 | onShowDidFinish(props);
68 | }
69 | });
70 | }
71 | hide(props) {
72 | const { translateYValue, opacityValue } = this.state;
73 | const { opacityMin, translateYMin, onHideDidFinish, ...rest } = props;
74 |
75 | Animated.parallel([
76 | Animated.timing(opacityValue, {
77 | toValue: opacityMin,
78 | ...rest,
79 | }),
80 | Animated.timing(translateYValue, {
81 | toValue: translateYMin,
82 | ...rest,
83 | }),
84 | ]).start(() => {
85 | if (onHideDidFinish) {
86 | onHideDidFinish(props);
87 | }
88 | });
89 | }
90 | render() {
91 | const { children } = this.props;
92 | const { opacityValue, translateYValue } = this.state;
93 |
94 | const animatedStyle = {
95 | opacity: opacityValue,
96 | transform: [{ translateY: translateYValue }],
97 | };
98 |
99 | return {children};
100 | }
101 | }
102 |
103 | TranslateYAndOpacity.propTypes = propTypes;
104 | TranslateYAndOpacity.defaultProps = defaultProps;
105 |
106 | export default TranslateYAndOpacity;
107 |
--------------------------------------------------------------------------------