Animated.loop image - react-native

Back in 1996 I created spinning logos for clients, because I could, and now in 2017 I'm back at it, thanks to Animated.
The code below the <hr /> works, but there's a tiny bump when it restarts.
Any idea how I can use Animated.loop? It doesn't: «each time it reaches the end, it resets and begins again from the start».
Animated.loop(
Animated.timing(this.state.spinValue, {
toValue: 1,
duration: this.props.duration,
easing: Easing.linear,
useNativeDriver: true
})
).start();
static defaultProps = {
duration: 60 / (33 + 1/3) * 1000
}
constructor (props) {
super(props);
this.state = {
spinValue: new Animated.Value(0)
};
}
componentDidMount () {
this._animate();
}
_animate () {
Animated.timing(this.state.spinValue, {
toValue: 1,
duration: this.props.duration,
easing: Easing.linear,
useNativeDriver: true
}).start(event => {
if (event.finished) {
this.setState({
spinValue: new Animated.Value(0)
}, this._animate.bind(this));
}
});
}
render () {
const spin = this.state.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
return (
<View style={ Loading.style.container }>
<Animated.Image
source={ logo }
style={{ transform: [{ rotate: spin }] }}
/>
</View>
);
}

Easy to use react-native-LoopAnimation.js'
https://github.com/Infinity0106/react-native-LoopAnimation
import LoopAnimation from 'react-native-LoopAnimation.js'
...
render() {
//you can also use, like source={imgSource}
const imgSource=
{uri:'http://www.menucool.com/slider/jsImgSlider/images/image-slider-2.jpg',width:700,height:306};
return (
<View style={{flex:1}}>
{/*this is the background animation */}
<LoopAnimation source={require('./img/back.jpg')} duration={10000} />
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
{/*Content goes here*/}
<View style={{width: 200, height: 400, backgroundColor: 'powderblue'}} />
</View>
</View>
);
}

Related

Close bottom sheet modal only from top part in React Native

I created this bottom sheet modal component and it works just fine, the thing is that when it has a ScrollView inside, the scroll no longer works properly because it tries to close the modal. Is there any way to make the modal only be closed from the top part and the rest of the content be scrolled as normal?
export default (props: any) => {
const screenHeight = Dimensions.get('screen').height;
const panY = useRef(new Animated.Value(screenHeight)).current;
const resetPositionAnim = Animated.timing(panY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
});
const closeAnim = Animated.timing(panY, {
toValue: screenHeight,
duration: 300,
useNativeDriver: true,
});
const translateY = panY.interpolate({
inputRange: [-1, 0, 1],
outputRange: [0, 0, 1],
});
const handleDismiss = () => closeAnim.start(props.onDismiss);
useEffect(() => {
resetPositionAnim.start();
}, [resetPositionAnim]);
const panResponders = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => false,
onPanResponderMove: Animated.event([null, { dy: panY }], {
useNativeDriver: false,
}),
onPanResponderRelease: (_, gs) => {
if (gs.dy > 0 && gs.vy > 2) {
return handleDismiss();
}
return resetPositionAnim.start();
},
}),
).current;
const insets = useSafeAreaInsets();
return (
<Modal animated animationType="fade" visible={props.visible} transparent onRequestClose={handleDismiss}>
<TouchableWithoutFeedback onPress={handleDismiss}>
<View style={styles.overlay}>
<Animated.View
style={{
...styles.container,
maxHeight: screenHeight - (insets.top || 100),
paddingBottom: insets.bottom,
transform: [{ translateY: translateY }],
}}
{...panResponders.panHandlers}
>
<View style={styles.sliderIndicatorRow}>
<View style={styles.sliderIndicator} />
</View>
<View style={[styles.sliderIndicatorRow, { justifyContent: 'flex-end', marginRight: 20 }]}>
<TouchableOpacity onPress={handleDismiss}>
<Feather name="x" size={16} color={colors.primaryText} />
</TouchableOpacity>
</View>
{props.children}
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
backgroundColor: 'rgba(0,0,0,0.2)',
flex: 1,
justifyContent: 'flex-end',
},
container: {
backgroundColor: 'white',
paddingTop: 12,
borderTopRightRadius: 30,
borderTopLeftRadius: 30,
minHeight: 200,
},
sliderIndicatorRow: {
flexDirection: 'row',
marginBottom: 4,
alignItems: 'center',
justifyContent: 'center',
},
sliderIndicator: {
backgroundColor: '#C4C4C4',
height: 6,
width: 75,
borderRadius: 100,
},
});

Using react memo to avoid re render each item on flatlist isnt working

