Related
The app I'm working on is a multi-game app (a bird game, mole game, and minesweeper). The start screen is the menu. It gives me a cycle warning on this screen. When I tap to start the mole game, it plays naturally, but the moment I press the back button to go back to the menu, I receive errors: "Can't perform a React state update on an unmounted component" error follow by continuous "TYPE ERORR: null is not an object".
Screenshots:
Cycle warning
Null is not an object
Node.js errors
Mole Game's App.js
import {
View,
StyleSheet,
Image,
SafeAreaView,
Text,
TouchableWithoutFeedback
} from 'react-native';
import Images from './assets/Images';
import Constants from './Constants';
import Mole from './Mole';
import GameOver from './GameOver';
import Clear from './Clear';
import Pause from './Pause';
const DEFAULT_TIME = 20;
const DEFAULT_STATE = {
level: 1,
score: 0,
time: DEFAULT_TIME,
cleared: false,
paused: false,
gameover: false,
health: 100
}
export default class MoleGame extends Component {
constructor(props) {
super(props);
this.moles = [];
this.state = DEFAULT_STATE;
this.molesPopping = 0;
this.interval = null;
this.timeInterval = null;
}
componentDidMount = () => {
this.setupTicks(DEFAULT_STATE, this.pause);
}
setupTicks = () => {
let speed = 750 - (this.state.level * 50);
if (speed < 350) {
speed = 350;
}
this.interval = setInterval(this.popRandomMole, speed);
this.timeInterval = setInterval(this.timerTick, 1000);
}
reset = () => {
this.molesPopping = 0;
this.setState(DEFAULT_STATE, this.setupTicks)
}
pause = () => {
if (this.interval) clearInterval(this.interval);
if (this.timeInterval) clearInterval(this.timeInterval);
this.setState({
paused: true
});
}
resume = () => {
this.molesPopping = 0;
this.setState({
paused: false
}, this.setupTicks);
}
nextLevel = () => {
this.molesPopping = 0;
this.setState({
level: this.state.level + 1,
cleared: false,
gameover: false,
time: DEFAULT_TIME
}, this.setupTicks)
}
timerTick = () => {
if (this.state.time === 0) {
clearInterval(this.interval);
clearInterval(this.timeInterval);
this.setState({
cleared: true
})
} else {
this.setState({
time: this.state.time - 1
})
}
}
gameOver = () => {
clearInterval(this.interval);
clearInterval(this.timerInterval);
this.setState({
gameover: true
})
}
onDamage = () => {
if (this.state.cleared || this.state.gameOver || this.state.paused) {
return;
}
let targetHealth = this.state.health - 10 < 0 ? 0 : this.state.health - 20;
this.setState({
health: targetHealth
});
if (targetHealth <= 0) {
this.gameOver();
}
}
onHeal = () => {
let targetHealth = this.state.health + 10 > 100 ? 100 : this.state.health + 10
this.setState({
health: targetHealth
})
}
onScore = () => {
this.setState({
score: this.state.score + 1
})
}
randomBetween = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
onFinishPopping = (index) => {
this.molesPopping -= 1;
}
popRandomMole = () => {
if (this.moles.length != 12) {
return;
}
let randomIndex = this.randomBetween(0, 11);
if (!this.moles[randomIndex].isPopping && this.molesPopping < 3) {
this.molesPopping += 1;
this.moles[randomIndex].pop();
}
}
render() {
let healthBarWidth = (Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60 - Constants.XR * 6) * this.state.health / 100;
return (
<View style={styles.container}>
<Image style={styles.backgroundImage} resizeMode="stretch" source={Images.background} />
<View style={styles.topPanel}>
<SafeAreaView>
<View style={styles.statsContainer}>
<View style={styles.stats}>
<View style={styles.levelContainer}>
<Text style={styles.levelTitle}>Level</Text>
<Text style={styles.levelNumber}>{this.state.level}</Text>
</View>
</View>
<View style={styles.stats}>
<View style={styles.timeBar}>
<Text style={styles.timeNumber}>{this.state.time}</Text>
</View>
<Image style={styles.timeIcon} resizeMode="stretch" source={Images.timeIcon} />
</View>
<View style={styles.stats}>
<View style={styles.scoreBar}>
<Text style={styles.scoreNumber}>{this.state.score}</Text>
</View>
<Image style={styles.scoreIcon} resizeMode="stretch" source={Images.scoreIcon} />
</View>
<View style={styles.stats}>
<TouchableWithoutFeedback onPress={this.pause}>
<View style={styles.pauseButton}>
<Image style={styles.pauseButtonIcon} resizeMode="stretch" source={Images.pauseIcon} />
</View>
</TouchableWithoutFeedback>
</View>
</View>
<View style={styles.healthBarContainer}>
<View style={styles.healthBar}>
<View style={[styles.healthBarInner, { width: healthBarWidth }]} />
</View>
<Image style={styles.healthIcon} resizeMode="stretch" source={Images.healthIcon} />
</View>
</SafeAreaView>
</View>
<View style={styles.playArea}>
{Array.apply(null, Array(4)).map((el, rowIdx) => {
return (
<View style={styles.playRow} key={rowIdx}>
{Array.apply(null, Array(3)).map((el, colIdx) => {
let moleIdx = (rowIdx * 3) + colIdx;
return (
<View style={styles.playCell} key={colIdx}>
<Mole
index={moleIdx}
onDamage={this.onDamage}
onHeal={this.onHeal}
onFinishPopping={this.onFinishPopping}
onScore={this.onScore}
ref={(ref) => { this.moles[moleIdx] = ref }}
/>
</View>
)
})}
</View>
)
})}
</View>
{this.state.cleared && <Clear onReset={this.reset} onNextLevel={this.nextLevel} level={this.state.level} score={this.state.score} />}
{this.state.gameover && <GameOver onReset={this.reset} level={this.state.level} score={this.state.score} />}
{this.state.paused && <Pause onReset={this.reset} onResume={this.resume} />}
</View>
)
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column'
},
backgroundImage: {
width: Constants.MAX_WIDTH,
height: Constants.MAX_HEIGHT,
position: 'absolute'
},
topPanel: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: Constants.YR * 250,
flexDirection: 'column'
},
statsContainer: {
width: Constants.MAX_WIDTH,
height: Constants.YR * 120,
flexDirection: 'row'
},
stats: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
pauseButton: {
width: Constants.YR * 50,
height: Constants.YR * 50,
backgroundColor: 'black',
borderColor: 'white',
borderWidth: 3,
borderRadius: 10,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
pauseButtonIcon: {
width: Constants.YR * 25,
height: Constants.YR * 25,
},
levelContainer: {
width: Constants.YR * 80,
height: Constants.YR * 80,
backgroundColor: '#ff1a1a',
borderColor: 'white',
borderWidth: 3,
borderRadius: 10,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
levelTitle: {
fontSize: 21,
color: 'white',
fontFamily: 'LilitaOne'
},
levelNumber: {
fontSize: 17,
color: 'white',
fontFamily: 'LilitaOne'
},
scoreIcon: {
position: 'absolute',
left: 0,
width: Constants.YR * 40,
height: Constants.YR * 40,
},
scoreBar: {
height: Constants.YR * 25,
position: 'absolute',
left: 20,
right: 5,
backgroundColor: 'white',
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center'
},
scoreNumber: {
fontSize: 17,
color: 'black',
fontFamily: 'LilitaOne',
},
timeIcon: {
position: 'absolute',
left: 0,
width: Constants.YR * 40,
height: Constants.YR * 40,
},
timeBar: {
height: Constants.YR * 25,
position: 'absolute',
left: 20,
right: 5,
backgroundColor: 'white',
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center'
},
timeNumber: {
fontSize: 17,
color: 'black',
fontFamily: 'LilitaOne',
},
healthBarContainer: {
height: Constants.YR * 40,
width: Constants.MAX_WIDTH - Constants.XR * 120,
marginLeft: Constants.XR * 60
},
healthIcon: {
position: 'absolute',
top: 0,
left: 0,
width: Constants.YR * 46,
height: Constants.YR * 40,
},
healthBar: {
height: Constants.YR * 20,
width: Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60,
marginLeft: Constants.XR * 40,
marginTop: Constants.YR * 10,
backgroundColor: 'white',
borderRadius: Constants.YR * 10
},
healthBarInner: {
position: 'absolute',
backgroundColor: '#ff1a1a',
left: Constants.XR * 3,
top: Constants.YR * 3,
bottom: Constants.YR * 3,
borderRadius: Constants.YR * 8
},
playArea: {
width: Constants.MAX_WIDTH,
marginTop: Constants.YR * 250,
height: Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112,
flexDirection: 'column',
},
playRow: {
height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
width: Constants.MAX_WIDTH,
flexDirection: 'row',
},
playCell: {
width: Constants.MAX_WIDTH / 3,
height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
alignItems: 'center'
}
});
Mole.js
import { View, StyleSheet, Button, Image, TouchableWithoutFeedback } from 'react-native';
import Images from './assets/Images';
import SpriteSheet from 'rn-sprite-sheet';
//import Constants from './Constants';
export default class Mole extends Component {
constructor (props) {
super(props);
this.mole = null;
this.actionTimeout = null;
this.isPopping = false;
this.isWacked = false;
this.isHealing = false;
this.isAttacking = false;
this.isFeisty = false;
}
pop =()=>{
this.isWacked = false;
this.isPopping = true;
this.isAttacking = false;
this.isFeisty = Math.random() < 0.4;
if(!this.isFeisty){
this.isHealing = Math.random() < 0.12;
}
if(this.isHealing){
this.mole.play({
type: "heal",
onFinish : ()=>{
this.actionTimeout = setTimeout(()=>{
this.mole.play({
type : "hide",
fps: 24,
onFinish: ()=>{
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}, 1000);
}
})
}
else{
this.mole.play({
type : "appear",
fps: 24,
onFinish: ()=>{
if (this.isFeisty){
this.actionTimeout = setTimeout(() => {
this.isAttacking = true;
this.props.onDamage();
this.mole.play({
type: "attack",
fps: 12,
onFinish: () => {
this.mole.play({
type: "hide",
fps: 24,
onFinish: () => {
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}
})
}, 1000)
}
else{
this.actionTimeout = setTimeout(()=>{
this.mole.play({
type : "hide",
fps: 24,
onFinish: ()=>{
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}, 1000);
}
}
})
}
}
whack = ()=>{
if(!this.isPopping || this.isWacked || this.isAttacking){
return;
}
if (this.actionTimeout){
clearTimeout(this.actionTimeout);
}
this.isWacked = true;
this.props.onScore ();
if( this.isHealing){
this.props.onHeal();
}
this.mole.play({
type: "dizzy",
fps: 24,
onFinish: () => {
this.mole.play({
type: "faint",
fps: 24,
onFinish: () => {
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}
})
}
render() {
return (
<View style= {styles.container}>
<SpriteSheet
ref= {ref=> {this.mole = ref}}
source = {Images.sprites}
columns = {6}
rows = {8}
width = {100}
animations = {{
idle: [0],
appear: [1,2,3,4],
hide: [4,3,2,1,0],
dizzy : [36,37,38],
faint: [42,43,44,0],
attack: [11,12,13,14,15,16],
heal: [24,25,26,27,28,29,30,31,32,33]
}} />
<TouchableWithoutFeedback onPress= {this.whack} style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }}>
<View style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }} />
</TouchableWithoutFeedback>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
})
index.js (Navigation)
import React from "react";
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import MoleGame from './Game1/App.js'
import BirdGame from './Game2/App.js'
import HomeScreen from './Game3/screens/HomeScreen.js'
import GameScreen from './Game3/screens/GameScreen.js'
import Home from './Home.js'
const Stack = createStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false, gestureEnabled: false, }} >
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="MoleGame" component={MoleGame} />
<Stack.Screen name="BirdGame" component={BirdGame} />
<Stack.Screen name="MineSweeperHome" component={HomeScreen} />
<Stack.Screen name="MineSweeperGame" component={GameScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
Home.js
import { View, Text, TouchableOpacity, Dimensions, StyleSheet } from 'react-native';
const GameButton = ({ label, onPress, style }) => {
return (
<TouchableOpacity
style={style}
onPress={onPress}
>
<Text style={styles.text}>{label}</Text>
</TouchableOpacity>
);
};
export default Home = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={styles.welcome}>
<Text style={styles.text}> Welcome! </Text>
</View>
<View style={styles.buttonContainer}>
<GameButton
label={'Play Bird Game'}
onPress={() => navigation.navigate('BirdGame')}
style={styles.gameButton}
/>
<GameButton
label={'Play Mole Game'}
onPress={() => navigation.navigate('MoleGame')}
style={{ ...styles.gameButton, backgroundColor: 'green' }}
/>
<GameButton
label={'Play MineSweeper Game'}
onPress={() => navigation.navigate('MineSweeperHome')}
style={{ ...styles.gameButton, backgroundColor: 'grey' }}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFCB43'
},
welcome: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
marginTop: 80,
},
buttonContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-evenly'
},
gameButton: {
height: 70,
width: Dimensions.get("screen").width / 1.4,
backgroundColor: 'red',
borderColor: 'red',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 20,
borderWidth: 2,
borderColor: 'black',
},
text: {
fontSize: 22,
}
})
Some context and notices
The project was started on my friend's mac machine. According to him, he doesn't receive those errors. And he also was able to build the app into an .apk for Android
We are both using the same node.js version (15.11.0)
There are times when my metro fails to run, and when that happens, the app runs without any errors.
I have tried to build it into an .apk, but the build failed.
Before the app was combined into 3 games. They were individual games. The mole game did run and was built successfully into an .apk.
The mole game was forked from here The code is pretty much the same.
UPDATE :
After adding
componentWillUnmount(){
clearInterval(this.interval);
clearInterval(this.timerInterval);
}
to the App.js. I no longer get continuous "TypeError: null is not an object (evaluating '_this.moles[randomIndex].isPopping')". Unfortunately, I still get TypeError: null is not an object (evaluating '_this.moles.play) error. This happens when I go back to the main menu. I've tried adding clearTimeout(this.actionTimeout) on mole.js, but that didn't give any effect.
Screenshot :
TypeError: null is not an object (evaluating '_this.moles.play)
The setInterval() function is commonly used to set a delay for functions that are executed again and again, such as animations. You can cancel the interval using clearInterval().
When you navigate from MoleGame to Home, the MoleGame route is popped off the navigation stack and its component is unmounted. But the intervals from the setupTicks method are still executing, and trying to set state on the MoleGame component and to access this (neither of which are possible).
Try clearInterval on componentWillUnmount to stop the intervals that are set in the setupTicks method.
// App.js
export default class MoleGame extends Component {
constructor(props) {
super(props);
this.interval = null;
this.timeInterval = null;
...
}
componentDidMount = () => {
this.setupTicks(DEFAULT_STATE, this.pause);
}
componentWillUnmount() {
clearInterval(this.interval);
clearInterval(this.timeInterval);
}
}
Similarly in Mole.js you would have to handle the same scenario for any setTimeout()s you have by using clearTimeout()
Further reading: Plugging memory leaks in your app
By adding:
componentWillUnmount(){
clearInterval(this.interval);
clearInterval(this.timerInterval);
}
it fixed the continuous TypeError: null is not an object (evaluating '_this.moles[randomIndex].isPopping')
To remove the "TypeError: null is not an object (evaluating '_this.moles.play)" error, I had to put this.mole.play into a variable within Mole.js because this no longer refer to this.mole.play when I go back to the menu. There is still memory leak warnings since it is finishing the animation as I go to the menu, but there are no more critical errors.
Hi I have TextInput like this
Now when i enter text inside TextInput and when i click on cancel then the it is clearing the text inside TextInput , Now along with this I want to hide Cancel when is is clicked.
Here is my code
import React, { Component } from 'react';
import {
Dimensions,
View,
Animated,
TextInput,
TouchableOpacity,
StyleSheet,
} from 'react-native';
import colors from '../styles/colors';
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 Test extends 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,
text: '',
showCancel: true
};
}
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();
};
clearInput = () => {
this.textInputRef.clear();
//this.onFocus.bind(this);
this.setState({
showCancel: !this.state.showCancel
});
}
_renderCancel() {
if (this.state.showCancel) {
return (
<AnimatedTouchable
style={[styles.cancelSearch, { right: this.state.cancelPosition }]}
onPress={(this.clearInput.bind(this))}
>
<Animated.Text
style={[styles.cancelSearchText, { opacity: this.state.opacity, color: colors.darkBlue }]}
onChangeText={text => this.setState({ text })}
value={this.state.text}
>
Cancel
</Animated.Text>
</AnimatedTouchable>
);
} else {
return null;
}
}
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="Enter condition, primary care, speciality"
ref={(ref) => { this.textInputRef = ref; }}
/>
</Animated.View>
<AnimatedTouchable
style={[styles.cancelSearch, { right: this.state.cancelPosition }]}
onPress={this.clearInput.bind(this)}
>
<Animated.Text
style={[styles.cancelSearchText, { opacity: this.state.opacity, color: colors.darkBlue }]}
onChangeText={text => this.setState({ text })}
value={this.state.text}
>
Cancel
</Animated.Text>
</AnimatedTouchable>
{this._renderCancel() }
</View>
);
}
}
const styles = StyleSheet.create({
searchContainer: {
flexDirection: 'row',
height: 72,
borderColor: colors.light_green,
paddingTop: 100
},
search: {
height: 40,
width: '100%',
marginTop: 5,
borderRadius: 6,
alignItems: 'flex-start',
justifyContent: 'flex-start',
borderColor: colors.light_green,
fontSize: 20,
borderWidth: 1,
},
cancelSearch: {
position: 'absolute',
marginHorizontal: 16,
textAlign: 'center',
justifyContent: 'center',
alignSelf: 'center',
color: colors.darkBlue
}
});
can anyone help me how to hide Cancel when it is clicked.
when cancel is clicked i want my original TextInput to be like this
set the style to following :
style={{ opacity: this.state.isVisible ? 1 : 0 }}
and set state isVisibile accordingly to make it visible/hidden.
If you are trying to implement the clear text then you can use
clearButtonMode.
clearButtonMode
Else if you are using state i.e showCancel you can use as
{showCancel? <CancelButton/> : <View/>}
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.
Im trying to play full screen video using react native video, how can I play video full screen for both android and ios?in my case ios playing full screen correctly and in android video is stretched.
for landscape mode I used transform: [{ rotate: '90deg' }], its works but in android the video screen in stretched
Your help is highly appreciated.
here is my code
return (
<View onLayout={this.onLayout.bind(this)} style={styles.fullScreen} key={this.state.key}>
<View style={styles.backButtonWrapper}>
<TouchableOpacity onPress={() => this.props.navigation.goBack()}>
<Image source={Share} />
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.videoView}
onPress={this.playOrPauseVideo.bind(this, paused)}>
<Video
ref={videoPlayer => this.videoPlayer = videoPlayer}
onEnd={this.onVideoEnd.bind(this)}
onLoad={this.onVideoLoad.bind(this)}
onProgress={this.onProgress.bind(this)}
source={{ uri: this.props.detailedWorkout.videoLink }}
paused={paused}
volume={Math.max(Math.min(1, volume), 0)}
resizeMode="none"
style={styles.videoContainer} />
{paused &&
<View style={styles.pauseImageWrapper}>
<Image style={styles.videoIcon} source={PlayButton} />
</View>
}
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
},
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
fullScreen: {
flex: 1,
backgroundColor: "white"
},
videoView: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
videoContainer: {
width: Dimensions.get('window').height,
height: Dimensions.get('window').width,
minWidth: Dimensions.get('window').height,
minHeight: Dimensions.get('window').width,
width: Dimensions.get('screen').height,
height: Dimensions.get('screen').width,
transform: [{ rotate: '90deg' }],
},
videoIcon: {
width: 50,
height: 50
},
pauseImageWrapper: {
alignItems: 'center',
alignSelf: 'center',
position: "absolute",
},
backButtonWrapper: {
backgroundColor: 'red',
position: 'absolute',
zIndex: 1,
alignSelf: "flex-end"
}
});
I solve the issue using react-native-orientation.I'll post my code for anyone who having this issue.Cheers thanks for your help
import React, { Component, PropTypes } from "react";
import {
View,
Dimensions,
StyleSheet,
TouchableOpacity,
Image,
StatusBar,
Platform,
} from "react-native";
import {
Share,
PlayButton
} from "../../config/images";
import {
TextLogo,
IconWithCount,
DefaultIcon,
ClickableIcon,
} from "../../mixing/UI";
import {
WorkoutDetail,
KeyValueText,
DetailText,
ProgressController
} from "../../components";
import Orientation from "react-native-orientation";
import Video from "react-native-video"
const width = Dimensions.get("window").width;
const height = Dimensions.get("window").height;
let FORWARD_DURATION = 7;
export default class VideoPlayer extends Component {
constructor(props, context, ...args) {
super(props, context, ...args);
this.state = { paused: false };
}
componentDidMount() {
Orientation.lockToLandscapeLeft();
}
componentWillUnmount() {
Orientation.lockToPortrait();
}
componentWillMount() {
StatusBar.setHidden(true);
Orientation.lockToLandscapeLeft();
}
onVideoEnd() {
this.videoPlayer.seek(0);
this.setState({ key: new Date(), currentTime: 0, paused: true });
}
onVideoLoad(e) {
this.setState({ currentTime: e.currentTime, duration: e.duration });
}
onProgress(e) {
this.setState({ currentTime: e.currentTime });
}
playOrPauseVideo(paused) {
this.setState({ paused: !paused });
}
onBackward(currentTime) {
let newTime = Math.max(currentTime - FORWARD_DURATION, 0);
this.videoPlayer.seek(newTime);
this.setState({ currentTime: newTime })
}
onForward(currentTime, duration) {
if (currentTime + FORWARD_DURATION > duration) {
this.onVideoEnd();
} else {
let newTime = currentTime + FORWARD_DURATION;
this.videoPlayer.seek(newTime);
this.setState({ currentTime: newTime });
}
}
getCurrentTimePercentage(currentTime, duration) {
if (currentTime > 0) {
return parseFloat(currentTime) / parseFloat(duration);
} else {
return 0;
}
}
onProgressChanged(newPercent, paused) {
let { duration } = this.state;
let newTime = newPercent * duration / 100;
this.setState({ currentTime: newTime, paused: paused });
this.videoPlayer.seek(newTime);
}
onLayout(e) {
const { width, height } = Dimensions.get('window')
}
goBack = () => {
this.props.navigation.goBack();
Orientation.lockToPortrait();
}
// navigation options
static navigationOptions = { header: null }
// render
render() {
let { onClosePressed, video, volume } = this.props;
let { currentTime, duration, paused } = this.state;
const completedPercentage = this.getCurrentTimePercentage(currentTime, duration) * 100;
return (
<View onLayout={this.onLayout.bind(this)} style={styles.fullScreen} key={this.state.key}>
<View style={styles.backButtonWrapper}>
<TouchableOpacity onPress={() => this.goBack()}>
<Image source={Share} />
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.videoView}
onPress={this.playOrPauseVideo.bind(this, paused)}>
<Video
ref={videoPlayer => this.videoPlayer = videoPlayer}
onEnd={this.onVideoEnd.bind(this)}
onLoad={this.onVideoLoad.bind(this)}
onProgress={this.onProgress.bind(this)}
source={{ uri: this.props.detailedWorkout.videoLink }}
paused={paused}
volume={Math.max(Math.min(1, volume), 0)}
resizeMode="none"
style={Platform.OS === "android" ? styles.videoContainerAndroid : styles.videoContainerIOS} />
{paused &&
<View style={styles.pauseImageWrapper}>
<Image style={styles.videoIcon} source={PlayButton} />
</View>
}
</TouchableOpacity>
</View>
);
}
}
// styles
const styles = StyleSheet.create({
fullScreen: {
flex: 1,
backgroundColor: "black"
},
videoView: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
videoContainerAndroid: {
height: "100%",
width: "100%"
},
videoContainerIOS: {
width: Dimensions.get('window').height,
height: Dimensions.get('window').width,
minWidth: Dimensions.get('window').height,
minHeight: Dimensions.get('window').width,
width: Dimensions.get('screen').height,
height: Dimensions.get('screen').width,
transform: [{ rotate: '90deg' }],
},
videoIcon: {
width: 50,
height: 50
},
pauseImageWrapper: {
alignItems: 'center',
alignSelf: 'center',
position: "absolute",
},
backButtonWrapper: {
backgroundColor: 'red',
position: 'absolute',
zIndex: 1,
alignSelf: "flex-end"
}
});
Try resizeMode="contain" (documentation)
I am building a tic-tac-toe game and need to know how I can add an individual style to an individual board piece. If I am using state, would I need a variable that represents each piece on the board? Is there more of an elegant way of doing this. I posted my code here, just give you an idea on how it looks.
import React, { Component } from 'react';
import { ListView, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
class MainApp extends Component {
state = {
clicked: true,
spacePlayed: [],
tables: [0, 1, 2, 3, 4, 5, 6, 7, 8],
playedSpace: [],
}
searchBoard = (index) => {}
_setBoard = (tableId) => {
this.setState({
clicked: !this.state.clicked,
});
var spacePlayed = this.state.playedSpace.push(tableId);
this.setState({ spacePlayed });
}
buildBoard = () => {
var clicked = false;
return (this.state.tables.map((table, index) => {
if (index === 0) {
return (
<TouchableOpacity key={index} onPress={() => this._setBoard(index)}>
<View style={styles.table}></View>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity key={index} onPress={() => this._setBoard(index)}>
<View style={styles.tableActive}></View>
</TouchableOpacity>
);
}
}));
}
render() {
const active = this.state.clicked
? styles.active
: null;
return (
<View style={styles.container}>
{this.buildBoard()}
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flexWrap: 'wrap',
flexDirection: 'row',
width: 200,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 70,
marginTop: 20,
},
table: {
height: 50,
width: 50,
backgroundColor: '#78BEC5',
margin: 5,
justifyContent: 'center',
alignItems: 'center',
},
active: {
backgroundColor: '#ECAF4F',
},
tableActive: {
height: 50,
width: 50,
backgroundColor: '#ECAF4F',
margin: 5,
justifyContent: 'center',
alignItems: 'center',
},
});
export default MainApp;