在 React Navigation 5 中向 createMaterialBottomTabNavigator 添加自定义 'Add' 按钮
Adding a custom 'Add' button to createMaterialBottomTabNavigator in react navigation 5
我正在制作一个项目(react native、expo、react navigation 5),我想在其中向底部选项卡添加一个自定义 'add' 按钮,但是因为...
A navigator can only contain 'Screen' components as its direct
children
...我需要找到一种方法来传递我的自定义组件。
看起来很简单,我的意思是有文档:
- https://reactnavigation.org/docs/material-bottom-tab-navigator
- https://reactnavigation.org/docs/bottom-tab-navigator
- https://reactnavigation.org/docs/custom-navigators/
...但是在查看这些和其他人的问题时,我要么只找到了很多复杂的示例,要么找到了如何在早期版本中实现此目的的示例。
最后我找到了一个简单的解决方案,到目前为止效果很好(非常感谢任何关于为什么这可能是一个糟糕想法的建议)。
我想 post 我的解决方案,如果有人遇到类似的问题。请参阅下面的答案。
将组件放在导航器外部,并使用 css 将其放置在选项卡上方。如示例所示,将选项卡的图标调整为左右。
就像我上面说的,热烈欢迎关于如何以不同方式实现这一目标的建议,但还没有遇到任何问题(敲木头)。
这是它的样子:
这是兔子:
import React from 'react';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome } from '@expo/vector-icons';
import AddButton from '../../components/UI/AddButton';
import SpotlightProductsScreen from './SpotlightProductsScreen';
import ProductsScreen from './ProductsScreen';
import UserSpotlightScreen from './../user/UserSpotlightScreen';
import UserProductsScreen from './../user/UserProductsScreen';
const ProductsOverviewScreen = props => {
const Tab = createMaterialBottomTabNavigator();
return (
<>
<AddButton
navigation={props.navigation}
style={{
position: 'absolute',
zIndex: 99,
bottom: 5,
alignSelf: 'center',
shadowColor: 'black',
shadowOpacity: 0.15,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 3 //Because shadow only work on iOS, elevation is same thing but for android.
}}
/>
<Tab.Navigator
initialRouteName="Spotlight"
labeled={false}
shifting={true}
activeColor="#f0edf6"
inactiveColor="#3e2465"
barStyle={{ backgroundColor: 'rgba(127,63,191,.9)' }}
>
<Tab.Screen
name="Spotlight"
component={SpotlightProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<Ionicons
name={
Platform.OS === 'android'
? 'md-notifications'
: 'ios-notifications'
}
color={color}
size={27}
style={{
marginLeft: -35
}}
/>
)
}}
/>
<Tab.Screen
name="Förråd"
component={ProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-download'}
color={color}
size={27}
style={{
marginLeft: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Mitt Förråd"
component={UserProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-upload'}
color={color}
size={30}
style={{
marginRight: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Min Sida"
component={UserSpotlightScreen}
options={{
tabBarBadge: 4,
tabBarIcon: ({ color }) => (
<FontAwesome
name={'user'}
color={color}
size={30}
style={{
marginRight: -35
}}
/>
)
}}
/>
</Tab.Navigator>
</>
);
};
export default ProductsOverviewScreen;
我遇到了同样的问题,我需要向选项卡导航器添加一个与屏幕无关的自定义组件,但我尝试的一切都失败了。就我而言,我正在尝试使用 createMaterialTopTabNavigator。
React Navigation 5 的文档有点粗糙,没有很多 React Navigation 5 的示例,但经过多次尝试,我可以创建一个自定义组件并自己设置样式,这样我就可以混合由选项卡导航器中嵌入的路由和自定义按钮。
import * as React from 'react';
import { View } from 'react-native'
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
createNavigatorFactory,
} from '@react-navigation/native';
import styled from 'styled-components'
import Colors from '../constants/Colors';
const customTabNavigator = ({
initialRouteName,
children,
screenOptions,
tabContainerStyle,
contentStyle,
leftIcon,
rightIcon
}) => {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationHelpersContext.Provider value={navigation}>
<OuterWrapper style={tabContainerStyle}>
{ leftIcon }
<TabWrapper>
{state.routes.map((route, i) => {
return (
<Tab
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={descriptors[route.key].options.tabStyle}
>
{ descriptors[route.key].options.label ?? <Label active={state.index === i}>{descriptors[route.key].options.title || route.name}</Label> }
</Tab>
)
})}
</TabWrapper>
{ rightIcon }
</OuterWrapper>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
</NavigationHelpersContext.Provider>
);
}
const OuterWrapper = styled.View`
height: 55px;
flex-direction: row;
justify-content: space-between;
background-color: ${Colors.grey1};
`
const TabWrapper = styled.View`
flex: 1;
flex-direction: row;
justify-content: space-evenly;
`
const Tab = styled.TouchableOpacity`
padding: 0 24px;
justify-content: center;
height: 100%;
`
const Label = styled.Text`
font-family: Futura-Medium;
font-size: 26px;
color: ${({ active }) => active ? Colors.grey6 : Colors.grey3};
`
export default createNavigatorFactory(customTabNavigator)
import customTabNavigator from './customTabNavigator'
import * as React from 'react';
import { View, Image } from 'react-native'
import {
ProjectsScreen,
RenderScreen,
EventsScreen,
CameraScreen
} from '../screens';
import Colors from '../constants/Colors'
import logo from '../assets/images/icon.png'
import { Ionicons } from '@expo/vector-icons';
import { TouchableOpacity } from 'react-native-gesture-handler';
const TopTab = customTabNavigator();
const INITIAL_ROUTE_NAME = 'Home';
export default function MainNavigator({ navigation, route }) {
navigation.setOptions({ headerTitle: getHeaderTitle(route) });
return (
<TopTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
leftIcon={(
<TouchableOpacity style={{ height: "100%", justifyContent: "center" }} onPress={() => alert("Whatever")}>
<Image source={logo} style={{ resizeMode: "center", width: 70, height: 40 }} />
</TouchableOpacity>
)}
>
<TopTab.Screen
name="Home"
component={ProjectsScreen}
options={{
title: 'Proyectos',
}}
/>
<TopTab.Screen
name="Preview"
component={EventsScreen}
options={{
title: 'Eventos',
}}
/>
<TopTab.Screen
name="Render"
component={RenderScreen}
options={{
title: 'Mi cuenta',
}}
/>
<TopTab.Screen
name="Camera"
component={CameraScreen}
options={{
title: "Camera",
label: (
<View style={{ width: 36, height: 32, backgroundColor: Colors.grey3, borderRadius: 3, alignItems: "center", justifyContent: "center" }}>
<Ionicons name="md-camera" style={{ color: Colors.grey5 }} size={25} />
</View>
),
tabStyle: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", flex: 1 }
}}
/>
</TopTab.Navigator>
);
}
function getHeaderTitle(route) {
const routeName = route.state?.routes[route.state.index]?.name ?? INITIAL_ROUTE_NAME;
switch (routeName) {
case 'Home':
return 'Montar vídeo';
case 'Preview':
return 'Previsualizar vídeo';
case 'Render':
return 'Renderizar';
case 'Gallery':
return 'Galería'
case 'Camera':
return 'Camera'
}
}
对于 https://reactnavigation.org/docs/custom-navigators 的示例,我添加了不同的样式和两个新道具,leftIcon 和 rightIcon。该道具接收一个组件,用于将其渲染到选项卡包装器的相应侧。并且此组件可以是 TouchableWhatevers,带有与屏幕无关的自定义 onPress :P
我希望它能帮到你,我几乎把自己扔进了 window 直到我成功了,哈哈
你可以试试这个:
<Tab.Screen
name = "Button"
component={ScanStack}
options={{
tabBarButton:()=>
<View style={{position:'relative',bottom:35,alignItems:'center', justifyContent:'space-around',height:85}}>
<Icon
name="barcode-scan"
type = "material-community"
reverse
color={'yellow'}
reverseColor='black'
containerStyle={{padding:0,margin:0,elevation:5}}
onPress={()=>console.log('Hi')}
size={30}/>
<Text>Scan</Text>
</View>
}}/>
在组件中需要使用有效的反应组件,我尝试使用component={()=>null}
但控制台出现警告
Result
step-1:- 在 bottomTabNav.js 中给 component-prop 提供一个 returns 什么都没有的屏幕
import React from 'react'
const AddMoreScreen = () => {
return null
}
export default AddMoreScreen
//I created AddMoreScreen.js component
第 2 步:- 如第 1 步中所述,我正在提供一个不向组件道具呈现任何内容的屏幕,稍后在选项道具中我将点击 tabBarButton 对象和 returns 一个自定义按钮
...
<Tab.Screen
name={"Add"}
component={AddMoreScreen}
options={{
tabBarButton: ()=> <AddMoreModal />
}}
/>
...
第 3 步:- 最后是我们的中心按钮代码,在我的例子中,如果我按下按钮,模式必须从底部出现,只需根据您的要求更改以下代码。 AddMoreModal.js
export default function AddMoreModal() {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={{ marginTop: 15, marginLeft: 10, marginRight: 10, }}>
<TouchableOpacity
onPress={() => {
setModalVisible(true);
}}
>
<View style={styles.buttonStyle}>
<Image
source={icons.quickAddOutlined}
style={styles.bottomTabImage}
/>
<Text style={styles.bottomTabText}>Add</Text>
</View>
</TouchableOpacity>
<View style={styles.container}>
<Modal
backdropOpacity={0.3}
isVisible={modalVisible}
onBackdropPress={() => setModalVisible(false)}
style={styles.contentView}
>
<View style={styles.content}>
<Text style={styles.contentTitle}>Hi !</Text>
<Text>Welcome to CRAZY MIDDLE BUTTON! forgotten by react-native navigation</Text>
</View>
</Modal>
</View>
</View>
);
}
const styles = StyleSheet.create({
content: {
backgroundColor: "white",
padding: 22,
justifyContent: "center",
alignItems: "center",
borderTopRightRadius: 17,
borderTopLeftRadius: 17,
},
contentTitle: {
fontSize: 20,
marginBottom: 12,
},
contentView: {
justifyContent: "flex-end",
margin: 0,
},
buttonStyle: {
marginBottom: 0,
alignItems:'center',
justifyContent:'center'
},
bottomTabImage: {
width: 25,
height: 25,
},
bottomTabText: {
marginTop: 4,
fontSize: FONTS.body3.fontSize,
fontFamily: FONTS.body3.fontFamily,
color: COLORS.fontGray90,
},
});
第四步:-
我正在制作一个项目(react native、expo、react navigation 5),我想在其中向底部选项卡添加一个自定义 'add' 按钮,但是因为...
A navigator can only contain 'Screen' components as its direct children
...我需要找到一种方法来传递我的自定义组件。
看起来很简单,我的意思是有文档:
- https://reactnavigation.org/docs/material-bottom-tab-navigator
- https://reactnavigation.org/docs/bottom-tab-navigator
- https://reactnavigation.org/docs/custom-navigators/
...但是在查看这些和其他人的问题时,我要么只找到了很多复杂的示例,要么找到了如何在早期版本中实现此目的的示例。
最后我找到了一个简单的解决方案,到目前为止效果很好(非常感谢任何关于为什么这可能是一个糟糕想法的建议)。
我想 post 我的解决方案,如果有人遇到类似的问题。请参阅下面的答案。
将组件放在导航器外部,并使用 css 将其放置在选项卡上方。如示例所示,将选项卡的图标调整为左右。
就像我上面说的,热烈欢迎关于如何以不同方式实现这一目标的建议,但还没有遇到任何问题(敲木头)。
这是它的样子:
这是兔子:
import React from 'react';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome } from '@expo/vector-icons';
import AddButton from '../../components/UI/AddButton';
import SpotlightProductsScreen from './SpotlightProductsScreen';
import ProductsScreen from './ProductsScreen';
import UserSpotlightScreen from './../user/UserSpotlightScreen';
import UserProductsScreen from './../user/UserProductsScreen';
const ProductsOverviewScreen = props => {
const Tab = createMaterialBottomTabNavigator();
return (
<>
<AddButton
navigation={props.navigation}
style={{
position: 'absolute',
zIndex: 99,
bottom: 5,
alignSelf: 'center',
shadowColor: 'black',
shadowOpacity: 0.15,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 3 //Because shadow only work on iOS, elevation is same thing but for android.
}}
/>
<Tab.Navigator
initialRouteName="Spotlight"
labeled={false}
shifting={true}
activeColor="#f0edf6"
inactiveColor="#3e2465"
barStyle={{ backgroundColor: 'rgba(127,63,191,.9)' }}
>
<Tab.Screen
name="Spotlight"
component={SpotlightProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<Ionicons
name={
Platform.OS === 'android'
? 'md-notifications'
: 'ios-notifications'
}
color={color}
size={27}
style={{
marginLeft: -35
}}
/>
)
}}
/>
<Tab.Screen
name="Förråd"
component={ProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-download'}
color={color}
size={27}
style={{
marginLeft: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Mitt Förråd"
component={UserProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-upload'}
color={color}
size={30}
style={{
marginRight: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Min Sida"
component={UserSpotlightScreen}
options={{
tabBarBadge: 4,
tabBarIcon: ({ color }) => (
<FontAwesome
name={'user'}
color={color}
size={30}
style={{
marginRight: -35
}}
/>
)
}}
/>
</Tab.Navigator>
</>
);
};
export default ProductsOverviewScreen;
我遇到了同样的问题,我需要向选项卡导航器添加一个与屏幕无关的自定义组件,但我尝试的一切都失败了。就我而言,我正在尝试使用 createMaterialTopTabNavigator。
React Navigation 5 的文档有点粗糙,没有很多 React Navigation 5 的示例,但经过多次尝试,我可以创建一个自定义组件并自己设置样式,这样我就可以混合由选项卡导航器中嵌入的路由和自定义按钮。
import * as React from 'react';
import { View } from 'react-native'
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
createNavigatorFactory,
} from '@react-navigation/native';
import styled from 'styled-components'
import Colors from '../constants/Colors';
const customTabNavigator = ({
initialRouteName,
children,
screenOptions,
tabContainerStyle,
contentStyle,
leftIcon,
rightIcon
}) => {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationHelpersContext.Provider value={navigation}>
<OuterWrapper style={tabContainerStyle}>
{ leftIcon }
<TabWrapper>
{state.routes.map((route, i) => {
return (
<Tab
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={descriptors[route.key].options.tabStyle}
>
{ descriptors[route.key].options.label ?? <Label active={state.index === i}>{descriptors[route.key].options.title || route.name}</Label> }
</Tab>
)
})}
</TabWrapper>
{ rightIcon }
</OuterWrapper>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
</NavigationHelpersContext.Provider>
);
}
const OuterWrapper = styled.View`
height: 55px;
flex-direction: row;
justify-content: space-between;
background-color: ${Colors.grey1};
`
const TabWrapper = styled.View`
flex: 1;
flex-direction: row;
justify-content: space-evenly;
`
const Tab = styled.TouchableOpacity`
padding: 0 24px;
justify-content: center;
height: 100%;
`
const Label = styled.Text`
font-family: Futura-Medium;
font-size: 26px;
color: ${({ active }) => active ? Colors.grey6 : Colors.grey3};
`
export default createNavigatorFactory(customTabNavigator)
import customTabNavigator from './customTabNavigator'
import * as React from 'react';
import { View, Image } from 'react-native'
import {
ProjectsScreen,
RenderScreen,
EventsScreen,
CameraScreen
} from '../screens';
import Colors from '../constants/Colors'
import logo from '../assets/images/icon.png'
import { Ionicons } from '@expo/vector-icons';
import { TouchableOpacity } from 'react-native-gesture-handler';
const TopTab = customTabNavigator();
const INITIAL_ROUTE_NAME = 'Home';
export default function MainNavigator({ navigation, route }) {
navigation.setOptions({ headerTitle: getHeaderTitle(route) });
return (
<TopTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
leftIcon={(
<TouchableOpacity style={{ height: "100%", justifyContent: "center" }} onPress={() => alert("Whatever")}>
<Image source={logo} style={{ resizeMode: "center", width: 70, height: 40 }} />
</TouchableOpacity>
)}
>
<TopTab.Screen
name="Home"
component={ProjectsScreen}
options={{
title: 'Proyectos',
}}
/>
<TopTab.Screen
name="Preview"
component={EventsScreen}
options={{
title: 'Eventos',
}}
/>
<TopTab.Screen
name="Render"
component={RenderScreen}
options={{
title: 'Mi cuenta',
}}
/>
<TopTab.Screen
name="Camera"
component={CameraScreen}
options={{
title: "Camera",
label: (
<View style={{ width: 36, height: 32, backgroundColor: Colors.grey3, borderRadius: 3, alignItems: "center", justifyContent: "center" }}>
<Ionicons name="md-camera" style={{ color: Colors.grey5 }} size={25} />
</View>
),
tabStyle: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", flex: 1 }
}}
/>
</TopTab.Navigator>
);
}
function getHeaderTitle(route) {
const routeName = route.state?.routes[route.state.index]?.name ?? INITIAL_ROUTE_NAME;
switch (routeName) {
case 'Home':
return 'Montar vídeo';
case 'Preview':
return 'Previsualizar vídeo';
case 'Render':
return 'Renderizar';
case 'Gallery':
return 'Galería'
case 'Camera':
return 'Camera'
}
}
对于 https://reactnavigation.org/docs/custom-navigators 的示例,我添加了不同的样式和两个新道具,leftIcon 和 rightIcon。该道具接收一个组件,用于将其渲染到选项卡包装器的相应侧。并且此组件可以是 TouchableWhatevers,带有与屏幕无关的自定义 onPress :P
我希望它能帮到你,我几乎把自己扔进了 window 直到我成功了,哈哈
你可以试试这个:
<Tab.Screen
name = "Button"
component={ScanStack}
options={{
tabBarButton:()=>
<View style={{position:'relative',bottom:35,alignItems:'center', justifyContent:'space-around',height:85}}>
<Icon
name="barcode-scan"
type = "material-community"
reverse
color={'yellow'}
reverseColor='black'
containerStyle={{padding:0,margin:0,elevation:5}}
onPress={()=>console.log('Hi')}
size={30}/>
<Text>Scan</Text>
</View>
}}/>
在组件中需要使用有效的反应组件,我尝试使用component={()=>null}
但控制台出现警告
Result
step-1:- 在 bottomTabNav.js 中给 component-prop 提供一个 returns 什么都没有的屏幕
import React from 'react'
const AddMoreScreen = () => {
return null
}
export default AddMoreScreen
//I created AddMoreScreen.js component
第 2 步:- 如第 1 步中所述,我正在提供一个不向组件道具呈现任何内容的屏幕,稍后在选项道具中我将点击 tabBarButton 对象和 returns 一个自定义按钮
...
<Tab.Screen
name={"Add"}
component={AddMoreScreen}
options={{
tabBarButton: ()=> <AddMoreModal />
}}
/>
...
第 3 步:- 最后是我们的中心按钮代码,在我的例子中,如果我按下按钮,模式必须从底部出现,只需根据您的要求更改以下代码。 AddMoreModal.js
export default function AddMoreModal() {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={{ marginTop: 15, marginLeft: 10, marginRight: 10, }}>
<TouchableOpacity
onPress={() => {
setModalVisible(true);
}}
>
<View style={styles.buttonStyle}>
<Image
source={icons.quickAddOutlined}
style={styles.bottomTabImage}
/>
<Text style={styles.bottomTabText}>Add</Text>
</View>
</TouchableOpacity>
<View style={styles.container}>
<Modal
backdropOpacity={0.3}
isVisible={modalVisible}
onBackdropPress={() => setModalVisible(false)}
style={styles.contentView}
>
<View style={styles.content}>
<Text style={styles.contentTitle}>Hi !</Text>
<Text>Welcome to CRAZY MIDDLE BUTTON! forgotten by react-native navigation</Text>
</View>
</Modal>
</View>
</View>
);
}
const styles = StyleSheet.create({
content: {
backgroundColor: "white",
padding: 22,
justifyContent: "center",
alignItems: "center",
borderTopRightRadius: 17,
borderTopLeftRadius: 17,
},
contentTitle: {
fontSize: 20,
marginBottom: 12,
},
contentView: {
justifyContent: "flex-end",
margin: 0,
},
buttonStyle: {
marginBottom: 0,
alignItems:'center',
justifyContent:'center'
},
bottomTabImage: {
width: 25,
height: 25,
},
bottomTabText: {
marginTop: 4,
fontSize: FONTS.body3.fontSize,
fontFamily: FONTS.body3.fontFamily,
color: COLORS.fontGray90,
},
});
第四步:-