How do I create a flippable card in react-native? - javascript

Specifically, how do I make one side of the card disappear once flipped. I am using Android, so backfaceVisibility did not help solve my problem. I used animate to remove the object via opacity, but the problem is that once I removed one side of the card, the 'invisible' buttons still worked, and the buttons on the current side of the card did not. I tried playing around with zIndex and 'disabled' in Pressable, and I am unsure how to fix this. I have attached source code below:
let animatedValue = new Animated.Value(0)
let val = 0;
animatedValue.addListener(({ value }) => {
val = value;
})
let frontOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0]
})
let backOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1]
})
let frontInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
})
let backInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
const frontAnimatedStyle = {
transform: [
{ rotateY: frontInterpolate }
],
opacity: frontOpacity,
}
const backAnimatedStyle = {
transform: [
{ rotateY: backInterpolate }
],
opacity: backOpacity,
}
let isFront = true;
const flipCard = () => {
isFront = !isFront;
if (val >= 90) {
Animated.spring(animatedValue, {
toValue:0,
friction: 8,
tension: 10,
useNativeDriver: true,
}).start();
} else {
Animated.spring(animatedValue, {
toValue:180,
friction: 8,
tension: 10,
useNativeDriver: true,
}).start();
}
}
const getFrontZ = () => {
return isFront ? 1 : 0;
}
return(
<View style={styles.container}>
<Animated.View
style={[styles.cardStyle,
styles.frontCardStyle,
frontAnimatedStyle,
{height: cardHeight, zIndex: -1,}]}>
<Pressable
onPress={() => console.log('Open Stack')}
style={{height: '100%', width: '100%'}}>
<Pressable
onPress={() => flipCard()}
style={{backgroundColor: 'blue', height: 50, width: 50,}}
>
</Pressable>
</Pressable>
</Animated.View>
<Animated.View
style={[styles.cardStyle,
styles.backCardStyle,
backAnimatedStyle,
{height: cardHeight, zIndex: 0}]}>
<Pressable
onPress={() => flipCard()}
style={{height: '100%', width: '100%'}}
>
</Pressable>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
cardStyle: {
height: '100%',
width: '100%',
backfaceVisibility: 'hidden',
},
frontCardStyle: {
backgroundColor: 'red',
},
backCardStyle: {
backgroundColor: 'purple',
position: 'absolute',
top: 0,
}
})

try this library react-native-card-flip
and import this in your file and use like this
import CardFlip from 'react-native-card-flip';
<CardFlip style={styles.cardContainer} ref={(card) => this.card = card} >
<TouchableOpacity style={styles.card} onPress={() => this.card.flip()} >
<Text>AB</Text></TouchableOpacity>
<TouchableOpacity style={styles.card} onPress={() => this.card.flip()} >
<Text>CD</Text></TouchableOpacity>
</CardFlip>

Related

How can I use the rotation gesture handler to rotate an animated view in react native about a specific point (not its center)?

