I tried to "change" the default refresh indicator of a Flatlist in React Native. I thought about to have something like Snapchat or Instagram or the default IOS refresh indicator instead of the ugly Android indicator. Now I tried following:
const screenHeight = Dimensions.get("screen").height;
const lockWidth = screenHeight;
const finalPosition = lockWidth;
const pan = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
const translateBtn = pan.y.interpolate({
inputRange: [0, finalPosition / 0.75],
outputRange: [0, finalPosition / 2],
extrapolate: 'clamp',
});
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {},
onPanResponderMove: Animated.event([null, {dy: pan.y}], {
useNativeDriver: false,
}),
onPanResponderRelease: (e, g) => {
if (g.vy > 2 || g.dy > lockWidth / 2) {
unlock();
} else {
reset();
}
},
onPanResponderTerminate: () => reset(),
}),
).current;
const reset = () => {
setGeneralState(prevState => ({...prevState, scroll: true}))
Animated.spring(pan, {
toValue: {x: 0, y: 0},
useNativeDriver: true,
bounciness: 0,
}).start();
};
const unlock = () => {
setLoading(true)
Animated.spring(pan, {
toValue: {x: 0, y: 280},
useNativeDriver: true,
bounciness: 0,
}).start();
// alert("")
setTimeout(() => {
reset();
setLoading(false)
}, 1000)
};
It works just as I expect:
Now the problem:
When the Flatlist is so big that it can be scrolled (if there are many items) I cannot pull down to refresh anymore because React Native always tries to move the flatlist when I'm scrolling.
The FlatList:
<Animated.View
style={[
styles.receiverBox,
{transform: [{translateY: translateBtn}]},
]}
{...panResponder.panHandlers}>
<View>
<FlatList
data={["user1", "user2", "user3", "user4", "user5",]}
renderItem={({item}) => {
return(
<View style={{backgroundColor: colors.background, padding: 20, marginTop: -5, width: "100%",}}>
<Text style={{fontSize: Title1, color: colors.text, textAlign: "center",}}>{item}</Text>
</View>
)
}}
/>
</View>
</Animated.View>
Figured out that this may be a problem from react native or the combination of the flatlist with the pan responder.
Related
There is a class components work piece. I want to convert it into a function component and implement it in the same way, but there's a problem.
Unlike the class type, the function type returns to the place where it was created without staying after the picture has moved.
This is the function components.
const [dogs, setDogs] = useState([])
function addDog(){
// setDogs([...dogs, {id: dogCount++}])
dogCount++;
console.log(dogCount)
// console.log(dogs)
const newDog = {id: dogCount, bottom: 125}
setDogs([...dogs, newDog])
}
return (
<View style={styles.entirebottomsheet}>
<View style={styles.container}>
<TouchableOpacity onPress={()=>{addDog();}} style={styles.dogButton}>
<Text>Dog</Text>
</TouchableOpacity>
{dogs.map(dog => {
return (
<DogContainer
key={dog.id}
style={{bottom: dog.bottom, position: 'absolute'}}
/>
);
})}
</View>
</View>
);
function DogContainer () {
const positionX = new Animated.Value(0);
const positionY = new Animated.Value(0);
// DogContainer.defaultProps = {
// onComplete() {}
// }
const getDogStyle = () => ({
transform: [{translateX: positionX}],
});
useEffect(() => {
Animated.spring(positionX, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start();
Animated.spring(positionY, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start();
return (
console.log('finish')
)
}, []);
return (
<Animated.View style={[{ bottom: 125},{
transform: [{translateX: positionX}, {translateY: positionY}],
}]}>
<Image source={dogImg} style={{width: 60, height: 60}}/>
</Animated.View>
);
}
This is the class components.
export default class App extends React.Component {
// let {up} = this.props.up;
state = {
dogs: [],
cats: [],
chicks: [],
};
addDog = () => {
console.log(...this.state.dogs);
this.setState(
{
dogs: [
...this.state.dogs,
{
id: dogCount,
bottom: 125,
},
],
},
() => {
dogCount++;
},
);
};
removeDog = id => {
this.setState({
dogs: this.state.dogs.filter(dog => {
return dog.id !== id;
}),
});
};
addCat = () => {
console.log(...this.state.cats);
this.setState(
{
cats: [
...this.state.cats,
{
id: catCount,
bottom: 125,
},
],
},
() => {
catCount++;
},
);
};
removeCat = id => {
this.setState({
cats: this.state.cats.filter(cat => {
return cat.id !== id;
}),
});
};
addChick = () => {
console.log(...this.state.chicks);
this.setState(
{
chicks: [
...this.state.chicks,
{
id: chickCount,
bottom: 125,
},
],
},
() => {
chickCount++;
},
);
};
removeChick = id => {
this.setState({
chicks: this.state.chicks.filter(chick => {
return chick.id !== id;
}),
});
};
render() {
return (
<GestureHandlerRootView>
<View style={styles.entirebottomsheet}>
<View style={styles.container}>
<TouchableOpacity onPress={this.addDog} style={styles.dogButton}>
<Text>Dog</Text>
</TouchableOpacity>
{this.state.dogs.map(dog => {
return (
<DogContainer
key={dog.id}
style={{bottom: dog.bottom, position: 'absolute'}}
onComplete={() => this.removeDog(dog.id)}
/>
);
})}
</View>
<View style={styles.container}>
<TouchableOpacity onPress={this.addCat} style={styles.catButton}>
<Text>Cat</Text>
</TouchableOpacity>
{this.state.cats.map(cat => {
return (
<CatContainer
key={cat.id}
style={{bottom: cat.bottom, position: 'absolute'}}
onComplete={() => this.removeCat(cat.id)}
/>
);
})}
</View>
<View style={styles.container}>
<TouchableOpacity onPress={this.addChick} style={styles.chickButton}>
<Text style={{position: 'absolute'}}>Chick</Text>
</TouchableOpacity>
{this.state.chicks.map(chick => {
return (
<ChickContainer
key={chick.id}
style={{bottom: chick.bottom, position: 'absolute'}}
onComplete={() => this.removeChick(chick.id)}
/>
);
})}
</View>
</View>
</GestureHandlerRootView>
);
}
}
class DogContainer extends React.Component {
state = {
position: new Animated.ValueXY({
x: 0,
y: 0,
}),
};
static defaultProps = {
onComplete() {},
};
componentDidMount() {
Animated.spring(this.state.position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
Animated.spring(this.state.position.y, {
duration: 1000,
toValue: -340,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
}
getDogStyle() {
return {
transform: [
{translateY: this.state.position.y},
{translateX: this.state.position.x},
],
};
}
render() {
return (
<Animated.View style={[this.getDogStyle(), this.props.style]}>
<Image source={dogImg} style={{width: 60, height: 60, borderRadius: 30}} />
</Animated.View>
);
}
}
Try to use useRef() hook
function DogContainer () {
const positionX = useRef(new Animated.Value(0)).current;
const positionY = useRef(new Animated.Value(0)).current;
...
In the Class-based version of DogContainer the position coordinate was a part of state.
class DogContainer extends React.Component {
state = {
position: new Animated.ValueXY({
x: 0,
y: 0,
}),
};
static defaultProps = {
onComplete() {},
};
componentDidMount() {
Animated.spring(this.state.position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
Animated.spring(this.state.position.y, {
duration: 1000,
toValue: -340,
easing: Easing.ease,
useNativeDriver: true,
}).start(this.props.onComplete);
}
getDogStyle() {
return {
transform: [
{translateY: this.state.position.y},
{translateX: this.state.position.x},
],
};
}
render() {
return (
<Animated.View style={[this.getDogStyle(), this.props.style]}>
<Image source={dogImg} style={{width: 60, height: 60, borderRadius: 30}} />
</Animated.View>
);
}
}
Function component using the useState hook
function DogContainer ({ onComplete = () => {} }) {
const [position, setPosition] = React.useState(
new Animated.ValueXY({
x: 0,
y: 0,
})
);
const getDogStyle = () => ({
transform: [{
translateX: position.x,
translateY: position.y,
}],
});
useEffect(() => {
Animated.spring(position.x, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(onComplete);
Animated.spring(position.y, {
duration: 1000,
toValue: width / 3,
easing: Easing.ease,
useNativeDriver: true,
}).start(onComplete);
console.log('finish');
}, []);
return (
<Animated.View
style={[
{ bottom: 125},
{ transform: [
{ translateX: position.x },
{ translateY: position.y }
],
}
]}
>
<Image source={dogImg} style={{ width: 60, height: 60 }} />
</Animated.View>
);
}
I'd like to create this animation:
And here's my code:
import React, { FC, useEffect } from 'react';
import { Animated, StyleSheet, View, Easing } from 'react-native';
const styles = StyleSheet.create({
circle: {
borderWidth: 2,
borderColor: 'black',
position: 'absolute',
},
container: {
width: 100,
height: 100,
position: 'relative',
alignItems: 'center',
justifyContent: 'center',
},
});
const createSequence = (animation: Animated.Value, from: number, to: number) => Animated.sequence([
Animated.timing(animation, {
toValue: from,
duration: 0,
useNativeDriver: true,
}),
Animated.timing(animation, {
toValue: to,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true,
}),
]);
const SpinnerCircle: FC<{ delay: number, color: string }> = ({ delay, color }) => {
const radius = new Animated.Value(35);
const opacity = new Animated.Value(0);
useEffect(() => {
Animated.sequence([
Animated.delay(delay),
Animated.loop(
Animated.parallel([
createSequence(radius, 0, 100),
createSequence(opacity, 1, 0),
]),
{ iterations: -1 },
),
]).start();
}, []);
return (
<Animated.View style={[styles.circle, {
width: radius,
height: radius,
borderRadius: radius,
borderColor: color,
opacity,
}]}/>
);
};
export const Spinner: FC = () => (
<View style={styles.container}>
<SpinnerCircle delay={0} color="#2626CC"/>
<SpinnerCircle delay={500} color="#E0E0E0"/>
</View>
);
The code DO work well in the web version. But when I'm trying to run it in android emulator, where's an error: Error: Style property 'width' is not supported by native animated module
How can I deal with it?
I know, I could use transform and scale. But scale also scales border width, which is required to be the same size.
I'm currently working on a Bumble-like swipe system so I can swipe horizontally (thanks to an Animated.View and a PanResponder so I can move my view wherever I want), and vertically (because my view is longer than the height of my screen).
After a long day of search, I finally found a solution which permits to know if the user is scrolling horizontally or vertically in the PanResponder, then choose if i block the move or not
The problem is that my canMove() function's console.log is printing null every time so only my vertical scroll is working currently. Otherwise, when i print my scrollType value in the onPanResponderMove, it changes well so I don't understand why my canMove() function gets null
here's my file so you can understand:
const story = useSelector((state) => state.entities.stories[storyId]);
const pan = useRef(new Animated.ValueXY(null, { useNativeDriver: true })).current;
const scrollType = useRef(null);
const checkSwipeDirection = (gestureState) => {
if (
(Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3))
&& (Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 3))
) {
scrollType.current = 'horizontal';
} else {
scrollType.current = 'vertical';
}
};
const canMove = () => {
console.log('scrollType.current: ', scrollType.current);
if (scrollType.current === 'horizontal') {
return true;
}
return false;
};
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: canMove,
onPanResponderGrant: () => {
pan.setValue({ x: 0, y: 0 });
},
onPanResponderMove: (evt, gestureState) => {
if (!scrollType.current) {
checkSwipeDirection(gestureState);
}
return Animated.event(
[null, { dx: pan.x, dy: pan.y }],
{ useNativeDriver: false },
);
},
onPanResponderRelease: () => {
Animated.spring(pan, {
toValue: 0,
useNativeDriver: false,
}).start();
scrollType.current = null;
},
}),
).current;
return (
<Animated.ScrollView
{...panResponder.panHandlers}
style={{
transform: [{ translateX: pan.x }, { translateY: pan.y },
{
rotate: pan.x.interpolate({
inputRange: [-200, 0, 200], outputRange: ['-20deg', '0deg', '20deg'],
}),
}],
}}
>
<TouchableOpacity activeOpacity={1} style={styles.card}>
<DiscoverCardHeader userId={story.recipient} />
<DiscoverStory
storyId={storyId}
navigation={navigation}
recipientId={story.recipient}
authorId={story.author}
/>
</TouchableOpacity>
</Animated.ScrollView>
);
};
if you need more informations i'm there to give you. hope we'll find a solution ! thanks
Try this, newbies <3
const pan = useRef(new Animated.ValueXY(null, {useNativeDriver: true})).current;
var [direction, setDirection] = useState(0)
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
setDirection(0)
pan.setValue({x: 0, y: 0});
},
onPanResponderMove: (evt, gestureState) => {
if ((direction === 0 || direction === 1) &&
(gestureState.dy > 20 || gestureState.dy < -20)) {
setDirection(1)
pan.setValue({x: 0, y: 0});
} else if ((direction === 0 || direction === 2) &&
(gestureState.dx > 20 || gestureState.dx < -20)) {
setDirection(2)
pan.setValue({x: gestureState.dx, y: 0});
}
},
onPanResponderRelease: () => {
setDirection(0)
pan.setValue({x: 0, y: 0});
},
})
const translate = {
transform: [
...[pan.getTranslateTransform()[0]]
]
}
return (
<Animated.ScrollView scrollEnabled={direction !== 2} >
<Animated.View
{...panResponder.panHandlers}
style={[translate]}
>
<View style={{
backgroundColor: "red",
marginTop: 100
}}>
<View>
<Text> Coucou</Text>
<Text style={{
marginTop: 1000
}}> Coucou juyin le chien</Text>
</View>
</View>
</Animated.View>
</Animated.ScrollView>
);
I am using react native gesture handlers to create a bar that can be scrolled up and down. Currently I can scroll it as much as I want. I want to modify it such that it should stop scrolling when I certain limit has reached.
export const SwipeablePanel: React.FunctionalComponent = () => {
//set up animation variables
const dragY = useRef(new Animated.Value(0));
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationY: dragY.current } }],
{
useNativeDriver: true,
},
);
const onHandleStateChange = (event: any) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
dragY.current.extractOffset();
}
console.log('EVENT', event.nativeEvent)
};
const animateInterpolation = dragY.current.interpolate({
inputRange: [-(SCREEN_HEIGHT * 2) / 3, 0],
outputRange: [moderateScale(80) - (SCREEN_HEIGHT * 2) / 3, 0],
extrapolate: 'clamp',
});
const animatedStyles = {
transform: [
{
translateY: animateInterpolation,
},
],
};
const whitelistBarMarginInterpolation = dragY.current.interpolate({
inputRange: [-SCREEN_HEIGHT + SCREEN_HEIGHT / 3, 0],
outputRange: [0, moderateScale(150)],
extrapolate: 'clamp',
});
const whitelistBarStyles = {
transform: [
{
translateY: whitelistBarMarginInterpolation,
},
],
};
return (
<PanGestureHandler
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandleStateChange}
activeOffsetX={[-1000, 1000]}
activeOffsetY={[-10, 10]}
>
<Animated.View
style={[
styles.container,
animatedStyles
]}>
<ScrollBar />
);
};
In onHandleStateChange, I can get values of event.nativeEvent such as
absoluteX: 237
absoluteY: 348.5
handlerTag: 109
numberOfPointers: 0
oldState: 4
state: 5
target: 5235
translationX: 7
translationY: 200.5
velocityX: 0
velocityY: 0
x: 237
y: 84.84616088867188
I want to use an if else condition in the code such that I can set limits after which point the scrolling stops. But I am not sure how to do that since the scrolling happens from the onGestureEvent.
I thought of adding checks in here but if I change it like this, it no longer works:
const onGestureEvent = () => {
Animated.event([{ nativeEvent: { translationY: dragY.current } }], {
useNativeDriver: true,
});
};
Snack Expo : https://snack.expo.io/#nhammad/insane-pizza
I tried to reproduce it but here I can't scroll at it. I am using the same code in my app and it scrolls over there.
Try ScrollView, It has prop called scrollEnabled. By help of this prop you can turn to false when you don't want user to be able to scroll any more. When false, the view cannot be scrolled via touch interaction.
Here is documentation: https://facebook.github.io/react-native/docs/scrollview.html#scrollenabled
ScrollView also has onScroll so it should be easy to implement the logic here.
In my react native app I'm trying to animate opacity of a View.
When I scroll, I saw the animation do the job, but it’s flickering at the same time. I don’t know why.
Video example : https://cdn.discordapp.com/attachments/102861040538120192/560165613092339734/video.mov
Here is the code I made
const Scrollable = () => {
const largeHeaderSize = useState({
height: 0,
y: 0
});
const animatedValueScrollY = new Animated.Value(largeHeaderSize.height);
const [scrollY, setScrollY] = useState(0);
const headerOpacity = animatedValueScrollY.interpolate({
inputRange: [0, largeHeaderSize.height],
outputRange: [1, 0],
extrapolate: "clamp"
});
return (
<SafeAreaView>
<Animated.View
style={{
borderBottomWidth:
scrollY >= largeHeaderSize.height ? StyleSheet.hairlineWidth : 0
}}>
<View>
<Animated.View style={{ zIndex: 1, opacity: headerOpacity }}>
<Text>Title</Text>
</Animated.View>
</View>
</Animated.View>
<Animated.ScrollView
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: animatedValueScrollY } } }],
{
useNativeDriver: true,
listener: event => setScrollY(event.nativeEvent.contentOffset.y)
}
)}
scrollEventThrottle={16}
contentInset={{
top: 0,
bottom: 50
}}
contentOffset={{
y: 0
}}
/>
</SafeAreaView>
);
};
How I can I solve that?
The solution is to use the useRef hook like so:
const animatedValueScrollY = useRef(new Animated.Value(0))
const headerOpacity = animatedValueScrollY.current.interpolate({
inputRange: [0, largeHeaderSize.height],
outputRange: [1, 0],
extrapolate: 'clamp'
});