How to fix differences in height from one tab to another?
+1
+1
You need to handle it yourself. Listen to layout changes in individual tabs and set tab view's height. There are no plans to implement it.
"There are no plans to implement it." - @satya164
Why?
Because you can implement it yourself and I don't want to work on or maintain a feature I'll never use.
Can you help us achieve this (dont need the entire code, just the methods and values that need to change)? I'm just lost on how to limit the height of one particular tab-view. Now its possible to scroll the entire animated screen based on the height of the largest tab-view
What's your use case? I'm don't know what you're planning to do, but I'm pretty sure that dynamic height for tab view is not the best solution.
Regarding methods and values, there's nothing special, you measure the layout of components with onLayout and set height with style.
And whoever is downvoting the comment that I won't do it, I am building this library for free and I'm not required to do whatever you want me to do. Thanks.
I can't make it re-render for now so I need to set multiple parameters for every tab height.
Here is the code if you are interested
Parent:
Remind: the init height can't be <=0, you can set it any number cause it will be changed later
constructor (props) {
super(props)
this.state = {
index: 0,
routes: [
{ key: 'first', title: 'tab1' },
{ key: 'second', title: 'tab2' }
],
tab1Height: Metrics.HEIGHT,
tab2Height: Metrics.HEIGHT
}
this.setHeight = this.setHeight.bind(this)
}
setHeight (idx, height) {
switch (idx) {
case 1:
this.setState({tab1Height: height})
break
case 2:
this.setState({tab2Height: height})
break
default:
break
}
}
renderScene = ({ route }) => {
switch (route.key) {
case 'first':
return <WalletTab1Screen setHeight={this.setHeight} />
case 'second':
return <WalletTab2Screen setHeight={this.setHeight} />
default:
return null
}
}
and render function:
<TabView
style={{height: index === 0 ? this.state.tab1Height : this.state.tab2Height}}
navigationState={this.state}
renderScene={this.renderScene}
renderPager={props => (
<PagerPan {...props} />
)}
renderTabBar={props =>
<TabBar
{...props}
style={{marginTop: HEADER_MAX_HEIGHT}}
indicatorStyle={{ backgroundColor: 'pink' }}
/>
}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Metrics.WIDTH }}
/>
Children tab 1:
<View onLayout={(event) => this.props.setHeight(1, event.nativeEvent.layout.height + Metrics.header + 80)}>...</View
Children tab 2:
<View onLayout={(event) => this.props.setHeight(2, event.nativeEvent.layout.height + Metrics.header + 80)}>...</View>
@yangnana11 thank you
it work for me
@yangnana11 thank you
it work for me
I ended up removing the tabs content from the tab control altogether. It's hacky but worked for me...
render() {
const {index} = this.state;
return (
<ScrollView>
<TabView
renderPager={this._renderPager}
renderScene={() => null}
onIndexChange={index => this.setState({index})}
initialLayout={{height: 0, width: Dimensions.get('window').width}}
/>
{index === 0 && <Tab1Content />}
{index === 1 && <Tab2Content />}
</ScrollView>
);
}
Based on @dseawel solution I came with my own aswell. I was using SceneMap to render my components. I changed to my own switch case so I could send a isActive prop to tell the component whether it should render all its content or an empty View.
Example, i changed this SceneMap:
renderScene = SceneMap({
details: DetailsTab,
gallery: GalleryTab,
ratings: RatingsTab
});
To this switch case showed in the reference example, but sending a isActive prop:
renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'details':
return <DetailsTab navigation={this.props.navigation} jumpTo={jumpTo} isActive={this.state.tabState.index === 0}/>;
case 'albums':
return <GalleryTab navigation={this.props.navigation} jumpTo={jumpTo} isActive={this.state.tabState.index === 1}/>;
case 'ratings':
return <RatingsTab navigation={this.props.navigation} jumpTo={jumpTo} isActive={this.state.tabState.index === 2}/>;
default:
return <View/>;
}
};
And on the component just put a simple validation:
render() {
return this.props.isActive ? (
<View>
{/* contents */}
</View>
) : <View/>
@dseawel @nicoleanater There are good, but it will force rerendering when click any tab.
One way to change TabView's height is:
import { Dimensions } from "react-native";
const SCREEN_HEIGHT = Dimensions.get('window').height
(...)
constructor(props) {
super(props);
this.state = {
tabView: {
index: 1,
routes: [
{ key: 'Media', title: 'Media' },
{ key: 'About', title: 'About' },
],
},
tabViewHeight: 522
}
}
setTabViewHeight = (currentIndex, tabIndex) => event => {
if (currentIndex === tabIndex) {
const height = event.nativeEvent.layout.height + SCREEN_HEIGHT * 0.08;
if (height < 487) {
this.setState({ tabViewHeight: 487 + SCREEN_HEIGHT * 0.08 });
} else {
this.setState({ tabViewHeight: height })
}
}
};
(...)
<TabView
style={ { height: tabViewHeight } }
navigationState={ tabView }
lazy={ true }
renderLazyPlaceholder={ () => <ActivityIndicator/> }
renderScene={ SceneMap({
Media: () => (
<View onLayout={ this.setTabViewHeight(tabView.index, 0) }>
<Media/>
</View>
),
About: () => (
<View onLayout={ this.setTabViewHeight(tabView.index, 1) }>
<About />
</View>
)
}) }
renderTabBar={ props => (
<TabBar
{ ...props }
style={ { backgroundColor: 'white' } }
labelStyle={ { color: 'black', fontSize: 12 } }
tabStyle={ { padding: 0 } }
indicatorStyle={ { backgroundColor: '#7DA89B' } }/>
) }
onIndexChange={ index => this.setState({
tabView: {
...this.state.tabView,
index: index
}
}) }/>
We make use of onLayout to get the height of the wrapping View of each rendered component in a tab, save it in this.state, and pass it to TabView via style.
We added + SCREEN_HEIGHT * 0.08 due to the measurements coming a bit short ( might be because of the Tabs component height ) and everything works as expected.
We used onLayout={ event => console.log(event) } to get the values 522 and 487 but I believe those are different for every device or layout you have.
@saniagh that work but you shoud need to type event.nativeEvent.layout.height to get the actual value of View
<View onLayout={event => console.log(event.nativeEvent.layout.height)}>
/* Something happen */
</View>
I ended up removing the tabs content from the tab control altogether. It's hacky but worked for me...
render() { const {index} = this.state; return ( <ScrollView> <TabView renderPager={this._renderPager} renderScene={() => null} onIndexChange={index => this.setState({index})} initialLayout={{height: 0, width: Dimensions.get('window').width}} /> {index === 0 && <Tab1Content />} {index === 1 && <Tab2Content />} </ScrollView> ); }
This works for me, thanks.
@amruu but the swipe is not working when we follow your solution.Can you please give an insight about this?
@anuragdwivedi29 true that swipe gesture gets disabled, however you can implement custom gesture using this https://www.npmjs.com/package/react-native-swipe-gestures
Try something like this,
Fix the height of the parent widget to the maximum screen height you want that Tab Navigator to have.
<ListView or ScrollView>
<View style={{height: (width)/(0.8)}} >
<Tab.Navigator tabBar={(props) => <TabBar {...props} />}>
<Tab.Screen name="T1" component={T1} />
<Tab.Screen name="T2" component={T2} />
<Tab.Screen name="T3" component={T3} />
</Tab.Navigator>
</View>
</ ListView or ScrollView>
And for tabs do Something like this
T1 ->
<View style={styles.container}>
<ScrollView nestedScrollEnabled={true}>
<FlatList
numColumns={3}
data={allMedia}
keyExtractor={(item) => item.id}
listKey={(post) => `${post.id}D`}
renderItem={({ item }) => (Anything)}
scrollEnabled ={false}
/>
</ScrollView>
</View>
Remember to disable the scroll view Inside the FlatList of tabs and wrap with a ScrollView with nestedScrollEnabled={true}
I found this useful in my case
https://www.gitmemory.com/issue/react-native-community/react-native-tab-view/290/552070362
it says to hide the inactive tab content so that max height of tabview will always be the active one's height
I found this useful in my case
https://www.gitmemory.com/issue/react-native-community/react-native-tab-view/290/552070362
it says to hide the inactive tab content so that max height of tabview will always be the active one's height
I had been using the same, unfortunately gesture does not work with this approach.
Any other way to solve other than bypassing the renderScene method or cleaning the content of each inactive scene?
I have written a solution that is based on the comment @satya164 said above: "Listen to layout changes in individual tabs and set tab view's height"
Here you can see it: https://stackoverflow.com/questions/63606479/react-native-tab-view-always-has-the-height-equal-to-height-of-the-highest-tab/63611264?noredirect=1#comment112488635_63611264
Maybe it can help someone in the future.
Pd: I have no idea how to do this with an inifinite scroll tab that uses pagination, but in my use case this works setting an undefined height to it.
I have written a solution that is based on the comment @satya164 said above: "Listen to layout changes in individual tabs and set tab view's height"
Here you can see it: https://stackoverflow.com/questions/63606479/react-native-tab-view-always-has-the-height-equal-to-height-of-the-highest-tab/63611264?noredirect=1#comment112488635_63611264
Maybe it can help someone in the future.
Pd: I have no idea how to do this with an inifinite scroll tab that uses pagination, but in my use case this works setting an undefined height to it.
In my case, I have set the height for the tab which uses infinite scroll with pagination in TabView to undefined, but it fails.
Here is the error:
Error: Invariant Violation: [56745,"RCTView",{"height":"<<-Infinity>>"}] is not usable as a native method argument
in the solution @amruu provided I get that all my content is starting at the bottom for some reason. Anyone else experienced this issue?
Most helpful comment
I ended up removing the tabs content from the tab control altogether. It's hacky but worked for me...