I have used the below model to make a handler rotate but cannot find a way to manipulate it as to make the handle rotate from where it begins instead of its center. As well, I am struggling to make the rotation occur with only one finger instead of two.
I think maybe the answer lies in the withAnchorPoint library but Iā€™m not sure of how to apply it.
Requiring assistance šŸ™šŸ¾
import React from 'react';
import {Animated, StatusBar, Image, ImageBackground, Dimensions, StyleSheet, View, Text} from "react-native";
import { PanGestureHandler, PinchGestureHandler, State, RotationGestureHandler } from 'react-native-gesture-handler';
//import { withAnchorPoint } from 'react-native-anchor-point';
import { USE_NATIVE_DRIVER } from '../../config';
const {width} = Dimensions.get('screen');
const SIZE = width * .9;
export default class MovableClock extends React.Component {
panRef = React.createRef();
rotationRef = React.createRef();
pinchRef = React.createRef();
constructor(props) {
super(props);
/* Pinching */
this._baseScale = new Animated.Value(1);
this._pinchScale = new Animated.Value(1);
this._scale = Animated.multiply(this._baseScale, this._pinchScale);
this._lastScale = 1;
this._onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this._pinchScale } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
/* Rotation */
this._rotate = new Animated.Value(0);
this._rotateStr = this._rotate.interpolate({
inputRange: [-100, 100],
outputRange: ['-100rad', '100rad'],
});
this._lastRotate = 0;
this._onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this._rotate } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
/* Tilt */
this._tilt = new Animated.Value(0);
this._tiltStr = this._tilt.interpolate({
inputRange: [-501, -500, 0, 1],
outputRange: ['1rad', '1rad', '0rad', '0rad'],
});
this._lastTilt = 0;
this._onTiltGestureEvent = Animated.event(
[{ nativeEvent: { translationY: this._tilt } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
}
_onRotateHandlerStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastRotate += event.nativeEvent.rotation;
this._rotate.setOffset(this._lastRotate);
this._rotate.setValue(0);
}
};
_onPinchHandlerStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastScale *= event.nativeEvent.scale;
this._baseScale.setValue(this._lastScale);
this._pinchScale.setValue(1);
}
};
_onTiltGestureStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastTilt += event.nativeEvent.translationY;
this._tilt.setOffset(this._lastTilt);
this._tilt.setValue(0);
}
};
render() {
return (
<View style={styles.background}>
<StatusBar hidden={true}/>
<View style={[styles.bigQuadran]}>
</View>
<Image style={styles.numbers}
source={require("../assets/nobgfreeclock2.png")}/>
<View style={[styles.mediumQuadran]}/>
<PanGestureHandler
ref={this.panRef}
onGestureEvent={this._onTiltGestureEvent}
onHandlerStateChange={this._onTiltGestureStateChange}
minDist={10}
minPointers={2}
maxPointers={2}
avgTouches>
<Animated.View style={[styles.mover
//{transform: [{ translateX: this.translateX, }, { translateY: this.translateY}]}
]}>
<RotationGestureHandler
ref={this.rotationRef}
simultaneousHandlers={this.pinchRef}
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}
>
<Animated.View style={[styles.mover]}>
<PinchGestureHandler
ref={this.pinchRef}
simultaneousHandlers={this.rotationRef}
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}
>
<Animated.View style={[styles.hours,
{transform: [
{ perspective: 200 },
{ scale: this._scale },
{ rotate: this._rotateStr },
{ rotateX: this._tiltStr },
]},
]}/>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
{/*
<PanGestureHandler onGestureEvent={this.onPanGestureEvent}>
<Animated.View style={[styles.mover, {
transform: [{ translateX: this.translateX, }, { translateY: this.translateY}]}]}>
<View style={styles.minutes}/>
</Animated.View>
</PanGestureHandler>
<PanGestureHandler onGestureEvent={this.onPanGestureEvent}>
<Animated.View style={[styles.mover, {
transform: [{ translateX: this.translateX, }, { translateY: this.translateY}]}]}>
<View style={styles.seconds}/>
</Animated.View>
</PanGestureHandler>
*/}
<View style={[styles.smallQuadran]}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'whitesmoke',
alignItems: 'center',
justifyContent: 'center',
},
background: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent'
},
mover: {
position: 'absolute',
width: SIZE,
height: SIZE,
borderRadius: SIZE/2,
alignItems: 'center',
justifyContent: 'flex-start',
top: 68
},
hours: {
backgroundColor: 'black',
height: '20%',
marginTop: '30%',
width: 4.5,
borderRadius: 4.5
},
minutes: {
backgroundColor: 'black',
height: '35%',
marginTop: '15%',
width: 3.5,
borderRadius: 3.5
},
seconds: {
backgroundColor: 'rgba(227, 71, 134, 1)',
height: '35%',
marginTop: '15%',
width: 2,
borderRadius: 2
},
bigQuadran: {
width: SIZE * 0.815,
height: SIZE * 0.815,
borderRadius: SIZE * 0.40,
backgroundColor: 'ghostwhite',
borderColor: 'black',
borderWidth: SIZE * 0.030,
position: 'absolute',
top: 100
},
smallQuadran: {
width: 10,
height: 10,
borderRadius: 5,
position: 'absolute',
backgroundColor: 'rgba(227, 71, 134, 1)',
top: 250
},
numbers: {
width: SIZE * 0.78,
height: SIZE * 0.78,
position: 'absolute',
top: 107,
borderRadius: SIZE * 4
}
})

Custom Textinput shows border around itself in IOS - React Native