im using react memo to avoid the re render of each item of the flatlist when scrolling, if that item is already render why it should runs the useEffect again? i need to do that because my app needs it, here is the structure for now:
flatlist:
<FlatList
style = {{
width: Dimensions.get("window").width
}}
bounces={false}
ref={FlatListRef}
data = {[]}
onScroll={({nativeEvent}) => {
const scrolling = nativeEvent.contentOffset.y;
if (scrolling > 200) {
if(scrollOver==false) {
Animated.timing(logoTranslationX, {
toValue: -15,
duration: 400,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
}).start();
Animated.timing(logoScale, {
toValue: 0.8,
duration: 400,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
}).start();
Animated.timing(logoAOpactiy, {
toValue: 0,
duration: 400,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
}).start();
Animated.timing(logoBOpactiy, {
toValue: 1,
duration: 400,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
}).start();
scrollOver=true
parent_flatlist_scroll.current = true;
}
} else {
if(scrollOver==true) {
Animated.timing(logoTranslationX, {
toValue: 0,
duration: 400,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
}).start();
Animated.timing(logoScale, {
toValue: 1,
duration: 400,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
}).start();
Animated.timing(logoAOpactiy, {
toValue: 1,
duration: 400,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
}).start();
Animated.timing(logoBOpactiy, {
toValue: 0,
duration: 400,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
}).start();
scrollOver=false
}
}
}}
showsVerticalScrollIndicator = {false}
data={dataSource}
// decelerationRate={0.995}
windowSize={6}
initialListSize={10}
initialNumToRender={15}
maxToRenderPerBatch={30}
keyExtractor={(item) => item.uuid}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={500}
renderItem = {({item, index}) => {
return (
<PublicationsMemo
data = {item}
index = {index}
top = {dataSourceTop}
>
</PublicationsMemo>
)
}}
onEndReachedThreshold = {0.3}
onEndReached = {loadMore}
ListFooterComponent={<CustomLoader/>}
ListHeaderComponent = {renderHeader()}
>
</FlatList>
publications memo component:
const Publications = (props) => {
const [uuid, setUuid] = useState(props.data.uuid);
const [item, setItem] = useState(props.data);
const [index, setIndex] = useState(props.index);
const [top, setTop] = useState(props.top);
useEffect(() => {
console.log("re render", index, item.title);
}, []);
return (
<>
{
(index % 5 === 0) ?
<>
<View style={{flex:1, overflow: "hidden", marginBottom: 10, alignItems: "center"}} key = {uuid}>
<View style={[{height:ITEM_HEIGHT, flexDirection: "row"}]}>
<TouchableOpacity activeOpacity={0.8}>
<SharedElement id={`item.${item.uuid}.photo`}>
<ExpoFastImage
style={{
width: ITEM_WIDTH,
height: 'auto',
aspectRatio: 16 / item.poster_align, //minimun 10, using aspect ratio because they want to choose which section of the image will be the preview
}}
uri={item.thumbnail}
cacheKey={item.uuid + 'thumbnail'}
resizeMode = "cover"
/>
</SharedElement>
</TouchableOpacity>
<View style={{flex:1,padding:15,paddingTop:0,height:ITEM_HEIGHT}}>
{item.category && <Text style={[styles.subtitle,{marginTop:0}]}>{item.category.name}</Text>}
<TouchableOpacity activeOpacity={0.8}>
<Text numberOfLines={2} style={styles.title}>{item.title}</Text>
</TouchableOpacity>
<DateTimeBlock item={item} />
<Voting
item={item} style={{position:'absolute',bottom:0,left:15,opacity:0.8}}
title={34}
index = {index}
// fetch_data = {fetchData}
/>
</View>
</View>
</View>
<Drop data = {top}></Drop>
</>
:
<View style={{flex:1, overflow: "hidden", marginBottom: 10, alignItems: "center"}} >
<View style={[{height:ITEM_HEIGHT, flexDirection: "row"}]}>
<TouchableOpacity activeOpacity={0.8}>
<SharedElement id={`item.${item.uuid}.photo`}>
<ExpoFastImage
style={{
width: ITEM_WIDTH,
height: 'auto',
aspectRatio: 16 / item.poster_align, //minimun 10, using aspect ratio because they want to choose which section of the image will be the preview
}}
uri={item.thumbnail}
cacheKey={item.uuid + 'thumbnail'}
resizeMode = "cover"
/>
</SharedElement>
</TouchableOpacity>
<View style={{flex:1,padding:15,paddingTop:0,height:ITEM_HEIGHT}}>
{item.category && <Text style={[styles.subtitle,{marginTop:0}]}>{item.category.name}</Text>}
<TouchableOpacity activeOpacity={0.8}>
<Text numberOfLines={2} style={styles.title}>{item.title}</Text>
</TouchableOpacity>
<DateTimeBlock item={item} />
<Voting
item={item} style={{position:'absolute',bottom:0,left:15,opacity:0.8}}
title={34}
index = {index}
// fetch_data = {fetchData}
/>
</View>
</View>
</View>
}
</>
)
}
const equal = (prev, next) => {
if(prev.index === next.index) {
return false;
}else {
return true;
}
}
export const PublicationsMemo = React.memo(Publications, equal);
but when scrolling down or up it runs the useEffect again

