Skip to content

Commit

Permalink
Style transition
Browse files Browse the repository at this point in the history
  • Loading branch information
Latropos committed Sep 19, 2024
1 parent 75a434b commit 78ff314
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Animated, { LinearStyleTransition } from 'react-native-reanimated';
import { Button, StyleSheet, View } from 'react-native';

import React from 'react';
import { SelectorRow } from '../commonComponents/RowButtonSelector';

export default function BasicLayoutAnimation() {
const [currentStyleIdx, setCurrentStyleIdx] = React.useState(0);
const [state, setState] = React.useState(false);
const [useLayout, setUseLayout] = React.useState(true);

const viewStyles = [
styles.googleColors,
Expand All @@ -21,31 +21,27 @@ export default function BasicLayoutAnimation() {
newStyleIdx = 0;
}
setCurrentStyleIdx(newStyleIdx);
setState(!state);
}

const transitionProp = useLayout
? { layout: LinearStyleTransition.duration(1000) }
: { styleTransition: LinearStyleTransition.duration(1000) };

return (
<View style={styles.container}>
<SelectorRow
firstButtonLabel="Layout Transition"
secondButtonLabel=" Style Transition"
selectedFirstButton={useLayout}
setSelectedFirstButton={setUseLayout}
/>

<Button onPress={incrementStyle} title="Update" />
<Animated.View
layout={LinearStyleTransition.duration(1000)}
style={[
styles.box,
viewStyles[currentStyleIdx],
{
marginLeft: state ? 100 : 0,
},
]}
/>
<View
style={[
styles.box,
viewStyles[currentStyleIdx],
{
marginLeft: state ? 100 : 0,
},
]}
{...transitionProp}
style={[styles.box, viewStyles[currentStyleIdx]]}
/>
<View style={[styles.box, viewStyles[currentStyleIdx]]} />
</View>
);
}
Expand All @@ -67,9 +63,9 @@ const styles = StyleSheet.create({
borderTopLeftRadius: 0,
borderColor: 'deepskyblue',
backgroundColor: 'skyblue',
width: 100,
height: 300,
transform: [{ translateX: 40 }, { rotateZ: '30deg' }, { rotateX: '30deg' }],
width: 150,
height: 150,
transform: [{ translateX: 40 }, { rotateZ: '45deg' }],
},
leaf: {
borderWidth: 60,
Expand Down Expand Up @@ -107,7 +103,6 @@ const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingTop: 50,
},
box: {
width: 200,
Expand Down
79 changes: 9 additions & 70 deletions apps/common-app/src/examples/PendulumExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ import Animated, {
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
import {
StyleSheet,
TextInput,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { StyleSheet, TextInput, Text, View } from 'react-native';
import type { Dispatch, SetStateAction } from 'react';
import React, { useState } from 'react';
import type {
Expand All @@ -21,6 +15,7 @@ import {
GestureDetector,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
import { SelectorRow } from './commonComponents/RowButtonSelector';

const NAVY = '#001A72';
const LIGHT_NAVY = '#C1C6E5';
Expand Down Expand Up @@ -118,40 +113,13 @@ export default function SpringExample() {

return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.buttonRow}>
<TouchableOpacity
style={
useConfigWithDuration
? styles.selectedButton
: styles.notSelectedButton
}
onPress={() => setUseConfigWithDuration(true)}>
<Text
style={
useConfigWithDuration
? styles.selectedButtonText
: styles.notSelectedButtonText
}>
with duration
</Text>
</TouchableOpacity>
<TouchableOpacity
style={
!useConfigWithDuration
? styles.selectedButton
: styles.notSelectedButton
}
onPress={() => setUseConfigWithDuration(false)}>
<Text
style={
!useConfigWithDuration
? styles.selectedButtonText
: styles.notSelectedButtonText
}>
without duration
</Text>
</TouchableOpacity>
</View>
<SelectorRow
firstButtonLabel="with duration"
secondButtonLabel="without duration"
selectedFirstButton={useConfigWithDuration}
setSelectedFirstButton={setUseConfigWithDuration}
/>

<GestureDetector gesture={gesture}>
<View style={styles.pendulumContainer}>
<Animated.View style={[styles.pendulum, style]}>
Expand Down Expand Up @@ -234,33 +202,4 @@ const styles = StyleSheet.create({
height: 160,
marginBottom: -5,
},
buttonRow: {
flexDirection: 'row',
height: 40,
width: '80%',
margin: 20,
marginBottom: 0,
borderWidth: 2,
borderColor: NAVY,
},
selectedButton: {
backgroundColor: NAVY,
width: '50%',
justifyContent: 'center',
alignItems: 'center',
},
notSelectedButton: {
backgroundColor: 'white',
width: '50%',
justifyContent: 'center',
alignItems: 'center',
},
selectedButtonText: {
color: 'white',
fontSize: 20,
},
notSelectedButtonText: {
color: NAVY,
fontSize: 20,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

const NAVY = '#001A72';

export function SelectorRow({
firstButtonLabel,
secondButtonLabel,
selectedFirstButton,
setSelectedFirstButton,
}: {
firstButtonLabel: string;
secondButtonLabel: string;
selectedFirstButton: boolean;
setSelectedFirstButton: (selectedFirstButton: boolean) => void;
}) {
return (
<View style={styles.buttonRow}>
<TouchableOpacity
style={
selectedFirstButton ? styles.selectedButton : styles.notSelectedButton
}
onPress={() => setSelectedFirstButton(true)}>
<Text
style={
selectedFirstButton
? styles.selectedButtonText
: styles.notSelectedButtonText
}>
{firstButtonLabel}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={
!selectedFirstButton
? styles.selectedButton
: styles.notSelectedButton
}
onPress={() => setSelectedFirstButton(false)}>
<Text
style={
!selectedFirstButton
? styles.selectedButtonText
: styles.notSelectedButtonText
}>
{secondButtonLabel}
</Text>
</TouchableOpacity>
</View>
);
}

const styles = StyleSheet.create({
buttonRow: {
flexDirection: 'row',
height: 40,
width: '80%',
margin: 20,
marginBottom: 0,
borderWidth: 2,
borderColor: NAVY,
},
selectedButton: {
backgroundColor: NAVY,
width: '50%',
justifyContent: 'center',
alignItems: 'center',
},
notSelectedButton: {
backgroundColor: 'white',
width: '50%',
justifyContent: 'center',
alignItems: 'center',
},
selectedButtonText: {
color: 'white',
fontSize: 20,
},
notSelectedButtonText: {
color: NAVY,
fontSize: 20,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ typedef enum LayoutAnimationType {
LAYOUT = 3,
SHARED_ELEMENT_TRANSITION = 4,
SHARED_ELEMENT_TRANSITION_PROGRESS = 5,
LAYOUT_AND_STYLE = 6
} LayoutAnimationType;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ void LayoutAnimationsManager::configureAnimationBatch(
enteringAnimationsForNativeID_[tag] = config;
continue;
}
if (type == LAYOUT_AND_STYLE) {
doLayoutAnimationContainStyle_[tag] = true;
} else {
doLayoutAnimationContainStyle_.erase(tag);
}
#endif
if (config == nullptr) {
getConfigsForType(type).erase(tag);
Expand Down Expand Up @@ -70,6 +75,18 @@ bool LayoutAnimationsManager::hasLayoutAnimation(
auto end = ignoreProgressAnimationForTag_.end();
return ignoreProgressAnimationForTag_.find(tag) == end;
}

if (type == LAYOUT_AND_STYLE) {
// LAYOUT_AND_STYLE is a subset of LAYOUT animations, therefore
// hasLayoutAnimation(tag of view with LAYOUT_AND_STYLE animation, LAYOUT) =
// true hasLayoutAnimation(tag of view with LAYOUT animation,
// LAYOUT_AND_STYLE) = false

auto includesAnimation = collection::contains(layoutAnimations_, tag);
bool includesStyle =
collection::contains(doLayoutAnimationContainStyle_, tag);
return includesAnimation && includesStyle;
}
return collection::contains(getConfigsForType(type), tag);
}

Expand Down Expand Up @@ -217,6 +234,7 @@ LayoutAnimationsManager::getConfigsForType(const LayoutAnimationType type) {
case EXITING:
return exitingAnimations_;
case LAYOUT:
case LAYOUT_AND_STYLE: // a subset of LAYOUT
return layoutAnimations_;
case SHARED_ELEMENT_TRANSITION:
case SHARED_ELEMENT_TRANSITION_PROGRESS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ class LayoutAnimationsManager {
std::unordered_map<int, std::shared_ptr<Shareable>> enteringAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> exitingAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> layoutAnimations_;
// LAYOUT_AND_STYLE animations are a subset of LAYOUT animations and use the
// same array to store config
// We use this additional array to figure out if given LAYOUT animation is
// LAYOUT_AND_STYLE too
std::unordered_map<int, bool> doLayoutAnimationContainStyle_;

std::unordered_map<int, std::shared_ptr<Shareable>>
sharedTransitionAnimations_;
std::unordered_set<int> ignoreProgressAnimationForTag_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,14 @@ void LayoutAnimationsProxy::handleUpdatesAndEnterings(
}

case ShadowViewMutation::Type::Update: {
auto shouldAnimate = hasLayoutChanged(mutation);
if (!layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT) ||
(!shouldAnimate && !layoutAnimations_.contains(tag))) {
auto hasLayoutAnimation =
layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT);
auto hasLayoutAndStyleAnimation =
layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT_AND_STYLE);
auto layoutChanged = hasLayoutChanged(mutation);
if (!hasLayoutAnimation ||
(!hasLayoutAndStyleAnimation && !layoutChanged &&
!layoutAnimations_.contains(tag))) {
// We should cancel any ongoing animation here to ensure that the
// proper final state is reached for this view However, due to how
// RNSScreens handle adding headers (a second commit is triggered to
Expand All @@ -336,11 +341,11 @@ void LayoutAnimationsProxy::handleUpdatesAndEnterings(
// TODO: find a better solution for this problem
filteredMutations.push_back(mutation);
continue;
} else if (!shouldAnimate) {
} else if (!layoutChanged && !hasLayoutAndStyleAnimation) {
updateOngoingAnimationTarget(tag, mutation);
continue;
}
startLayoutAnimation(tag, mutation, true);
startLayoutAnimation(tag, mutation, hasLayoutAndStyleAnimation);
break;
}

Expand Down Expand Up @@ -580,7 +585,6 @@ void LayoutAnimationsProxy::createLayoutAnimation(
int count = 1;
auto layoutAnimationIt = layoutAnimations_.find(tag);

const SurfaceId &surfaceId = mutation.oldChildShadowView.surfaceId;
if (layoutAnimationIt != layoutAnimations_.end()) {
auto &layoutAnimation = layoutAnimationIt->second;
oldView = *layoutAnimation.currentView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,20 @@ export type LayoutAnimationStaticContext = {
presetName: string;
};

type LayoutOrStyleTransition = (
| BaseAnimationBuilder
| ILayoutAnimationBuilder
| typeof BaseAnimationBuilder
) &
LayoutAnimationStaticContext;

export type AnimatedComponentProps<P extends Record<string, unknown>> = P & {
forwardedRef?: Ref<Component>;
style?: NestedArray<StyleProps>;
animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
animatedStyle?: StyleProps;
layout?: (
| BaseAnimationBuilder
| ILayoutAnimationBuilder
| typeof BaseAnimationBuilder
) &
LayoutAnimationStaticContext;
layout?: LayoutOrStyleTransition;
styleTransition?: LayoutOrStyleTransition;
entering?: (
| BaseAnimationBuilder
| typeof BaseAnimationBuilder
Expand Down
Loading

0 comments on commit 78ff314

Please sign in to comment.