Implementing a scale screen transition with React Native? - react-native

Im trying to implement a screen transition effect, when a user clicks on a Box Component (inside a ScrollView), the image will scale and move to the top, Airbnb has a great example of this:
Airbnb scale transition
<ScrollView>
<Box /> // onPress the scaleImage comp should move to top screen
<Box /> // regardless of scroll position
<Box />
<Box />
...
</ScrollView>
<Animated.Image
style={styles.scaleImage}
source={this.state.activeImage} />
I know how to implement the animation part of this, but the problem Im having is the layout, if the images that Im trying to scale and move to the top are in a ScrollView, how can I determine the distance to get to the top.

You could try using React Native's measure function. You can give it any native component (View, ListView, Image, etc.) and it will give you that component's offsets to its frame (the native view which contains it), its page (the screen borders), and its width/height in a callback as soon as rendering is done (or immediately, if rendering is already done)
To get a reference to your image component, define it as:
<Image ref="myImage" /*...*/ />
Then, within your component class, you can do:
this.refs.myImage.measure((frameX, frameY ,width, height, pageX, pageY) => {
//do stuff with the measurements
})
In your case, you would likely want to use pageX and pageY as they will give you the absolute position of your image. Just remember this function only works on native components and not on user-defined components, so you should call it on the actual Image component and not on any wrappers you define around it.

Related

display a view above a react nativigation modal

I have a custom snackbar component which has a zIndex higher than other views and an absolute position in the app.
The snackbar works fine, as long as no modals are opened. The problem is, I have nested navigation with nested modals and the snackbar component needs to be on top, all the time.
currently, my screenOptions are: { presentation: 'modal' }
Others like containedModal work, but gives me stacked headers for all the parent modals. I like the way modal presentation looks on iOs. Any ideas how an absolute View can be displayed above any modal from the root of the app?
Z-index in RN doesn't work the same as it does on web - see the docs for more. Components are rendered as a tree, with the first items on the bottom and stacking until the last items are on top.
Therefore you can put a component on top of everything else by rendering it outside and after everything else. I.e.
// App.js
const App = () => {
return (
<>
<NavigationContainer>
<MainStack />
</NavigationContainer>
<Snackbar />
</>
);
};

React native partial modal

I am working on a react-native app using react navigation.
I want to add a partial modal that covers 30% of screen when pressing on one of the tabs in the bottom-tab, similar to the "+" tab in the YouTube app:
Youtube modal
I've tried to use react-native Modal component, but
I have problems with activating it from bottom tab
it covers whole screen
Any suggestions?
Thanks..
To answer your question 100% correct I'd need to know more details about your project but, I can try to explain how can be your logic to do this withou any libs.
You can create another component called "Start" where you're gonna put your Modal with absolute position above your navigator.
You can create a Modal that will be above your navigator:
export const Start(){
return(
<View style={{flex:1}}>
<Modal>
<View/>
</Modal>
<NavigatorComponent/>
</View>
)
}
This is going to work because when you put a component with absolute position above the other, this component stay in front of the below component. In your case, will stay in front of everything including the TabBar.
To the modal has just 30% of the height you just need to put in its styles the attribute height: '30%'.
In the initial component of your App (usually the index.js file), you just return the new Start component.
I hope you like my answer. Please, if you have more questions you can ask. Waiting for your feedback :).

How to prerender a component in react-navigation before switching to it?