Animation does not work properly in react native

I tried making a carousel by watching a tutorial but I cannot get it to work for an event driven animation. Instead of animating it just updates the position to new location.
This does not happen if I use only one type of animation for transition, mentioning just one value to transform rotate instead of passing an expression.
what it looks like
what it should look like
const cards = ["tomato", "teal", "pink"]
const alpha = Math.PI/6
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current
const [toggled, setToggled] = React.useState(false)
const animationFn = () => {
Animated.spring(value, {
toValue: 1,
friction: 10,
useNativeDriver: true
}).start()
setToggled(toggled => !toggled)
}
const rotateOpen = (rotate) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`]
})
}
const rotateClose = (rotate, maxValues) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: [`${maxValues}rad`, `${rotate}rad`]
})
}
return(
<>
{cards.map((card, index) => {
const rotate = toggled ? (index - 1) * alpha : 0
const maxValues = (index-1) * alpha
return (
<Animated.View key={card} style={{transform: [
{translateY: -50},
{translateX: -100},
{rotate: !toggled ? rotateOpen(rotate) : rotateClose(rotate, maxValues) },
{translateX: 100},
], borderRadius: 15, position: 'absolute', backgroundColor: card, height: 100, width: 200}} />
)
})}
<View style={{paddingTop: 100}}>
<TouchableOpacity onPress={() => { animationFn() }}>
<Text style={{fontSize: 30}}> Animate </Text>
</TouchableOpacity>
</View>
</>
)
}
Your interpolation values shouldn't change between the open and close functions. The animation library knows that when you go from 0 to 1, you're rotating the block "out" and then when you go from 1 back to 0, you're applying the same interpolation in reverse
so this code appears to work correctly for me:
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current;
const [toggled, setToggled] = React.useState(false);
useEffect(() => {
Animated.spring(value, {
toValue: toggled ? 0 : 1,
friction: 10,
useNativeDriver: false,
}).start();
}, [toggled, value]);
return (
<>
{cards.map((card, index) => {
const rotate = (index - 1) * alpha;
return (
<Animated.View
key={card}
style={{
transform: [
{ translateY: -50 },
{ translateX: -100 },
{
rotate: value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`],
}),
},
{ translateX: 100 },
],
borderRadius: 15,
position: 'absolute',
backgroundColor: card,
height: 100,
width: 200,
}}
/>
);
})}
<View style={{ paddingTop: 100 }}>
<TouchableOpacity
onPress={() => {
setToggled(!toggled);
}}>
<Text style={{ fontSize: 30 }}> Animate </Text>
</TouchableOpacity>
</View>
</>
);
};

React Native animated input text

