Im creating a reusable Text component with a onFocus and onBlur animation, but when I put this in a form; the focus and blur event triggers the animation for every Input in the form... can you help me to avoid this behavior?
Here is the code if you need more details, but I think this is very clear
import React, { Component } from 'react';
import { TextInput, View, Text, Animated, StyleSheet } from 'react-native';
const animatedPlaceholder = new Animated.Value(30);
class Input extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
isFocused: false,
textLength: 0
};
}
secureTextEntry = this.props.secureTextEntry || false;
autoCapitalize = this.props.autoCapitalize || 'sentences';
keyboardType = this.props.keyboardType || 'default';
focus = () => {
this.setState({isFocused: true});
Animated.timing(animatedPlaceholder, {
toValue: 0,
duration: 300
}).start();
}
blur = () => {
this.setState({isFocused: false});
Animated.timing(animatedPlaceholder, {
toValue: 30,
duration: 300
}).start();
}
render() {
return(
<View {...this.props}>
<Animated.Text style={
this.state.isFocused ? styles.usedValue : styles.emptyValue
} > {this.props.placeholder} </Animated.Text>
<TextInput
onFocus={this.focus}
onBlur={this.blur}
autoCapitalize={this.autoCapitalize}
secureTextEntry={this.secureTextEntry}
keyboardType={this.keyboardType}
style={
styles.textInput
}
/>
</View>
);
}
}
export default Input;
I didn't quite got your question, but i created a component which animates the place holder when its focused, animated back if value is empty,
check this snack example https://snack.expo.io/#ashwith00/frowning-cookie
Code
import React, { Component } from 'react';
import { TextInput, View, Text, Animated, StyleSheet } from 'react-native';
export default class Input extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
isFocused: false,
textLength: 0,
};
this.animatedPlaceholder = new Animated.Value(0);
}
secureTextEntry = this.props.secureTextEntry || false;
autoCapitalize = this.props.autoCapitalize || 'sentences';
keyboardType = this.props.keyboardType || 'default';
focus = () => {
Animated.timing(this.animatedPlaceholder, {
toValue: -40,
duration: 300,
}).start();
};
blur = () => {
if (!this.props.value) {
Animated.timing(this.animatedPlaceholder, {
toValue: 0,
duration: 300,
}).start();
}
};
render() {
const {value, onChangeText} = this.props;
return (
<View style={[ {
justifyContent: 'center'
}]}>
<Animated.Text
style={{
position: 'absolute',
transform: [{translateY: this.animatedPlaceholder}]
}}>
{' '}
{this.props.placeholder}{' '}
</Animated.Text>
<TextInput
value={value}
onChangeText={onChangeText}
onFocus={this.focus}
onBlur={this.blur}
autoCapitalize={this.autoCapitalize}
secureTextEntry={this.secureTextEntry}
keyboardType={this.keyboardType}
style={styles.textInput}
/>
</View>
);
}
}
const styles = StyleSheet.create({
usedValue: {} ,
emptyValue: {},
textInput: {
alignSelf: 'stretch',
height: 50,
borderWidth: 0.4
}
})
Related
Hello i'm creating a game in react native and i'm stuck because i wan't both players can drag and drop horizontaly an element in same time on the same phone.
I have two components like that:
export class Player1 extends Component{
constructor(props){
super(props);
this.state = {
pan : new Animated.ValueXY()
};
}
componentWillMount(){
this.panResponder = PanResponder.create({
onMoveShouldSetResponderCapture : () => true,
onMoveShouldSetPanResponderCapture : () => true,
onPanResponderGrant : (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove : Animated.event([null,{
dx : this.state.pan.x,
}]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
render(){
return (
<View style={styles.mainContainer}>
{this.renderDraggable()}
</View>
);
}
renderDraggable(){
return (
<View style={styles.draggableContainer}>
<Animated.View
style={[this.state.pan.getLayout(), styles.triangle]}
{...this.panResponder.panHandlers} >
</Animated.View>
</View>
);
}
}
And in my screen i call my components like that:
export default function HomeScreen() {
return (
<View>
<Player1></Player1>
<Player2></Player2>
</View>
);
}
Thanks for your help
I found a solution, i used react-native-gesture-handle like in the directory doubleDraggable of the example: https://kmagiera.github.io/react-native-gesture-handler/docs/example.html
My Code:
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import {
PanGestureHandler,
ScrollView,
State,
} from 'react-native-gesture-handler';
export class Players extends Component {
constructor(props) {
super(props);
this._translateX = new Animated.Value(0);
this._translateY = new Animated.Value(0);
this._lastOffset = { x: 0, y: 0 };
this._onGestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this._translateX,
},
},
],
);
}
_onHandlerStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastOffset.x += event.nativeEvent.translationX;
this._translateX.setOffset(this._lastOffset.x);
this._translateX.setValue(0);
this._translateY.setOffset(this._lastOffset.y);
this._translateY.setValue(0);
}
};
render() {
return (
<PanGestureHandler
{...this.props}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{
transform: [
{ translateX: this._translateX },
{ translateY: this._translateY },
],
},
this.props.boxStyle,
]}
/>
</PanGestureHandler>
);
}
}
export default class Example extends Component {
render() {
return (
<View style={styles.scrollView}>
<DraggableBox />
</View>
);
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
},
box: {
position: 'absolute',
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 25,
borderRightWidth: 25,
borderBottomWidth: 50,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
},
});
And Screen:
<View styles={styles.container}>
<Players boxStyle={styles.player1}></Players>
<Players boxStyle={styles.player2}></Players>
</View>
I have been searching for something similar endlessly for a few days but I couldn't find these demos that react-native-gesture-handler provides. Thanks a lot for posting this here #Lillian Pacaud. Here is the link for several of their demos including the draggable component: https://snack.expo.dev/#adamgrzybowski/react-native-gesture-handler-demo
If you need any simultaneous presses/gesture/drags/etc... your best bet is to use react-native-gesture-handler because the native implementation of all touch/gesture-based components in react native don't allow for simultaneous interactions with each especially for Android.
I made a functional component that does the same thing as the accepted answer. Just pass whatever you want to be draggable as a child under the component. It can handle simultaneous drags as well like the accepted answer on both iOS and Android.
Example of using the draggable component:
import React from 'react';
import { View } from 'react-native';
import { DraggableTest } from '../components/test';
export default function Screen() {
return (
<View style={{ flex: 1 }}>
<DraggableTest>
<View
style={{ width: 150, height: 150, backgroundColor: 'lime' }}
/>
</DraggableTest>
</View>
);
}
The draggable component:
import React, { useRef } from 'react';
import { Animated, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
export function DraggableTest({ children }) {
const pan = useRef(new Animated.ValueXY()).current;
const lastOffset = useRef({ x: 0, y: 0 }).current;
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: pan.x, translationY: pan.y } }],
{ useNativeDriver: false },
);
const onHandlerStateChange = event => {
if (event.nativeEvent.oldState == State.ACTIVE) {
lastOffset.x += event.nativeEvent.translationX;
lastOffset.y += event.nativeEvent.translationY;
pan.setOffset({ x: lastOffset.x, y: lastOffset.y });
pan.setValue({ x: 0, y: 0 });
}
};
return (
<PanGestureHandler
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange}>
<Animated.View style={[pan.getLayout(), styles.animatedView]}>
{children}
</Animated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
animatedView: {
position: 'absolute',
},
});
I create a class component with animation for common button:
import React, { Component } from 'react';
import { Text, TouchableOpacity, StyleSheet, Animated, Easing, View } from 'react-native';
class AnimatedPrimaryButton extends Component {
constructor(props) {
super(props);
this.state = {
toggle: false,
animated: new Animated.Value(0)
}
}
animatedButton = (toggle) => {
this.state.animated.setValue(toggle ? 1 : 0);
Animated.timing(this.state.animated, {
toValue: toggle ? 0 : 1,
duration: 250,
easing: Easing.bounce
}).start();
this.setState({ toggle: !toggle });
}
render() {
const { toggle, animated } = this.state;
const { onPress, disabled, width, height } = this.props;
return (
<TouchableOpacity
disabled={disabled}
onPress={onPress}
style={[styles.buttonStyle, { width, height }]}
>
<Animated.View style={{
// other styles
transform: [{ scale: animated.interpolate({ inputRange: [0, 1], outputRange: [0, 1]})
}
]
}}>
</Animated.View>
<Text style={[styles.textStyle, { fontSize }]}>
{children}
</Text>
</TouchableOpacity>
);
};
}
const styles = StyleSheet.create({
// some styles
});
export default AnimatedPrimaryButton;
I use the create component on other screen like:
import AnimatedPrimaryButton from '../Shared/Button/AnimatedPrimaryButton';
doSomething = () => {
// do something...
}
render() {
return (
<View>
<AnimatedPrimaryButton
onPress={() => this.doSomething()}
width={400}
height={57}
fontSize={20}
backgroundColor={confirmButtonBg}
disabled={disabledConfirmButton}
>
{I18n.t('SIGN_IN_BUTTON')}
</AnimatedPrimaryButton>
</View>
);
}
Now I want to use doSomething function and trigger animatedButton at the same time.
In some conditions my disable will switch true or false, so I try to set the code on my AnimatedPrimaryButton is not working.
onPress={() => !disabled ? this.animatedButton(toggle) : onPress}
It looks like use the props onPress under arrow function won't work.
How to use doSomething and animatedButton function on class component AnimatedPrimaryButton ?
Any help would be appreciated.
in AnimatedPrimaryButton component you can make a onPress function
onPress(){
this.props.onPress();
this. animatedButton();
}
and rest you are sending the doSomething() function correctly on onPress while calling AnimatedPrimaryButton on other screen.
I would like to call a function at the end of the function scrollTo called like that :
scrollTo({y: 0, animated: true})
but by default this function doesn't have a second parameter.
So how can i handle the end of the scroll animation to trigger an other function ?
You can use onMomentumScrollEnd as mentioned in this issue
However if you want more control over your scroll state you can implement smth like this
import React from 'react';
import { StyleSheet, Text, View, ScrollView, Button } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<ScrollView
style={{ marginVertical: 100 }}
ref={this.refScrollView}
onScroll={this.onScroll}
>
<Text style={{ fontSize: 20 }}>
A lot of text here...
</Text>
</ScrollView>
<Button title="Scroll Text" onPress={this.scroll} />
</View>
);
}
componentDidMount() {
this.scrollY = 0;
}
onScroll = ({ nativeEvent }) => {
const { contentOffset } = nativeEvent;
this.scrollY = contentOffset.y;
if (contentOffset.y === this.onScrollEndCallbackTargetOffset) {
this.onScrollEnd()
}
}
onScrollEnd = () => {
alert('Text was scrolled')
}
refScrollView = (scrollView) => {
this.scrollView = scrollView;
}
scroll = () => {
const newScrollY = this.scrollY + 100;
this.scrollView.scrollTo({ y: newScrollY, animated: true });
this.onScrollEndCallbackTargetOffset = newScrollY;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
padding: 20,
},
});
Login component:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableHighlight,
TextInput,
Image
} from 'react-native';
var DisplayData = require('./DisplayData');
export default class Login extends Component{
constructor(props){
super(props)
this.state = {
Latitude: '',
Longitude: '',
}
}
onPressDisplayData() {
this.props.navigator.push({
name: 'DisplayView',
component: DisplayData
});
}
render() {
return (
<View style = {styles.container}>
<Image source={require('./logo.png')}/>
<TextInput
style = {styles.input}
placeholder = 'Latitude'
autoCapitalize = 'none'
onChangeText={(text) => this.setState({Latitude:text})}
/>
<TextInput
style = {styles.input}
placeholder = 'Longitude'
autoCapitalize = 'none'
onChangeText={(text) => this.setState({Longitude:text})}
/>
<TouchableHighlight
style = {styles.submit}
onPress = {() => this.onPressDisplayData()
}
>
<Text>
Submit
</Text>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create ({
container: {
flex: 1,
alignItems: 'center',
justifyContent:'center',
paddingBottom: 40
},
input: {
margin: 15,
height: 40,
borderColor: 'grey',
borderWidth: 1
},
submit: {
backgroundColor: '#FFDD03',
padding: 10
}
})
module.exports = Login;
DisplayData component:
import React, {
Component,
} from 'react';
import {
AppRegistry,
Image,
ListView,
StyleSheet,
Text,
View,
} from 'react-native';
var REQUEST_URL = 'http://api.geonames.org/earthquakesJSON?north='+4+'&south='+-9.9+'&east='+-22.4+'&west='+55.2+'&username=afdsanfd'
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
class DisplayData extends Component {
constructor(props){
super(props)
this.state = {
earthquakes: ds.cloneWithRows([])
};
}
componentDidMount() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => this.setState({ earthquakes: ds.cloneWithRows(responseData.earthquakes) }))
.catch(function(error) {
console.warn(error);
});
}
render() {
return (
<View>
<ListView
dataSource = {this.state.earthquakes}
renderRow={(rowData) => (
<View style={{flex: 1, paddingTop: 10, paddingBottom: 10, borderWidth: 0.5, paddingLeft: 10, borderColor: '#D3D3D3'}}>
<Text style={{fontSize: 25}}>{rowData.src}</Text>
<Text>DateTime: {rowData.datetime}</Text>
<Text>Magnitude: {rowData.magnitude}</Text>
<Text>EqID: {rowData.eqid}</Text>
<Text>Depth: {rowData.depth}</Text>
</View>
)}
/>
</View>
);
}
}
module.exports = DisplayData;
So what I am trying to accomplish through this app is having someone enter latitude and longitude such that an API call (REQUEST_URL) that'd return earthquake info. However, i'm struggling really hard with passing the entered latitude and longitude into the DisplayData component. I've tried things such as creating a global latitude and longitude but none have been working.
So I am rendering a ListView with two columns of items. The following function renders the rows, renderRow(rowData):
For each item, if clicked, I want it to be changed to opacity of 0.5 and if clicked again, want it to be back to clear up the opacity, so what I was thinking is setting it to opacity of 1.0.
I tried the following, but for some reason the opacity is not being updated:
constructor(props){
super(props);
this.state = {
opacity: 1.0,
selected: false,
}
}
renderRow(rowData){
return (
<View style={styles.item}>
<TouchableHighlight onPress={()=>this._selected() underlayColor='transparent'}>
<View style={{opacity:this.state.opacity}}>
<Text>{rowData.firstName}</Text>
<Text>{rowData.lastName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
_selected(){
if(this.state.selected){
console.log('ITEM HAS BEEN UNSELECTED')
this.setState({
opacity: 1.0,
selected: false
})
}else{
console.log('ITEM HAS BEEN SELECTED')
this.setState({
opacity: 0.5,
selected: true
})
}
}
Why isn't the opacity being updated once clicked and rerendering the view inside the TouchableHighlight? Also how can I do so with individual item, with each's 'opacity' and 'selected' states?
**FULL CODE
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
ListView,
Image,
TouchableHighlight,
TouchableOpacity
} from 'react-native';
class Interest extends Component {
constructor(props){
super(props);
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
selected: false,
dataSource: ds.cloneWithRows([
{firstName: 'Johnny', lastName: 'Boy'},
{firstName: 'Shawn', lastName: 'Ke'},
{firstName: 'An', lastName: 'Twon'}
};
}
renderRow(rowData){
return (
<View style={this.state.selected ? styles.transItem : styles.opacItem}>
<TouchableHighlight
onPress={ () => { this.setState({selected: !this.state.selected})}} underlayColor='transparent'>
<View>
<Text>{rowData.firstName}</Text>
<Text>{rowData.lastName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
render() {
return (
<View style={styles.container}>
<ListView contentContainerStyle={styles.list} dataSource={this.state.dataSource} renderRow={this.renderRow.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
flexDirection: 'row',
flexWrap: 'wrap',
},
opacItem: {
margin: 15,
width: 155,
height: 175,
opacity: 1.0
},
transItem: {
margin: 15,
width: 155,
height: 175,
opacity: 0.5
}
});
export default Interest
I think you set the selected state that is not intended.
In the code above, the selected is a state for the entire app - not just for a selected line. To select a single line, you should keep a selected state for each item. For cleaner code it is recommended to have another module for the item, and keep the state there and not in the parent module.
Code:
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
ListView,
Image,
TouchableHighlight,
TouchableOpacity
} from 'react-native';
class Interest extends Component {
constructor(){
super();
this._renderRow = this._renderRow.bind(this);
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
{firstName: 'Johnny', lastName: 'Boy'},
{firstName: 'Shawn', lastName: 'Ke'},
{firstName: 'An', lastName: 'Twon'}
])};
}
render() {
return (
<View style={styles.container}>
<ListView
contentContainerStyle={styles.list}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
/>
</View>
);
}
_renderRow(rowData) {
return(
<InterestItem rowData={rowData} />);
}
}
class InterestItem extends Component {
constructor(props){
super(props);
this.state = {
selected: false
}
}
render(){
const { rowData } = this.props;
return (
<View style={this.state.selected ? styles.transItem : styles.opacItem}>
<TouchableHighlight
onPress={ () => { this.setState({selected: !this.state.selected})}}
underlayColor='transparent'
>
<View>
<Text>{rowData.firstName}</Text>
<Text>{rowData.lastName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
flexDirection: 'row',
flexWrap: 'wrap',
},
opacItem: {
margin: 15,
width: 155,
height: 175,
opacity: 1.0
},
transItem: {
margin: 15,
width: 155,
height: 175,
opacity: 0.5
}
});
export default Interest