Animate box expansion in the center of the screen (React Native Animated) - ios

Its my first time animating with react native's animated library. Is it possible to animate in the center of the screen? If so, how can I do that?
This is my code below:
I basically had a 60x60 box on the top left corner of my screen to go down 1/2 the screen and over 1/2 the width. I want it to expand horizontally about 150 px across. The problem is that the box is not expanding in the center of the screen
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import {
View,
Animated,
Dimensions,
Easing
} from 'react-native';
const {width,height} = Dimensions.get('window')
export default class App extends Component {
constructor(){
super()
this.state = {
width: new Animated.Value(60),//Animated.Value/ValueXY used to hook up animation attributes, also for single values
height: new Animated.Value(60),
animateXY : new Animated.ValueXY({x:0,y:0})//for vectors
}
}
componentWillMount(){
//called when an instance of a component is being created and inserted into the DOM
Animated.sequence([
//function where we add the ending results
Animated.timing(
this.state.animateXY,
{
toValue: {x:0, y:height/2},
duration: 2000
}),
Animated.timing(
this.state.animateXY,
{
toValue: {x:width/2, y:height/2},
duration: 2000,
}),
Animated.timing(
this.state.width,
{
toValue: 150,
duration: 3000
}),
]).start()
}
render() {
return (
<View>
<Animated.View
style={
{
width: this.state.width,
height:this.state.height,
backgroundColor:"teal",
position:'absolute',
top: this.state.animateXY.y,
left: this.state.animateXY.x,
}
}
/>
</View>
);
}
}
Any advice will do! thank you!
Jemma

You are not updating the height. Maybe in your case you can use the width only since it’s a square

Related

How to delay AppLoading in React Native? Some elements load faster than others?

