akveo / react-native-ui-kitten

:boom: React Native UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode
https://akveo.github.io/react-native-ui-kitten/
MIT License
10.28k stars 951 forks source link

Configurable animations #837

Open mdebo opened 4 years ago

mdebo commented 4 years ago

Hi

I would like to have a way to configure animation using ui.kitten. For exemple when opening modal, select or menu etc. Is there a way to do it properly? (using the theme for exemple, to preserve consistancy accross all the app)?

Thanks!

artyorsh commented 4 years ago

No, we don't support configurable animations currently (even on the internal level), but it is on the plan for future releases. Let's keep this issue open to view the user interest and tracking a progress

mdebo commented 4 years ago

OK thanks - yes, i'll leave it open

lesmo commented 4 years ago

I think this should be out of scope for UI Kitten, there's some powerful and simple libraries like react-native-animatable that can be easily integrated with this library 🤔

artyorsh commented 4 years ago

@lesmo I guess there are some basic use-cases like animating modals that should be possible to customize, but currently there is no way to do that :)

annie-elequin commented 4 years ago

Yes! I'm very interested in having animated modals / popups / tooltips!!

annie-elequin commented 4 years ago

I'm going to look into the Animatable library though, I haven't seen that.

mdebo commented 4 years ago

@lesmo it's not about the difficulty to implement it or not - according to me the purpose of using a framework for your UI is to avoid this and to preserve consistancy and homogeneity of UI and UX So i keep thinking it's fully in scope - I'll even say more: it's part of user experience. For exemple, angular material implementation in angular use a mat-ripple directive that is used to both indicate the point of touch, and to confirm that touch input was received.

fr3fou commented 4 years ago

i'd love to animated the TabView component as the easing / animation is too slow in my opinion

m4ttheweric commented 4 years ago

In the meantime, anyone have any examples of an animated Modal they could share? Thanks!

CostachescuCristinel commented 4 years ago

For modals, wrap your content in an Animated.View as first child. To start/stop animations on mount/unmount, you may create a wrapper class component where you can start and stop animations.

class CustomAnimatedModal extends React.Component {
    constructor(props){
        super(props);

        // should start with invisible content
        // Until componentDidMount is triggered, no content will be visible due to opacity=0
        // when componentDidMount is triggered, content will fade in
        this.opacityAnimation = new Animated.Value(0.0);
    }

    componentDidMount = ()=>{
        Animated.timing(this.opacityAnimation, { toValue: 1.0, duration: 1000, useNativeDriver: true }).start();
    }

    componentWillUnmount = ()=>{
        Animated.timing(this.opacityAnimation, { toValue: 0.0, duration: 1000, useNativeDriver: true }).start();
    }

