I'm working on React Native.
Actually I want to draw a line between two points in react native.
The point is where I start touch and where I release touch.
I'm doing this using penResponder.
Using penResponder I get those point. Where i start touch and where I release touch.
Here is my code:
import React, {Component} from 'react';
import {
StyleSheet,
View,
Platform,
Text,
PanResponder,
Image,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
//initialize state
this.panResponder;
this.state = {
locationX: 0,
locationY: 0,
locationSX: 0,
locationSY: 0,
};
//panResponder initialization
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onStartShouldSetPanResponderCapture: (event, gestureState) => {
this.setState({
locationX: event.nativeEvent.locationX.toFixed(2),
locationY: event.nativeEvent.locationY.toFixed(2),
});
},
onMoveShouldSetPanResponder: (event, gestureState) => false,
onMoveShouldSetPanResponderCapture: (event, gestureState) => false,
onPanResponderGrant: (event, gestureState) => false,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {
this.setState({
locationSX: event.nativeEvent.locationX.toFixed(2),
locationSY: event.nativeEvent.locationY.toFixed(2),
});
},
});
this.setState({
locationX: 0,
locationY: 0,
locationSX: 0,
locationSY: 0,
});
}
render() {
return (
<View style={styles.MainContainer}>
<View style={styles.childView}>
<View
style={[
{
height: 22,
width: 22,
marginTop: 5,
position: 'absolute',
borderRadius: 14,
backgroundColor: '#afeeee',
},
{
top: parseFloat(this.state.locationY - 5),
left: parseFloat(this.state.locationX - 15),
},
]}
/>
<View
style={[
{
height: 22,
width: 22,
marginTop: 5,
position: 'absolute',
borderRadius: 14,
backgroundColor: '#afeeee',
},
{
top: parseFloat(this.state.locationSY - 2),
left: parseFloat(this.state.locationSX - 11),
},
]}
/>
<View
style={{flex: 1, backgroundColor: 'transparent'}}
{...this.panResponder.panHandlers}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer: {
flex: 1,
},
childView: {
flex: 1,
overflow: 'hidden',
},
point: {
height: 22,
width: 22,
marginTop: 5,
position: 'absolute',
borderRadius: 14,
backgroundColor: '#afeeee',
},
});
But how to draw line between the two points?
Actually I want this:
Please help!
Thanks in advance.
You can use svg (https://github.com/react-native-community/react-native-svg) to do this. I recommend you to put your PanResponder on top of your svg to handle touches.
Svg example:
<Svg height={windowHeight} width={windowWidth}>
<Line x1={startTouch.x} y1={startTouch.y} x2={endTouch.x} y2={endTouch.y} stroke="red" strokeWidth="2" />
</Svg>
Related
here is my code.
const actionButtonsAnimated = new Animated.Value(0);
const animated = new Animated.Value(255);
const animateTrendingCardSheet = () => {
Animated.timing(animated, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}).start();
Animated.timing(actionButtonsAnimated, {
toValue: -180,
duration: 1000,
useNativeDriver: true,
}).start();
};
<Animated.View
style={[
{
transform: [{ translateY: actionButtonsAnimated }],
},
]}
>
<MainActionButtons />
</Animated.View>
<Animated.View
style={[
{
transform: [{ translateY: animated }],
width: "100%",
position: "absolute",
bottom: 0,
},
]}
>
<View style={styles.trendingCards}>
<Text h5 center color={colors.trendingText}>
Trending in your area...
</Text>
<View style={styles.flatlistWrapper}>
<FlatList
horizontal={true}
data={trendingCards}
renderItem={({ item }) => <TrendingCardComponent card={item} />}
/>
</View>
</View>
</Animated.View>
so if i call the animateTrendingCardSheet function inside useEffect like this.
useEffect(() => {
animateTrendingCardSheet()
}, [])
it works as expected but once i put it in a condition that it should be called after the API call has been finished it does not work at all if i again save the file it hot reload animation works
useEffect(() => {
if (loadTrendingCard) {
animateTrendingCardSheet();
}
}, [loadTrendingCard]);
Your issue is that after the first call to animateTrendingCardSheet the toValue that you are animating to is the current value of your Animated variables; so it looks like nothing is happening. You can counteract this by resetting your animation variables before calling your animation function:
import * as React from 'react';
import {
Text,
View,
StyleSheet,
Animated,
TouchableOpacity,
FlatList,
Button
} from 'react-native';
import Constants from 'expo-constants';
const MainActionButtons = () => {
return (
<View
style={{
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
}}>
<TouchableOpacity>Btn 1</TouchableOpacity>
<TouchableOpacity>Btn 2</TouchableOpacity>
<TouchableOpacity>Btn 3</TouchableOpacity>
<TouchableOpacity>Btn 4</TouchableOpacity>
</View>
);
};
const TrendingCardComponent = ({ card }) => {
return (
<View style={{ width: '100%' }}>
<Text>{card.title}</Text>
<Text>{card.message}</Text>
</View>
);
};
const trendingCards = [
{ title: 'A Cool Card', message: 'A cool message' },
{ title: 'Card 1', message: 'A cool message' },
{ title: 'A Cool Card', message: 'A cool message' },
{ title: 'A Cool Card', message: 'A cool message' },
];
const initialActionButton = 0;
const initialAnimated = 255;
export default function App() {
const actionButtonsAnimated = new Animated.Value(initialActionButton);
const animated = new Animated.Value(initialAnimated);
const opacity = new Animated.Value(1)
const onApiCall = ()=>{
// set opacity to 0
Animated.timing(opacity,{toValue:0,duration:500}).start(()=>{
// when view is invisible do resets
animated.setValue(initialAnimated)
actionButtonsAnimated.setValue(initialActionButton)
Animated.timing(opacity,{toValue:1,duration:500}).start()
animateTrendingCardSheet()
})
}
const animateTrendingCardSheet = () => {
Animated.timing(animated, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}).start();
Animated.timing(actionButtonsAnimated, {
toValue: -180 ,
duration: 1000,
useNativeDriver: true,
}).start();
};
// React.useEffect(() => {
// animateTrendingCardSheet();
// }, []);
return (
<View style={styles.container}>
<Button title="Simulate API call" onPress={onApiCall}/>
<Animated.View
style={[
{
transform: [{ translateY: actionButtonsAnimated }],
opacity
},
]}>
<MainActionButtons />
</Animated.View>
<Animated.View
style={[
{
transform: [{ translateY: animated }],
width: '100%',
position: 'absolute',
bottom: 0,
opacity
},
]}>
<View style={styles.trendingCards}>
<Text>Trending in your area...</Text>
<View style={styles.flatlistWrapper}>
<FlatList
style={{ flex: 1 }}
horizontal={true}
data={trendingCards}
renderItem={({ item }) => <TrendingCardComponent card={item} />}
/>
</View>
</View>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
flatlistWrapper: {
width: '100%',
height: 200,
},
});
Demo
The above answer is good but it does not solve my problem, its all due to a stupid mistake, the issue was i was setting the state before calling the animation call function, as the state changes and it interfere the animation i had to use useRef like this.
const actionButtonsAnimated = useRef(new Animated.Value(initialActionButton)).current;
const animated = useRef(new Animated.Value(initialAnimated)).current;
it reference the state and animation works as expected.
I have to create Neumorphism styled UI into React Native project and I'm currently developing for android. I've currently encountered a problem in which shadows properties don't work as expected for android. I then tried using react-native-drop-shadow which I read from this blog:
https://blog.logrocket.com/applying-box-shadows-in-react-native/
Here is my current implementation
NeoView.tsx
import React, { ReactNode } from "react";
import { StyleSheet, View, ViewStyle } from "react-native";
import DropShadow from "react-native-drop-shadow";
type NeoViewProps = {
children: ReactNode;
size?: number;
style?: ViewStyle;
};
const NeoView = ({ children, size, style }: NeoViewProps) => {
return (
<>
<DropShadow style={styles.bottomShadow}></DropShadow>
<DropShadow style={styles.topShadow}>
<View
style={[
styles.inner,
{ width: size || 40, height: size || 40, borderRadius: 10 },
style,
]}
>
{children}
</View>
</DropShadow>
</>
);
};
export default NeoView;
const styles = StyleSheet.create({
inner: {
backgroundColor: "#F0F0F3",
alignItems: "center",
justifyContent: "center",
borderColor: "#D3D5D8",
borderWidth: 1,
},
topShadow: {
shadowOffset: {
width: -2,
height: -2,
},
shadowOpacity: 1,
shadowRadius: 4,
shadowColor: "#fff",
},
bottomShadow: {
shadowOffset: {
width: 2,
height: 2,
},
shadowOpacity: 1,
shadowRadius: 4,
shadowColor: "#7A7987",
},
});
Problem:
In my react native app I have created an animation like tinder cards. It is working correctly in Ios but in Android Animated view which has panResponder and transform even does not render in the view . This is how I have organized my code.
const TinderCardAnimation = ({ theme, data, onHeartClick }) => {
const { width, height } = useWindowDimensions();
const [currentIndex, setCurrentIndex] = useState(0);
const pan = useRef(new Animated.ValueXY()).current;
const rotate = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: ['-10deg', '0deg', '10deg'],
extrapolate: 'clamp'
})
const rotateAndTranslate = {
transform: [{
rotate: rotate
},
...pan.getTranslateTransform()
]
}
const likeOpacity = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp'
})
const nopeOpacity = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: [1, 0, 0],
extrapolate: 'clamp'
})
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) =>
true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) =>
true,
onPanResponderTerminationRequest: (evt, gestureState) =>
true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value
});
},
onPanResponderMove: (evt, gestureState) => {
pan.setValue({
x: gestureState.dx,
y: gestureState.dy
})
},
onPanResponderRelease: () => {
pan.flattenOffset();
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// Returns whether this component should block native components from becoming the JS
// responder. Returns true by default. Is currently only supported on android.
return true;
}
})
).current;
return <View style={{ backgroundColor: '#ffffff' }}>{data?.map((item, index) => {
if (index < currentIndex) {
return null
} else if (index == currentIndex) {
return (<Animated.View {...panResponder.panHandlers} key={index} style={[rotateAndTranslate, { height: height / 5, width: width, padding: 10, position: 'absolute' }]}>
<Animated.View
style={{
opacity: likeOpacity,
transform: [{ rotate: "-30deg" }],
position: "absolute",
top: 50,
left: 40,
zIndex: 1000
}}
>
<TouchableOpacity >
<Image style={{ marginTop: height / 4 }} source={require("_assets/images/XCircleb.png")} />
</TouchableOpacity>
</Animated.View>
<Animated.View
style={{
opacity: nopeOpacity,
transform: [{ rotate: "30deg" }],
position: "absolute",
top: 50,
right: 40,
zIndex: 1000
}}
>
<TouchableOpacity onPress={() => onHeartClick(item?.id)}>
<Image style={{ marginTop: height / 4 }} source={require("_assets/images/HCircleb.png")} />
</TouchableOpacity>
</Animated.View>
<ProfileCard key={index} profile={item} />
</Animated.View>)
} else {
return (<Animated.View key={index} style={{ height: height / 5, width: width, padding: 10, position: 'absolute' }}>
<ProfileCard key={index} profile={item} />
</Animated.View>)
}
}).reverse()}
</View >
}
export default withTheme(TinderCardAnimation);
I tried a lot to make it work in Android. But I was unable to do so. Can someone help me to solve this issue? Thank you
I'm having trouble getting an AnimatedTextInput to work properly where a normal TextInput works properly. When I press the TextInput to start typing, there are no problems, and I can start typing immediately. However, on an AnimatedTextInput, the software keyboard closes immediately.
So, this works just fine:
return (
<View>
<TextInput style={[style, styles.input, { paddingTop: 20, paddingBottom: 20 }]} {...restOfProps} onFocus={() => handleFocus()} onBlur={handleBlur} ref={refInput} />
<Animated.View style={[styles.labelContainer, { opacity: opacity}]}>
<Text style={styles.label}>{label}</Text>
</Animated.View>
</View>
)
However this:
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
return (
<View>
<AnimatedTextInput style={[style, styles.input, { paddingTop: paddingTop, paddingBottom: paddingBottom }]} {...restOfProps} onFocus={() => handleFocus()} onBlur={handleBlur} ref={refInput} />
<Animated.View style={[styles.labelContainer, { opacity: opacity}]}>
<Text style={styles.label}>{label}</Text>
</Animated.View>
</View>
)
Immediately causes the software keyboard to quickly open then close when touching the input to gain focus/to start typing. On searching, answers I've seen say this is related to being part of a scrollview, but this happens whether in a scrollview or not.
For reference, here's the entire file:
import React, { useRef } from 'react';
import { Animated, StyleSheet, Text, TextInput, View } from 'react-native';
const styles = StyleSheet.create({
input: {
padding: 24,
borderColor: 'transparent',
borderWidth: 1,
borderRadius: 4,
fontFamily: 'Avenir-Medium',
fontSize: 16,
backgroundColor: '#F8F8F8',
},
labelContainer: {
position: 'absolute',
left: 16,
top: 9,
paddingHorizontal: 8,
},
label: {
color: '#ABB4BD',
fontFamily: 'Avenir-Heavy',
fontSize: 12,
},
})
// extend from native TextInput props
const TextField = (props) => {
const { label, style, ...restOfProps } = props;
const [isFocused, setIsFocused] = React.useState(false);
const opacity = useRef(new Animated.Value(0)).current;
const paddingTop = useRef(new Animated.Value(20)).current;
const paddingBottom = useRef(new Animated.Value(20)).current;
const refInput = useRef();
React.useEffect(() => {
Animated.timing(
paddingBottom,
{
useNativeDriver: false,
toValue: isFocused ? 10 : 20,
duration: 200,
}
).start();
Animated.timing(
paddingTop,
{
useNativeDriver: false,
toValue: isFocused ? 30 : 20,
duration: 200,
}
).start();
Animated.timing(
opacity,
{
useNativeDriver: false,
toValue: isFocused ? 1 : 0,
duration: 200,
}
).start();
}, [isFocused])
const handleFocus = (input) => {
console.log('here')
setIsFocused(true);
}
const handleBlur = () => {
console.log('there')
setIsFocused(false);
}
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
return (
<View>
<AnimatedTextInput style={[style, styles.input, { paddingTop: paddingTop, paddingBottom: paddingBottom }]} {...restOfProps} onFocus={() => handleFocus()} onBlur={handleBlur} ref={refInput} />
<Animated.View style={[styles.labelContainer, { opacity: opacity}]}>
<Text style={styles.label}>{label}</Text>
</Animated.View>
</View>
)
};
export default TextField;
Actually I want to draw a line once I drag on screen.
Please see the image:
Suppose I swipe from point A to point B. Then here will be create a line (Like the image).
Please give me an idea how to do it.
It's easily done by using PanResponer (https://reactnative.dev/docs/panresponder) and SVG (https://github.com/react-native-community/react-native-svg).
Here is the code:
import React, {Component} from 'react';
import Svg, {Line} from 'react-native-svg';
import {
StyleSheet,
View,
Platform,
Text,
PanResponder,
Image,
Dimensions,
} from 'react-native';
var width = Dimensions.get('window').width;
var height = Dimensions.get('window').height;
export default class App extends Component {
constructor() {
super();
//initialize state
this.panResponder;
this.state = {
startTouchX: 0,
startTouchY: 0,
endTouchX: 0,
endTouchY: 0,
};
//panResponder initialization
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onStartShouldSetPanResponderCapture: (event, gestureState) => {
this.setState({
startTouchX: event.nativeEvent.locationX.toFixed(2),
startTouchY: event.nativeEvent.locationY.toFixed(2),
});
},
onMoveShouldSetPanResponder: (event, gestureState) => false,
onMoveShouldSetPanResponderCapture: (event, gestureState) => false,
onPanResponderGrant: (event, gestureState) => false,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {
this.setState({
endTouchX: event.nativeEvent.locationX.toFixed(2),
endTouchY: event.nativeEvent.locationY.toFixed(2),
});
},
});
this.setState({
startTouchX: 0,
startTouchY: 0,
endTouchX: 0,
endTouchY: 0,
});
}
render() {
return (
<View style={styles.MainContainer}>
<View style={styles.childView}>
<Svg height={height} width={width} position="absolute">
<Line
x1={this.state.startTouchX}
y1={this.state.startTouchY}
x2={this.state.endTouchX}
y2={this.state.endTouchY}
stroke="red"
strokeWidth="8"
/>
</Svg>
<View
style={{flex: 1, backgroundColor: 'transparent'}}
{...this.panResponder.panHandlers}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer: {
flex: 1,
},
childView: {
flex: 1,
overflow: 'hidden',
},
point: {
height: 22,
width: 22,
marginTop: 5,
position: 'absolute',
borderRadius: 14,
backgroundColor: '#afeeee',
},
});