Inside my StackNavigator, one of the components includes web content with a long loading time. However, this screen will only be shown late in my navigation flow.
How can I use this time to render my component in the background before finally switching to it?
I couldn't find anything comparable to ReactDOM.render in React Native that would allow me to render it manually.
I am not aware of any option in react-navigation to preload a screen that is not displayed, except maybe when the screen is part of a tab navigator.
Depending on what is slowing down the rendering, you might be able to do some actions in the first screen and later pass the results to the second screen using a navigation parameter.
For instance, if you are fetching data from an api in the second screen, you could fetch this data in the first screen and pass it to the second one:
this.props.navigation.navigate('SecondScreen', { data: this.data });
If it is a component, you could also try to build it in the first screen and pass it in the same fashion:
this.props.navigation.navigate('SecondScreen', { component: this.component });
If you are rendering a WebView in the second screen, what can help is to render the WebView in the first screen too, but with no width or height. The WebView will not be displayed but the website data will be fetched and cached, making the real render more efficient:
render() {
return (
<View>
<WebView source={{ uri: 'https://github.com/facebook/react-native' }} style={{ height: 0, width: 0 }} />
{this.renderCurrentScreen()}
</View>
);
}

How to access OnPress event that happens outside the component?

I am creating a custom dropdown component in React Native. I want to close it contents, when user presses screen outside of the component on any other part of the application.
However, I cannot know if user pressed outside the component. Is there a global OnPress event that can accessed or some other way, kindly let me know.
Edited:
add a logic when you click dropdown it should create a transparent view covering wholescreen in a absolute position.
Do it Like this:
// inside render
<Fragment>
<Nested>
<DropDown/>
</Nested>
{isDrop &&
<View style={styles.container} // height:'100%', width:'100%', backgroundColor:transparent , position: 'absolute'
//Trigger for pressing outside DropDown
onResponderStart={() => { condition for dropdown}}
//Required to start interacting with touches
onStartShouldSetResponder={(e) => {return true}}/>}
</Fragment>
DropDown component and view with touch must be the same level

React Native - Pagination of ListViews in ScrollView?

I have 3 ListView components inside a single ScrollView component like this:
<ScrollView>
<Header />
<ListView onEndReached={() => alert('load more 1')}/>
<ListView onEndReached={() => alert('load more 2')}/>
<ListView onEndReached={() => alert('load more 3')}/>
<Footer />
</ScrollView>
The Header component has some common content and also has 3 tabs, which trigger showing the respective ListView
The issue is any ListView with onEndReached={() => alert('load more 1')} never runs the alert, so I can never load more as I scroll down and hit the end of the listview. Remove the wrapping ScrollView and the alert runs, but the common Header doesn't scroll, since we just removed the wrapping ScrollView. The header needs to scroll with the listview, which is why I wrapped everything that needs to scroll in the ScrollView.
IMPORTANT NOTE:
I can't really use ListView with renderHeader={this.header}, for this scenario. Because, even though the Header will scroll, it will rerender the common Header and the 3 tabs for each ListView each time a ListView renders, instead of once. So a new Header rerender each time for each ListView won't cut it for the app.
Looking for a solution to this problem, where the Header scrolls with the listviews and the onEndReached is triggered for the visible ListView.
I think you're going to have to solve this by changing the dataSource in each listView in response to what header element is selected instead of loading three different ListViews.
getInitialState() {
return {
currentList: this.ds.cloneWithRowsAndSections(INITIAL_DATA);
}
},
render() {
return <ListView renderHeader={this._renderHeader} dataSource={this.state.currentList}/>
}
The only reason you wouldn't want to do this is if you wanted to maintain the scroll position in the three sub ListViews, but that wouldn't be useful because you always have to scroll to the top to change which ListView you're looking at anyway.
Then in your _renderHeader function you would render a selector that populates currentList with different data depending on the header selected.
In styling you can set it's position as relative with top:0 and left:0 . This way it will remain static on top.
<View>
<Header style={{position:"relative",top:0,left:0,height:30,width:Dimensions.get("window").width}} />
<ListView />
<ListView />
<ListView />
<Footer />
</View>
Second option which may work in scrollview is to specify height for all three ListView.
You can maybe use ScrollView::onScroll but it will be a little hacky. You will need to know the size of your listviews.
Work with ListView only
Maybe the best solution will be to play with the ListView dataSource and the onEndReached function.
If you update you dataset when ListView::onEndReached is triggered, I think you can add more elements to your ListView. This way, you do not need to do hacky things with ListViews in ScrollViews.