Skip to content

Commit

Permalink
feat: custom animations
Browse files Browse the repository at this point in the history
  • Loading branch information
seniv committed Apr 30, 2022
1 parent 7353e64 commit 581addd
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 27 deletions.
136 changes: 112 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![platforms: ios, android, web](https://img.shields.io/badge/platform-ios%2C%20android%2C%20web%2C%20expo-orange)
[![license MIT](https://img.shields.io/badge/license-MIT-brightgreen)](https://github.com/seniv/react-native-notifier/blob/master/LICENSE)

Fast and simple in-app notifications for React Native
Fast, simple, and customizable in-app notifications for React Native

![Demo of package](https://raw.githubusercontent.com/seniv/react-native-notifier/master/demo.gif)

Expand Down Expand Up @@ -86,29 +86,31 @@ Show notification with params.
`params`
Name | Type | Default | Description
----------------------|------------|-------------------------------|-------------
title | String | null | Title of notification. __Passed to `Component`.__
description | String | null | Description of notification. __Passed to `Component`.__
duration | Number | 3000 | Time after notification will disappear. Set to `0` to not hide notification automatically
Component | Component | NotifierComponents.Notification | Component of the notification body. You can use one of the [built-in components](#components), or your [custom component](#custom-component).
componentProps | Object | {} | Additional props that are passed to `Component`. See all available props of built-in components in the [components section](#components).
queueMode | String | 'reset' | Determines the order in which notifications are shown. Read more in the [Queue Mode](#queue-mode) section.
swipeEnabled | Boolean | true | Can notification be hidden by swiping it out
animationDuration | Number | 300 | How fast notification will appear/disappear
showAnimationDuration | Number | animationDuration \|\| 300 | How fast notification will appear.
hideAnimationDuration | Number | animationDuration \|\| 300 | How fast notification will disappear.
easing | Easing | null | Animation easing. Details: https://reactnative.dev/docs/easing
showEasing | Easing | easing \|\| null | Show Animation easing.
hideEasing | Easing | easing \|\| null | Hide Animation easing.
onStartHiding | () => void | null | Function called when notification started hiding
onHidden | () => void | null | Function called when notification completely hidden
onPress | () => void | null | Function called when user press on notification
hideOnPress | Boolean | true | Should notification hide when user press on it
swipePixelsToClose | Number | 20 | How many pixels user should swipe-up notification to dismiss it
swipeEasing | Easing | null | Animation easing after user finished swiping
swipeAnimationDuration| Number | 200 | How fast should be animation after user finished swiping
translucentStatusBar | Boolean | false | Add additional top padding that equals to `StatusBar.currentHeight`. Android Only.
Name | Type | Default | Description
----------------------|------------------|-------------------------------|-------------
title | String | null | Title of notification. __Passed to `Component`.__
description | String | null | Description of notification. __Passed to `Component`.__
duration | Number | 3000 | Time after notification will disappear. Set to `0` to not hide notification automatically
Component | Component | NotifierComponents.Notification | Component of the notification body. You can use one of the [built-in components](#components), or your [custom component](#custom-component).
componentProps | Object | {} | Additional props that are passed to `Component`. See all available props of built-in components in the [components section](#components).
containerStyle | Object\|Function | null | Styles Object or a Function that will receive `translateY: Animated.Value` as first parameter and should return Styles that will be used in container. Using this parameter it is possible to create [your own animations of the notification](#custom-animations).
containerProps | Object | {} | Props of Animated Container
queueMode | String | 'reset' | Determines the order in which notifications are shown. Read more in the [Queue Mode](#queue-mode) section.
swipeEnabled | Boolean | true | Can notification be hidden by swiping it out
animationDuration | Number | 300 | How fast notification will appear/disappear
showAnimationDuration | Number | animationDuration \|\| 300 | How fast notification will appear.
hideAnimationDuration | Number | animationDuration \|\| 300 | How fast notification will disappear.
easing | Easing | null | Animation easing. Details: https://reactnative.dev/docs/easing
showEasing | Easing | easing \|\| null | Show Animation easing.
hideEasing | Easing | easing \|\| null | Hide Animation easing.
onStartHiding | () => void | null | Function called when notification started hiding
onHidden | () => void | null | Function called when notification completely hidden
onPress | () => void | null | Function called when user press on notification
hideOnPress | Boolean | true | Should notification hide when user press on it
swipePixelsToClose | Number | 20 | How many pixels user should swipe-up notification to dismiss it
swipeEasing | Easing | null | Animation easing after user finished swiping
swipeAnimationDuration| Number | 200 | How fast should be animation after user finished swiping
translucentStatusBar | Boolean | false | Add additional top padding that equals to `StatusBar.currentHeight`. Android Only.
### `hideNotification`
Expand Down Expand Up @@ -256,6 +258,92 @@ Notifier.showNotification({
```
![Demo of custom component](https://raw.githubusercontent.com/seniv/react-native-notifier/master/custom-component.jpg)
## Custom Animations
![Demo of Custom Animations](https://raw.githubusercontent.com/seniv/react-native-notifier/master/custom-animations.gif)
It is easy to create your own animation using `containerStyle` param.
When you pass a function as `containerStyle` param, it will receive a `translateY` Animated Value as first parameter.
This Animated Value is a Driver of all Animations in this library and varies between `-1000`(notification completely hidden) and `0` (notification is shown). By default this value is used as a `Y` position of the Notification.
So when you call `showNotification` — this value starts changing from `-1000` to `0` and when the notification is starts hiding — the value is changing from `0` to `-"height of the component"+50` (or `-200`, depending what is bigger) and when animation is finished, the values will be set to `-1000` (just to make sure that the notification is completely hidden).
You need to remember three points of the animated value:
1. `-1000` - Notification completely hidden;
2. `-200` - Most likely notification is still hidden, but will be visible soon (depending on height of the notification);
3. `0` - Notification is shown.
I know, this all is complicated, so here is a Code Example with combination of scaling and transition:
```ts
const getContainerStyleWithTranslateAndScale = (translateY: Animated.Value) => ({
transform: [
{
// this interpolation is used just to "clamp" the value and didn't allow to drag the notification below "0"
translateY: translateY.interpolate({
inputRange: [-1000, 0],
outputRange: [-1000, 0],
extrapolate: 'clamp',
}),
// scaling from 0 to 0.5 when value is in range of -1000 and -200 because mostly it is still invisible,
// and from 0.5 to 1 in last 200 pixels to make the scaling effect more noticeable.
scale: translateY.interpolate({
inputRange: [-1000, -200, 0],
outputRange: [0, 0.5, 1],
extrapolate: 'clamp',
}),
},
],
});

Notifier.showNotification({
title: 'Custom animations',
description: 'This notification is moved and scaled',
containerStyle: getContainerStyleWithTranslateAndScale,
})
```
Code from example above should work great on both Android and iOS.
But if you will animate `opacity` style with component that have shadows (such as `NotifierComponents.Notification`)
you may notice that on Android shadows doesn't animate properly. To fix this problem, you need to use `containerProps` parameter and pass `needsOffscreenAlphaCompositing: true` to it. Details: [RN's Repository Issue](https://github.com/facebook/react-native/issues/23090#issuecomment-669157170)
```ts
const animatedContainerProps = isAndroid ? { needsOffscreenAlphaCompositing: true } : undefined;
// ...
Notifier.showNotification({
title: 'Custom animations',
description: 'This notification is moved and scaled',
containerStyle: getContainerStyleWithTranslateAndScale,
containerProps: animatedContainerProps,
})
```
Keep in mind that this library uses [React Native's Animated library](https://reactnative.dev/docs/animated) with [Native Driver](https://reactnative.dev/docs/animations#using-the-native-driver) turned on, and the current version of React Native has a limited list of style properties that can be animated. Here you can [view list of styles that can be animated](https://github.com/facebook/react-native/blob/main/Libraries/Animated/NativeAnimatedHelper.js#L234).
Also you can see the code of all [Animations from Example GIF](https://github.com/seniv/react-native-notifier/blob/master/example/src/customAnimations.ts). Feel free to copy those animations to your codebase and play with them.
### Troubleshooting
You might notice that some animations such as zoom in/out(using `scale` param) might work incorrectly on iOS and instead of just "scaling" component also moves up and down.
That is because of padding that was added by SafeAreaView.
This behavior can be fixed by moving safe area inset from component to container, like this:
```ts
Notifier.showNotification({
title: 'Zoom In/Out Animation',
containerStyle: (translateY: Animated.Value) => ({
// add safe area inset to the container
marginTop: safeTopMargin,
// ...
}),
// replace SafeAreaView with View
componentProps: {
ContainerComponent: View,
},
})
```
This behavior will be fixed in feature releases.
## Using with `react-native-navigation`
If you are using `react-native-navigation`, this issue might be helpful to use notifier with native-navigation: https://github.com/seniv/react-native-notifier/issues/16
Expand Down
Binary file added custom-animations.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,6 @@ SPEC CHECKSUMS:
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b

PODFILE CHECKSUM: fe2ec885e953ce5f44cebfbcc59482388d18a647
PODFILE CHECKSUM: cd0caa5f9d17bea209e56d50acc7d709273a8268

COCOAPODS: 1.10.1
97 changes: 96 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@ import { Easing, Notifier, NotifierRoot, NotifierComponents } from 'react-native
import Modal from 'react-native-modal';
import Button from './Button';
import CustomComponent from './CustomComponent';
import {
getContainerStyleBottomPosition,
getContainerStyleClassicWithOverSwipe,
getContainerStyleOpacityOnly,
getContainerStyleOpacityTransformScale,
getContainerStyleScaleAndRotation,
getContainerStyleScaleOnly,
getContainerStyleWithTranslateAndScale,
} from './customAnimations';
import Section from './Section';

const isAndroid = Platform.OS === 'android';

// "needsOffscreenAlphaCompositing" prop is needed to fix shadows on android when using "opacity" style in container
const animatedContainerProps = isAndroid ? { needsOffscreenAlphaCompositing: true } : undefined;

export default function App() {
const notifierRef = React.useRef<NotifierRoot>(null);
const [statusBar, setStatusBar] = React.useState(true);
Expand Down Expand Up @@ -140,6 +153,88 @@ export default function App() {
})
}
/>
<Section title="Custom Animations">
<Button
title="Opacity, TranslateY and Scale animation"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Opacity, TranslateY and Scale animation',
description:
'This notification uses Opacity and Transformation of Scale and TranslateY',
containerStyle: getContainerStyleOpacityTransformScale,
containerProps: animatedContainerProps,
queueMode: 'standby',
})
}
/>
<Button
title="Pull down"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Pull down',
description: 'Notification can be slightly pulled down',
containerStyle: getContainerStyleClassicWithOverSwipe,
queueMode: 'standby',
})
}
/>
<Button
title="Fade In/Out Notification"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Fade In/Out Notification',
description: 'This notification is faded in using Animated opacity style',
containerStyle: getContainerStyleOpacityOnly,
containerProps: animatedContainerProps,
queueMode: 'standby',
})
}
/>
<Button
title="Zoom In/Out Animation"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Zoom In/Out Animation',
description: 'Uses only Scale Transformation to zoom in',
containerStyle: getContainerStyleScaleOnly,
queueMode: 'standby',
})
}
/>
<Button
title="Zoom + Rotation"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Zoom + Rotation',
description: 'Scale and Rotate the notification. This is a MADNESS!',
containerStyle: getContainerStyleScaleAndRotation,
queueMode: 'standby',
})
}
/>
<Button
title="Animation from code example"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Animation from code example',
description: 'Scale and Translate',
containerStyle: getContainerStyleWithTranslateAndScale,
})
}
/>
<Button
title="Bottom Position"
onPress={() =>
notifierRef.current?.showNotification({
title: 'Bottom Position',
description: 'Moved to the bottom using containerStyle property',
containerStyle: getContainerStyleBottomPosition,
// Disable swipes because currently bottom position is not fully supported
swipeEnabled: false,
})
}
/>
</Section>
<Button title="Hide" onPress={() => Notifier.hideNotification()} />
<Button title="Open react-native-modal" onPress={() => setModalVisible(true)} />
{isAndroid && (
Expand Down Expand Up @@ -183,7 +278,7 @@ const styles = StyleSheet.create({
flex: 1,
},
contentContainer: {
paddingVertical: 100,
paddingVertical: 50,
},
modalContainer: {
flex: 1,
Expand Down
34 changes: 34 additions & 0 deletions example/src/Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

const styles = StyleSheet.create({
line: {
marginHorizontal: 10,
flex: 1,
height: 1,
backgroundColor: 'silver',
},
bottomLine: {
marginTop: 10,
},
title: {
textAlign: 'center',
marginTop: 10,
color: '#2c2c2c',
},
});

interface SectionProps {
title: string;
}

const Section: React.FC<SectionProps> = ({ children, title }) => (
<>
<Text style={styles.title}>{title}</Text>
<View style={styles.line} />
{children}
<View style={[styles.line, styles.bottomLine]} />
</>
);

export default Section;

0 comments on commit 581addd

Please sign in to comment.