I've created a custom text input component for my react native app. It has no issues in the android build. But In IOS, the custom component draws a border around its view.
This is how it looks in ios. You can see a border around it.
I want it took to look like this in IOS without a square border
This is my custom component (MyInput.js)
import React, {useState, useRef, useEffect} from 'react';
import {
Animated,
Text,
ActivityIndicator,
TouchableOpacity,
} from 'react-native';
const {View, TextInput, StyleSheet} = require('react-native');
const MyInput = (props) => {
const [hide, makeHide] = useState(false);
const [phAanimatedValue] = useState(new Animated.Value(0));
const [labelAanimatedValue] = useState(new Animated.Value(0));
const [overflow, setOverflow] = useState('hidden');
const movePlaceHolderLeft = phAanimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -10],
});
const moveLabelRight = labelAanimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-10, 0],
});
const animatePlaceHolder = (v, hide) => {
Animated.timing(phAanimatedValue, {
toValue: v,
duration: 50,
useNativeDriver: true,
}).start(() => {
makeHide(hide);
animateLabel(v);
});
};
const animateLabel = (v) => {
setOverflow('visible');
Animated.timing(labelAanimatedValue, {
toValue: v,
duration: 50,
useNativeDriver: true,
}).start();
};
return (
<View
style={[
styles.input,
{
overflow: overflow,
borderColor:props.error?"#ff0033":"#e5deda",
},
]}>
<TextInput
onFocus={() => {
animatePlaceHolder(1, true);
}}
ref={props.cref}
value={props.value}
onChangeText={(v) => props.onChangeText(v)}
style={{paddingStart: 25}}
onBlur={() => {
if (props.value.length > 0) {
makeHide(true);
} else {
animatePlaceHolder(0, false);
}
}}
/>
{!hide ? (
<Animated.Text
onPress={() => props.cref.current.focus()}
style={{
position: 'absolute',
transform: [{translateX: movePlaceHolderLeft}],
height: '100%',
fontFamily: 'Muli-Regular',
width: '100%',
marginStart: 20,
paddingVertical:15,
color: props.error ? '#ff0033' : '#a6a6a6',
textAlignVertical: 'center',
}}>
{props.label}
</Animated.Text>
) : (
<Animated.Text
onPress={() => props.cref.current.focus()}
style={{
position: 'absolute',
transform: [{translateX: moveLabelRight}],
marginTop: -10,
zIndex:1000,
backgroundColor: 'white',
color:props.error?'red':'#e5deda',
marginStart: 20,
fontFamily: 'Muli-Regular',
paddingHorizontal: 5,
textAlignVertical: 'top',
}}>
{props.label}
</Animated.Text>
)}
</View>
);
};
const styles = StyleSheet.create({
input: {
borderWidth: 2,
borderColor: '#e5deda',
backgroundColor: 'white',
fontSize: 14,
color: '#353839',
paddingVertical:15,
marginHorizontal: 20,
lineHeight: 18,
fontFamily: 'Muli-Regular',
fontWeight: '600',
borderRadius: 100,
},
});
export default MyInput;
This is How I Render:
<MyInput
value={value}
onChangeText={(v)=>
{
setValue(v)
setError(true)
}}
cref={postalRef}
error={error}
label="Postal Code"
/>
Is there any solution for this or is this a bug?

React Native Animated - Card Won't flip back