Im developing a React Native app in Expo and I have a custom splash screen animation that I have up initially after the AppLoading splash screen disappears. This works in simulator - the animate out transition is an Animated.timing moving from 1 to 0.
Problem is, on an actual iPhone, theres a split second where the AppLoading disppears and the secondary animated splash screen (an Animated.Image) isn't behind it. Other elements of the app have loaded in that split second though.
This happens even if I take out the "animate out" animation. So my logic is to delay the AppLoading disappearing by 1 second long enough for the secondary splash to be steadily in place.
How can I do this? What is happening here?
EDIT: Only mention of AppLoading is here:
if (!fontsLoaded) {
return <AppLoading />;
} else {
The animated splash screen elements
<Animated.Image pointerEvents={"none"} style={[styles.splash, { opacity: splashOpacity }]} source={require('./assets/splashnew.png')} />
<Animated.Image pointerEvents={"none"} style={[styles.splashTxt, { opacity: splashOpacity, transform: [{scaleY: splashScale }, {scaleX: splashScale }]} ]} source={require('./assets/splash.png')} />
Are controlled by this function:
useEffect(() => {
Animated.sequence([
Animated.delay(1000),
Animated.spring(splashScale, {
toValue: 1,
bounciness: 4,
useNativeDriver: false,
speed: 2
})
]).start()
const interval = setInterval(() => {
Animated.sequence([
Animated.parallel([
Animated.spring(splashScale, {
toValue: 0,
bounciness: 4,
useNativeDriver: false,
speed: 2
}),
Animated.spring(splashOpacity, {
toValue: 0,
bounciness: 2,
useNativeDriver: false,
speed: 3
}),
all you have to do is setup another state variable and use that instead of fontsLoaded.
const [waitOneSecond, setWaitOneSecond] = useState(false);
useEffect(()=>{
if (fontsLoaded) setTimeOut(()=>{setWaitOneSecond(true);},1000);
},[fontsLoaded]);
if (!waitOneSecond) {
return <AppLoading />;
} else {

In react-navigation, how do I get the dimensions of the visible area between the header and TabBar?

const viewableWindowHeight = Dimensions.get('window').height - Header.HEIGHT - ???
How do I get the TabBar height?
What if the iPhone is X? How can I take that into account?
Solution 1
If you want to calculate viewable window height directly, then you can use the onLayout callback, for eg, on tab navigation each page,
render() {
return (
<View style={{ flex: 1}} onLayout={(event) => {
var {x, y, width, height} = event.nativeEvent.layout;
this.viewableWindowHeight=height;
// use height as viewableWindowHeight
}} />
<ScollView>
//Your scrollable contant
</ScrollView>
</View>
);
Solution 2
According to an issue in react navigation, you can't directly calculate the height of the bottom tab Bar. But if you wrap bottom tab bar into a view and then you can calculate that views height as bottom tab bar. Consider the example below
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
import { BottomTabBar } from 'react-navigation';
class TabBarComponent extends Component {
measure = () => {
if (this.tabBar) {
this.tabBar.measureInWindow(this.props.setTabMeasurement);
}
}
render() {
return (
<View
ref={(el) => { this.tabBar = el; }}
onLayout={this.measure}
>
<BottomTabBar {...this.props} />
</View>
);
}
}
function mapDispatchToProps(dispatch) {
return {
setTabMeasurement: (x, y, width, height) => dispatch({
type: 'SET_TAB_MEASUREMENT',
measurement: {
x, y, width, height,
},
}),
};
}
export default connect(null, mapDispatchToProps)(TabBarComponent);
Try this:
import { Dimensions, Platform } from 'react-native';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
import { Header } from 'react-navigation';
const { height } = Dimensions.get('window');
const stackHeaderHeight = Header.HEIGHT;
/* Taken from source code of react-navigation-tabs*/
const TAB_BAR_DEFAULT_HEIGHT = 49;
const TAB_BAR_COMPACT_HEIGHT = 29;
const TAB_BAR_HEIGHT = this.bottomTabBarRef._shouldUseHorizontalLabels() && !Platform.isPad
? TAB_BAR_COMPACT_HEIGHT
: TAB_BAR_DEFAULT_HEIGHT;
const marginTop = getStatusBarHeight() + stackHeaderHeight;
const marginBottom = getBottomSpace() + TAB_BAR_HEIGHT;
// < What you're after
const viewableWindowHight = height - marginTop - marginBottom;
FOR TBBAR
Height is changing between these two values >> TAB_BAR_COMPACT_HEIGHT, and TAB_BAR_DEFAULT_HEIGHT, according to a condition determined by this method:
According to react-navigation-tabs source code.
OR
You could set initialLayout to your TabNavigatorConfig as mentioned in the documentation:
initialLayout - Optional object containing the initial height and
width, can be passed to prevent the one frame delay in
react-native-tab-view rendering.
FOR IPHONE-X
You can access statusBar height, bottomSpace in Iphone-X safely though react-native-iphone-x-helper npm module
You can simply use SafeAreaView which will automatically set topBarHeight mainly for iPhoneX phones.
In the new version use
import { useHeaderHeight } from "react-navigation-stack";
console.log(useHeaderHeight());

How to know the useful height of an iOS device in React Native?

In some very specific cases I need to set the height of a View to the full height of the device useful area (without using flex).
I was using a hardcoded "notch height" to calculate this useful height but I just discovered that the notch can have different heights depending on the device. (3 points of difference between iPhone XS and iPhone XS Max).
Is there a way to know the useful height of a device with notch and safe area?
use 'react-native-safe-area-context'
https://www.npmjs.com/package/react-native-safe-area-context#usesafeareainsets
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function Screen() {
const insets = useSafeAreaInsets();
console.log(insets);
//{"bottom": 34, "left": 0, "right": 0, "top": 48}
return <View />;
}
As #mohammed-ashfaq said, react-native-safe-area solves the problem. However, it returns the insets with a promise and I needed those values statically.
Given that, I created react-native-static-safe-area-insets that enables access to the insets values as constants.
You can use the react-native-safe-area. it provides function to Get safe area inset top, bottom, left, right.
import SafeArea, { type SafeAreaInsets } from 'react-native-safe-area'
//Retrieve safe area insets for root view
SafeArea.getSafeAreaInsetsForRootView()
.then((result) => {
console.log(result)
// { safeAreaInsets: { top: 44, left: 0, bottom: 34, right: 0 } }
})
You can get the screen, which users phone, width and height from Dimensions component.
import { Dimensions } from 'react-native'
const { width, height } = Dimensions.get('window'); // if you use the width you can get the screen width by pixels. And also height is the height pixels of the phone.
const screenWidthSomePart = width * 0,6 // Some times you can get the percentage of the screen so you can use this. screen %60
If you wanna see the safe are for the Iphone X. You can use the SafeAreaView Componenet
import { SafeAreaView } from 'react-native'
return(
<SafeAreaView>
..... // your screen componenet
</SafeAreaView>
);
For anyone still looking for a solution to this without installing any packages, this is what i did.
A SafeAreaView has to be used as the wrapper of the screen with a simple View as the only child and then the rest of your desired component tree inside said View.
SafeAreaView uses padding at the top and bottom to take care of insets in devices with notch.
Then at the View inside the SafeAreaView, onLayout callback can be used to get the actual "drawable" height of the View without the insets.
The height from onLayout can then be saved to a state to do any desired logic.
<SafeAreaView>
<View onLayout={({ nativeEvent: layout }) => console.log(layout.height)}>
{children}
</View>
</SafeAreaView>
Use Dimension module from 'react-native' like there :
import { Dimensions, Platform, StatusBar } from 'react-native'
const windowHeight = Dimensions.get('window').height
const screenHeight = Dimensions.get('screen').height

KeyboardAvoidingView overlapping screen on iPhone X

I currently have a KeyboardAvoidingView with a hard-coded keyboardVerticalOffset of 64. This works fine on the iPhone but is about 20px short on the iPhone X.
The component looks like this:
<KeyboardAvoidingView behavior='padding' keyboardVerticalOffset={ 64 }>
<View style={ styles.messageList }>
...
</View>
<View style={ styles.messageInput }>
...
</View>
</KeyboardAvoidingView>
Is there a better way to determine what keyboardVerticalOffset should be than hard coding a value? Is there something else I could be doing differently with component placement? I'm open to any suggestions.
iPhone 8
iPhone X
This is caused by the status bar height being different for iphoneX. (you also get the same issue with other iphones if you toggle the 'in-call' status bar using ⌘Y in the simulator).
You can get the status bar height and use this to set the keyboardVerticalOffset value of the KeyboardAvoidingView. (in our case this was 44 + statusBarHeight)
import React, {Component} from 'react';
import {KeyboardAvoidingView, NativeModules, StatusBarIOS} from 'react-native';
const {StatusBarManager} = NativeModules;
export class IOSKeyboardAvoidingView extends Component {
state = {statusBarHeight: 0};
componentDidMount() {
StatusBarManager.getHeight((statusBarFrameData) => {
this.setState({statusBarHeight: statusBarFrameData.height});
});
this.statusBarListener = StatusBarIOS.addListener('statusBarFrameWillChange', (statusBarData) => {
this.setState({statusBarHeight: statusBarData.frame.height});
});
}
componentWillUnmount() {
this.statusBarListener.remove();
}
render() {
const {style, children} = this.props;
return (
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={44 + this.state.statusBarHeight}
style={style}
>{children}
</KeyboardAvoidingView>
);
}
}
Please refer to : https://stackoverflow.com/a/51169574/10031014 for similar issues
I have used a custom component to overcome this situation.
import React from "react";
import {Animated, Keyboard} from "react-native";
export default class KeyboardAwareComponent extends React.Component {
constructor(props) {
super(props)
this.keyboardHeight = new Animated.Value(0);
}
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
})
]).start();
};
keyboardWillHide = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: 0,
})
]).start();
};
render(){
const {children, style, ...props} = this.props
return(
<Animated.View style={[{flex:1,alignItems:'center',paddingBottom: this.keyboardHeight},style]} {...props}>
{children}
</Animated.View>
);
}
}
Just use the component "KeyboardAwareComponent" as a root component of any page. It will automatically adjust the view when keyboard will show or hide.
Example:---
YourComponent extends React.Component{
render(){
<KeyboardAwareComponent>
{Your child views}
</KeyboardAwareComponent>
}
}
So I did a quick check, given my understanding of how to do this in native iOS, and it seems like in newer versions of react native, you can do this relatively easily.
There do seem to be a couple of options, depending on your flexibility needs.
First, have you tried using KeyboardAvoidView instead of a standard container View without specifying keyboardVerticalOffset?
Another option that gives you much more control (similar to what I would do in a native iOS app) is to use the Keyboard module to create listeners on the keyboard events.
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillChangeSub = Keyboard.addListener('keyboardWillChangeFrame', this.keyboardWillChange);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillChangeSub.remove();
this.keyboardWillHideSub.remove();
}
This would allow you to get the keyboard height from the event parameter:
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
}),
Animated.timing(this.imageHeight, {
duration: event.duration,
toValue: IMAGE_HEIGHT_SMALL,
}),
]).start();
};
Repeat something similar for keyboardWillChange and keyboardWillHide.
For a better, more detailed explanation of your options, see this page:
https://medium.freecodecamp.org/how-to-make-your-react-native-app-respond-gracefully-when-the-keyboard-pops-up-7442c1535580
I think the best first test would be to try to remove the keyboardVerticalOffset before trying to add code to handle the keboard events.