I want to show a cancel button, on the focus TextInput animation.
I did the following code, but a cancel button does not display and follow the box when focused. It's only shown after the animation end.
And when cancel button displayed, it is not on the same line with textinput.
How do I fix this?
const { width } = Dimensions.get('window');
const PADDING = 16;
const SEARCH_FULL_WIDTH = width - PADDING * 2; //search_width when unfocused
const SEARCH_SHRINK_WIDTH = width - PADDING - 90; //search_width when focused
class Search extends React.Component {
constructor(props: IProps) {
super(props);
this.state = {
inputLength: new Animated.Value(SEARCH_FULL_WIDTH),
searchBarFocused: false,
}
}
private onFocus = () => {
Animated.timing(this.state.inputLength, {
toValue: SEARCH_SHRINK_WIDTH,
duration: 250,
}).start(() => this.setState({ searchBarFocused: true }));
}
private onBlur = () => {
Animated.timing(this.state.inputLength, {
toValue: SEARCH_FULL_WIDTH,
duration: 250,
}).start(() => this.setState({ searchBarFocused: false }));
}
<View style={styles.searchContainer}>
<Animated.View style={[
styles.search,
{
width: this.state.inputLength,
position: 'absolute',
left: 16,
alignSelf: 'center'
},
searchBarFocused === true ? undefined : { justifyContent: 'center' }
]}>
<Image source={searchIcon} style={styles.image} />
<TextInput
style={styles.searchInput}
....
onBlur={this.onBlur}
onFocus={this.onFocus}
/>
</Animated.View>
{searchBarFocused &&
<Touchable style={styles.cancelSearch} onPress={this.cancelSearch}>
<Text style={styles.cancelSearchText}>Cancel</Text>
</Touchable>
}
</View>
const styles = StyleSheet.create({
searchContainer: {
flexDirection: 'row',
height: 72,
borderBottomColor: SOLITUDE_COLOR,
},
search: {
flex: 1,
flexDirection: 'row',
height: 40,
borderRadius: 6,
},
cancelSearch: {
marginHorizontal: 16,
textAlign: 'center',
justifyContent: 'center'
}
});
gif: when unfocus and focused
Here is a slightly modified version of your code.
import React from "react";
import {
Dimensions,
View,
Animated,
TextInput,
TouchableOpacity,
StyleSheet,
} from "react-native";
const { width } = Dimensions.get("window");
const PADDING = 16;
const SEARCH_FULL_WIDTH = width - PADDING * 2; //search_width when unfocused
const SEARCH_SHRINK_WIDTH = width - PADDING - 90; //search_width when focused
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLength: new Animated.Value(SEARCH_FULL_WIDTH),
cancelPosition: new Animated.Value(0),
opacity: new Animated.Value(0),
searchBarFocused: false
};
}
onFocus = () => {
Animated.parallel([
Animated.timing(this.state.inputLength, {
toValue: SEARCH_SHRINK_WIDTH,
duration: 250
}),
Animated.timing(this.state.cancelPosition, {
toValue: 16,
duration: 400
}),
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 250
})
]).start();
};
onBlur = () => {
Animated.parallel([
Animated.timing(this.state.inputLength, {
toValue: SEARCH_FULL_WIDTH,
duration: 250
}),
Animated.timing(this.state.cancelPosition, {
toValue: 0,
duration: 250
}),
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 250
})
]).start();
};
render() {
const { searchBarFocused } = this.state;
return (
<View style={styles.searchContainer}>
<Animated.View
style={[
styles.search,
{
width: this.state.inputLength,
position: "absolute",
left: 16,
alignSelf: "center"
},
searchBarFocused === true ? undefined : { justifyContent: "center" }
]}
>
<TextInput
style={styles.searchInput}
onBlur={this.onBlur}
onFocus={this.onFocus}
placeholder="Type something"
/>
</Animated.View>
<AnimatedTouchable
style={[styles.cancelSearch, { right: this.state.cancelPosition }]}
onPress={() => null}
>
<Animated.Text
style={[styles.cancelSearchText, { opacity: this.state.opacity }]}
>
Cancel
</Animated.Text>
</AnimatedTouchable>
</View>
);
}
}
const styles = StyleSheet.create({
searchContainer: {
flexDirection: "row",
height: 72,
borderBottomColor: "#00000033",
paddingTop: 100
},
search: {
flex: 1,
flexDirection: "row",
height: 40,
borderRadius: 6,
backgroundColor: "red"
},
cancelSearch: {
position: "absolute",
marginHorizontal: 16,
textAlign: "center",
justifyContent: "center",
alignSelf: "center"
}
});
You're setting searchBarFocused only after your animation completes. Since the cancel button is conditionally rendered based on searchBarFocused, it only appears at the end of the animation.

How to make react native deck animation?

I making a playing card game and i want to add an deck deal animation like this:
Deal Animation
i tried change top value for Animated.Image component but output:
https://pasteboard.co/HiMc6Af.gif
i need create a clone of this component and original component should not move. But i could not put clone component over original component.
import React from 'react';
import { View, Text, Animated, TouchableOpacity } from 'react-native';
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
gameStart: false,
top: new Animated.Value(-175 / 2),
};
}
componentDidMount() {}
shuffleCards(e) {
this.setState({
gameStart: true,
});
this.cycleAnimation();
}
cycleAnimation() {
Animated.loop(
Animated.sequence([
Animated.timing(this.state.top, {
toValue: -900,
duration: 300,
delay: 100,
}),
Animated.timing(this.state.top, {
toValue: -175 / 2,
duration: 1,
delay: 1,
}),
Animated.timing(this.state.top, {
toValue: 900,
duration: 300,
delay: 100,
}),
]),
{
iterations: 4,
},
).start();
}
render() {
return (
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
}}>
<TouchableOpacity>
<Animated.Image
ref={'deck'}
resizeMode="stretch"
source={require('./assets/card/back.png')}
style={[
{
width: 100,
height: 175,
},
]}
/>
</TouchableOpacity>
<TouchableOpacity onPress={e => this.shuffleCards(e)}>
<Animated.Image
resizeMode="stretch"
source={require('./assets/card/back.png')}
style={[
{
width: 100,
height: 175,
position: 'absolute',
left: -100,
},
{ top: this.state.top },
]}
/>
</TouchableOpacity>
</View>
);
}
}