I've been messing around with React Native and I've tried to create a simple card that flips around on press (back and forth):
I had it working as a class component but now I've tried to refactor to a functional component and its only flipping one way but not back on second press.
Can someone advise me what I've missed? :/
I tried using useState and useEffect for the value and animated value etc, but no dice...
import React from 'react';
import { View, StyleSheet, Animated, TouchableWithoutFeedback, Text } from 'react-native';
import TabBarIcon from '../components/TabBarIcon';
const App = () => {
let animatedValue = new Animated.Value(0);
let value = 0;
animatedValue.addListener(({ value }) => {
this.value = value;
})
let frontInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
})
let backInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
let frontOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0]
});
let backOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1]
});
let elevationFront = animatedValue.interpolate({
inputRange: [0, 25],
outputRange: [10, 0]
})
let elevationBack = animatedValue.interpolate({
inputRange: [155, 180],
outputRange: [0, 10]
})
const flipCard = () => {
if (value >= 90) {
Animated.spring(animatedValue,{
toValue: 0,
friction: 8,
tension: 10
}).start();
} else {
Animated.spring(animatedValue,{
toValue: 180,
friction: 8,
tension: 10
}).start();
}
}
const frontAnimatedStyle = {
transform: [{ rotateY: frontInterpolate}]
}
const backAnimatedStyle = {
transform: [{ rotateY: backInterpolate }]
}
return (
<TouchableWithoutFeedback onPress={() => flipCard()} >
<View>
<Animated.View style={[frontAnimatedStyle, styles.paperFront,{elevation: elevationFront}, {opacity: frontOpacity}]}>
<Text style={{fontSize: 20,paddingTop: 8, paddingLeft: 8, color: 'black',lineHeight: 20}}>
Title Front {value} - <Text style={{fontSize: 8}}>KPI</Text>
</Text>
<View style={{position: "absolute", paddingTop: 3, right: 8}}>
<TabBarIcon style={{color: "black"}} name={"md-more"} />
</View>
</Animated.View>
<Animated.View style={[backAnimatedStyle, styles.paperBack, {elevation: elevationBack}, {opacity: backOpacity}]}>
<Text>Back title {value}</Text>
</Animated.View>
</View>
</TouchableWithoutFeedback>
);
}
const styles = StyleSheet.create({
paperFront : {
marginHorizontal: 15,
backgroundColor: "white",
minHeight: 200,
borderRadius: 5,
marginBottom: 15,
},
paperBack : {
top: -215,
marginHorizontal: 15,
backgroundColor: "white",
minHeight: 200,
borderRadius: 5,
marginBottom: 15,
}
});
export default App
You need to call your "value" not "this.value", but since you already got "value" parameter I suggest you change the variable name:
let val = 0;
animatedValue.addListener(({ value}) => {
val = value;
});
P.S: don't forget to change your calls to "value" variable into "val"
fix it
if (this.value >= 90) {
in expo

translate on Y using SectionList and header view creating empty space on scroll - react native

i'm new in react native and Animated API, so what i'm trying to do a simple parallax header and Animated SectionList like this
my code is
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {
fullName: '',
scrollEnabled: true,
scrollY: new Animated.Value(0),
}
}
_renderSectionHeader(section){
return <View style={[styles.SectionHeaderViewStyle]}><Text style={styles.SectionHeaderStyle}> {section.title} </Text></View>;
}
_renderItem(item){
return <View><TextFieldValidate /></View>;
}
_onScroll(event) {
// console.log(event.nativeEvent.contentOffset.y);
}
render() {
const backgroundScrollY = this.state.scrollY.interpolate({
inputRange: [0, 224],
outputRange: [0, -170],
extrapolate: 'clamp',
});
const listScrollY = this.state.scrollY.interpolate({
inputRange: [0, 224],
outputRange: [0, -170],
extrapolate: 'clamp',
});
const infoOpacity = this.state.scrollY.interpolate({
inputRange: [0, 0.5, 150],
outputRange: [1, 1, 0],
extrapolate: 'clamp',
});
const AnimatedSectionList = Animated.createAnimatedComponent(SectionList)
return (
<View style={{flex: 1}}>
<View style={{position: 'relative', height: 224, justifyContent: 'center', alignItems: 'center'}}>
<Animated.Image source={Images.profileEdit} style={[styles.background, {transform: [{translateY: backgroundScrollY}]}]} blurRadius={1.5}/>
<Text style={styles.fullNameHeader}>Steve Smith</Text>
<Animated.View style={{opacity: infoOpacity, position: 'relative', justifyContent: 'center', alignItems: 'center'}}>
<AnimatedCircularProgress size={76} width={4} fill={50} rotation={0} tintColor={Colors.cyan} backgroundColor={Colors.white}>
{
(fill) => (
<Image source={require('./img/details_icon.png')}/>
)
}
</AnimatedCircularProgress>
<Text style={styles.communityNameHeader}>Jumeirah Village Circle</Text>
<Text style={styles.profileCompletionHeader}>50% Profile Completion</Text>
</Animated.View>
</View>
<AnimatedSectionList
bounces={false}
scrollEnabled={this.state.scrollEnabled}
style={[{position: 'relative'}, {transform: [{translateY: listScrollY}]}]}
onScroll={
Animated.event(
[{nativeEvent: {contentOffset: {y: (this.state.scrollY)}}}],
{useNativeDriver: true, listener: this._onScroll.bind(this)}
)
}
sections={[
{title: 'MY DETAILS', data: myDetailsFields},
{title: 'MY COMMUNITY', data: myCommunity},
{title: 'MY FAMILY', data: myFamily},
]}
renderSectionHeader={({section}) => this._renderSectionHeader(section)}
renderItem={({item}) => this._renderItem(item)}
keyExtractor={(item, index) => index}
stickySectionHeadersEnabled={true}
/>
</View>
);
}
}
please any help to remove the bottom white space when scrolling i tried paddingBottom:950 in the "AnimatedSectionList" style but it missed up the keyboard viewing and scrolling
style={[{position: 'relative'}, {paddingBottom:950}, {transform: [{translateY: listScrollY}]}]}
AFTER ADDING marginBottom
if any one interested in the Answer i just added "marginBottom:-170" to the AnimatedSectionList instead of "paddingBottom:950"
style={[{position: 'relative'}, {marginBottom:-170}, {backgroundColor:'blue'}, {transform: [{translateY: listScrollY}]}]}