React-native: scrollview inside of panResponder

I am using a ScrollView inside of a PanResponder. On Android it works fine but on iOS the ScrollView will not scroll. I did some investigation and here are some facts:
If I put a break point in PanResponder.onMoveShouldSetPanResponder(), before I step over, the scrollView will scroll as normal but once I release the break point, the scrollView stops working.
If I modify ScrollResponder.js, and return true in scrollResponderHandleStartShouldSetResponderCapture() - it used to return false at runtime; and return false in scrollResponderHandleTerminationRequest(), the scrollView works OK but of course, since it swallows the event the outer PanResponder will not get the event.
So the questions are:
I want to make the scrollview to work, and not to swallow the event. Any one know what's the approach?
How the responding system works on iOS? The react-native responder system doc does not explain that to me.
To enable scrolling in a ScrollView that is a child of a parent with PanResponder, you have to make sure the ScrollView is the responder to any gesture inside of it. By default, gesture events bubble up from the deepest component to the parent component. In order to capture the event by the ScrollView, you can add a View with a PanResponder inside of it. See the (pseudo) example below, where ChildComponent is a child of a parent with PanResponder.
const ChildComponent = ({ theme }) => {
const panResponder = React.useRef(
PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
})
).current;
return (
<ScrollView style={{ height: 500 }}>
<View {...panResponder.panHandlers}>
...
</View>
</ScrollView>
);
};
Within PanResponder is an event that returns the current touch position. You can use that to compare 2 values to perform a scroll.
I finally solve this by wrap the scrollview inside a view ,and set the style of scrollview a limited height.
import * as React from 'react';
import { Text, View, StyleSheet,PanResponder,Animated,ScrollView,Dimensions} from 'react-native';
import { Constants } from 'expo';
const WINDOW_WIDTH = Dimensions.get("window").width;
const WINDOW_HEIGHT = Dimensions.get("window").height;
// You can import from local files
export default class App extends React.Component {
constructor(props){
super(props);
this.minTop = 100;
this.maxTop = 500;
this.state={
AnimatedTop:new Animated.Value(0),
}
}
componentWillMount(){
let that = this;
this._previousTop = 100;
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(){
return true;
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})
}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.overlay,{top:this.state.AnimatedTop}]}
{...this._panResponder.panHandlers}
>
<View style={{height:200,backgroundColor:"black"}}></View>
<View>
<ScrollView
style={{height:500}}
>
<View style={{backgroundColor:"blue",height:200}}></View>
<View style={{backgroundColor:"yellow",height:200}}></View>
<View style={{backgroundColor:"pink",height:200}}></View>
<View style={{backgroundColor:"red",height:200}}></View>
</ScrollView>
</View>
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
overlay:{
position:"absolute",
width:WINDOW_WIDTH,
height:WINDOW_HEIGHT-100,
}
});
enter link description here
I spent plenty of time to solve this.Hope this will help someone confused with the same problem.
I don't know if it's usefull right now but you can add that line,
return !(gestureState.dx === 0 && gestureState.dy === 0)
in 'onMoveShouldSetPanResponder' property of PanResponder with evt and gestureState as parameters.
Your PanResponder should look like this :
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(evt, gestureState){
return !(gestureState.dx === 0 && gestureState.dy === 0)
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})
I have solved this by adding onPress handlers to all of the contents of the ScrollView - doesn't matter if the handler is a no-op, the scroll view still works fine.
I'm not sure you still need this or not but I'll put to help others as well
If you put the panResponder on a sibling view of the ScrollView, the ScrollView behaves properly. then you can position that sibling view
here is an example of the workaround.
I solved this issue by doing it
onMoveShouldSetPanResponder: (event, gesture) => {
if (gesture?.moveX > gesture?.moveY) {
return false;
}
return true;
},
On my case, I have a horizontal Flatlist inside a PanResponder...

Resources