    render = ()=>{
        return <Modal visible={this.state.visible}>
            <Animated.View style={{ width: "100%", height: "100%", opacity: this.opacityAnimation  }}>
                {/* content */}
            </Animated.View>
       </Modal>;
}

For other components, you can usually wrap them in Animated.View that you can then animate as you want.

<Layout level="1" style={{ flex: 1 }}>
    <Animated.View style={{ opacity: this.opacityAnimation.interpolate({ inputRange: [0.0, 1.0], outputRange: [0.3, 0.8] }) }}>
        <Layout level="2">{/* note how no flex/width/height styling is specified here, this Layout will take the size of the content - Animated.View - mostly behaving as one (except for position properties) */}
            <Animated.View style={{ width: "100%", height: this.opacityAnimation.interpolate({ inputRange: [0.0, 1.0], outputRange: [50, 150] }) }}>
                <Text category="c2">example text</Text>
            </Animated.View>
        </Layout>
    </Animated.View>
</Layout>

Hopefully you'll find this useful :)

sschottler commented 4 years ago

Here's one way to animate a button using composition pattern:

const AnimatedButton = ({ style, onPressIn, onPressOut, ...props }) => {
  const animation = useRef(new Animated.Value(1)).current;

  const handlePressIn = e => {
    Animated.spring(animation, {
      toValue: 0.5,
      useNativeDriver: true,
    }).start();
    onPressIn && onPressIn(e);
  };

  const handlePressOut = e => {
    Animated.spring(animation, {
      toValue: 1,
      friction: 3,
      tension: 40,
      useNativeDriver: true,
    }).start();
    onPressOut && onPressOut(e);
  };

  const animatedStyle = { transform: [{ scale: animation }] };

  return (
    <Button
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      {...props}
      style={[style, animatedStyle]}
    />
  );
};
sschottler commented 4 years ago

If you wanted the Material ripple effect, you could use the react-native-material-ripple package and plugin to the ui-kitten theme (and mapping if desired with the styled hoc). Something like:

const AnimatedButton = ({children, ...props}) => {
  const theme = useTheme();
  return (
    <Ripple 
      rippleColor="white"
      rippleOpacity={1}
      style={{backgroundColor: theme['color-primary-default'], height: 60}}
      {...props}>
      <Text>{children}</Text>
    </Ripple>
  );
};
sschottler commented 3 years ago

Follow up on my button example. If you want to animate styles like backgroundColor, you need to use Animated.createAnimatedComponent and set useNativeDriver to false:

import React, { FC } from 'react';
import { Animated } from 'react-native';
import { Button, ButtonProps } from '@ui-kitten/components';
import { useScaleAnimation } from './useScaleAnimation';

const AnimatableButton = Animated.createAnimatedComponent(Button);

export const AnimatedButton: FC<ButtonProps> = (props) => {
  const animatedProps = useScaleAnimation(props);
  return <AnimatableButton {...animatedProps} />;
};

  /* 
    If you need animation to be theme-able, you could add
    an "AnimatedButton" key to custom mapping with
    custom parameters that control animation type, then access
    those parameters from eva prop by wrapping this component with the
    styled HOC like this: styled('AnimatedButton')(AnimatedButton)
  */ 
import { useRef } from 'react';
import { Animated, Easing, GestureResponderEvent } from 'react-native';
import { ButtonProps, useTheme } from '@ui-kitten/components';

const BUTTON_PRESSED_SIZE = 0.95;
const BUTTON_EASING = Easing.bezier(0.25, 0.1, 0.25, 1);

export function useScaleAnimation({
  appearance = 'filled',
  status = 'primary',
  onPressIn,
  onPressOut,
  style,
  ...props
}: ButtonProps): ButtonProps {
  const scale = useRef(new Animated.Value(1)).current;
  const theme = useTheme();

  const shrink = () => {
    Animated.timing(scale, {
      toValue: BUTTON_PRESSED_SIZE,
      duration: 100,
      easing: BUTTON_EASING,
      useNativeDriver: false,
    }).start();
  };

  const grow = () => {
    Animated.timing(scale, {
      toValue: 1,
      duration: 300,
      easing: BUTTON_EASING,
      useNativeDriver: false,
    }).start();
  };

  const handlePressIn = (e: GestureResponderEvent) => {
    shrink();
    onPressIn && onPressIn(e);
  };

  const handlePressOut = (e: GestureResponderEvent) => {
    grow();
    onPressOut && onPressOut(e);
  };

  const animatedStyle: any = { transform: [{ scale }] };

  if (appearance === 'filled') {
    const defaultColor = theme[`color-${status}-default`];
    const activeColor = theme[`color-${status}-active`];

    const backgroundColor = scale.interpolate({
      inputRange: [BUTTON_PRESSED_SIZE, 1],
      outputRange: [activeColor, defaultColor],
    });

    animatedStyle.backgroundColor = backgroundColor;
  }

  return {
    status,
    appearance,
    ...props,
    onPressIn: handlePressIn,
    onPressOut: handlePressOut,
    style: [style, animatedStyle],
  };
}
greenafrican commented 2 years ago

Another use case for this is to be able to control the animation ease curve when switching between Tabs in a TabView. I'd like to have some control of the speed an arrival ease as they feel quite abrupt.