Swipe card on button click using panresponder

I am trying to make a tinder-like app component to swipe cards, it is working fine when I swipe using a finger, however, I want to achieve the same on button click as well. I have used Panresponder so far for creating swipe action, below is my code
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center', alignItems: 'center', backgroundColor: 'white' }}>
<View style={{
height: Dimensions.get('window').height * .75,
borderRadius: 5,
width: Dimensions.get('window').width * .75,
}}>
{
this.state.ARRAY.map((item, index) => {
this.position = new Animated.ValueXY()
this.rotate = this.position.x.interpolate({
inputRange: [-CONST_WIDTH / 2, 0, CONST_WIDTH / 2],
outputRange: ['-30deg', '0deg', '10deg'],
extrapolate: 'clamp'
})
this.rotateAndTranslate = {
transform: [{
rotate: this.rotate
},
...this.position.getTranslateTransform()
]
}
this.isPanDisabled = true
this.PanResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => this.isPanDisabled,
onPanResponderMove: (evt, gestureState) => {
this.position.setValue({ x: gestureState.dx, y: 0 })
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.dx > 120) {
this.isPanDisabled = true
// console.log('dxgreaterThan120', gestureState.dx)
Animated.spring(this.position, {
toValue: { x: CONST_WIDTH + 100, y: 0 },
useNativeDriver: Platform.OS === 'android'
}).start(() => {
let modified = this.state.ARRAY.filter((x, ind) => { return ind != index })
console.log('modified', modified)
this.setState({ ARRAY: modified })
})
}
else if (gestureState.dx < -120) {
this.isPanDisabled = true
Animated.spring(this.position, {
toValue: { x: -CONST_WIDTH - 150, y: 0 },
useNativeDriver: Platform.OS === 'android'
}).start(() => {
let modified = this.state.ARRAY.filter((x, ind) => { return ind != index })
console.log('modified', modified)
this.setState({ ARRAY: modified })
})
}
else if (gestureState.dy < -120 || gestureState.dy > 120) {
this.isPanDisabled = false
}
else {
this.isPanDisabled = true
Animated.spring(this.position, {
toValue: { x: 0, y: 0 },
friction: 4,
useNativeDriver: Platform.OS === 'android'
}).start()
}
}
})
return (
<Animated.View
key={index}
{...this.PanResponder.panHandlers}
style={[this.rotateAndTranslate, {
borderRadius: 5,
backgroundColor: color[index],
position: 'absolute',
top: 0, bottom: 0,
left: 0, right: 0,
}]}>
<Animated.ScrollView
onScrollEndDrag={() => {
this.isPanDisabled = true
}}>
<View style={{ flex: 1, flexGrow: 1 }}>
{
ARRAY_SUUMB.map((item, index) => {
return (
<Text style={{
padding: 16,
color: 'white'
}}>
Swiper
</Text>
)
})
}
</View>
</Animated.ScrollView>
</Animated.View>
)
})
}
</View>
<View style={{ justifyContent: 'center', flexDirection: 'row', alignContent: 'center', alignItems: 'center' }}>
<TouchableOpacity
onPress={() => this.swipeLeft()}
style={{
justifyContent: 'center',
height: 60,
width: 60,
borderRadius: 60 >> 1,
backgroundColor: 'gray',
alignContent: 'center',
alignItems: 'center'
}}>
<Text>Left</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.swipeRight()}
style={{
justifyContent: 'center',
height: 60, width: 60, borderRadius: 60 >> 1,
backgroundColor: 'gray',
alignContent: 'center', alignItems: 'center'
}}>
<Text>Right</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
)
}
I want to make a function this.swipeRight() which I can swipe the card just like as if I am swiping using finger, I have tried creating a similar function but it just removes the top card instantly and afterwards it stops working
swipeRight = () => {
this.position.setValue({ x: Dimensions.get('screen').width * 1.5, y: 0 }, () => {
this.isPanDisabled = true
Animated.spring(this.position, {
toValue: { x: CONST_WIDTH + 100, y: 0 },
useNativeDriver: Platform.OS === 'android'
}).start()
let modified = this.state.ARRAY.splice(0, -1)
console.log('modified', modified)
this.setState({ ARRAY: modified })
})
}
Any help would be appreciated, I have the panresponder inside a map function

Categories