Вложенный TabNavigator внутри StackNavigator: управление заголовком

У меня такая установка:

let Tabs = createBottomTabNavigator({
    screen1: Screen1,
    screen2: Screen2
})

let Stack = createStackNavigator({
    tabs: Tabs
    otherScreen: OtherScreen
})

У навигатора стека есть заголовок, и это нормально. Я хочу получать разные значки заголовков в зависимости от того, на какой вкладке я сейчас нахожусь.

Использую следующие версии:

"react": "16.3.1",
"react-native": "~0.55.2",
"react-navigation": "^2.2.5"

Я подумал о том, чтобы изменить свою настройку, чтобы на каждом экране вкладок был свой собственный StackNavigator, но мне нравится скользящая анимация при переключении вкладок, и я не хочу, чтобы значки заголовков скользили вместе. Панель заголовка должна оставаться статичной, но просто отображать разные значки в зависимости от текущей вкладки.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
11
0
12 708
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете добиться желаемого поведения с помощью текущей конфигурации вашего стека навигации. Возможно, вам придется изменить пару вещей и объединить несколько свойств, но это довольно просто, если вы разберетесь с этим.

Я пытаюсь пояснить это на небольшом примере.

Подумайте о наличии навигаторов ниже;

const Tabs = createBottomTabNavigator({
    screen1: Tab1,
    screen2: Tab2
})

const Stack = createStackNavigator({
    tabs: {
      screen: TabsPage,
      navigationOptions: ({navigation}) => {
        return { title: (navigation.state.params && navigation.state.params.title ? navigation.state.params.title : 'No Title' ) }
      }
    },
    otherScreen: Page
})

Как видите, я устанавливаю параметр заголовка из состояния навигации. Чтобы задать параметры для этого навигатора, нам понадобится свойство screenProps;

class TabsPage extends Component {
  onTabsChange = (title) => {
    this.props.navigation.setParams({ title })
  }
  render() {
    return(<Tabs screenProps = {{ onTabsChange: this.onTabsChange }} />)
  }
}

Я создал компонент-оболочку для навигатора вкладок и передал функцию, которая устанавливает параметр заголовка.

Для последней части нам нужно знать, как и когда использовать эту функцию, которую мы передали. Для этого мы будем использовать опору навигации addListener.

class Tab1 extends React.Component {
  setTitle = () => {
    this.props.screenProps.onTabsChange('Title from Tab 1')
  }
  componentDidMount() {
    this.props.navigation.addListener('willFocus', this.setTitle)
  }
  render() {
    return <View><Text>{'Tab1'}</Text></View>
  }
}

Когда наша вкладка сфокусирована, переданная функция запустится, а затем установит заголовок для этой вкладки. Вы можете использовать этот процесс для настройки различных кнопок или значков для заголовка. Вы можете найти рабочую закуску здесь.

Это действительно лучшее решение, но как перейти к «otherScreen»? this.props.navigation.navigate ('Страница'); не работает из Tab1 или Tab2

Chris 29.06.2018 21:01

Вы можете проверить это Q stackoverflow.com/questions/56999290/… @bennygenel

DevAS 12.07.2019 04:25
Ответ принят как подходящий

Вы можете использовать это ниже, https://reactnavigation.org/docs/en/stack-navigator.html

//Screen1 Stack.

const Screen1 = createStackNavigator ({
    Home: {
        screen: Home,
        navigationOptions: {
            header: null //Need to set header as null.
        }
    }
});

//Screen2 Stack

const Screen2 = createStackNavigator ({
    Profile: {
        screen: Profile,
        navigationOptions: {
            header: null  //Need to set header as null.
        }
    }
});


let Tabs = createMaterialTopTabNavigator({
    Screen1:{
      screen: Screen1 //Calling Screen1 Stack.
    },
    Screen2:{
      screen: Screen2 //Calling Screen2 Stack.
    }
},{ tabBarPosition: 'bottom' }) //this will set the TabBar at Bottom of your screen.

let Stack = createStackNavigator({
    tabs:{
      screen: Tabs, //You can add the NavigationOption here with navigation as parameter using destructuring.
      navigationOptions: ({navigation})=>{
       //title: (navigation.state.routes[navigation.state.index])["routeName"]  
       //this will fetch the routeName of Tabs in TabNavigation. If you set the routename of the TabNavigation as your Header. 

       //use the following title property,this will fetch the current stack's routeName which will be set as your header in the TabBar.

        //title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName

       //you can use switch case,on matching the route name you can set title of the header that you want and also header left and right icons similarly.

        switch ((navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName) {
            case "Screen1":
                return {
                    title: "Home", 
                    headerLeft: (<Button
                        onPress = {()=> alert("hi")}
                        title = "Back"
                        color = "#841584"
                        accessibilityLabel = "Learn more about this purple button"
                    /> ),
                    headerRight: <Button title= "Right"/>
                }
            case "Screen2":
                return { 
                    title: "Profile",
                    headerLeft: (<Button
                        onPress = {()=> alert("hi")}
                        title = "Back"
                        color = "#841584"
                        accessibilityLabel = "Learn more about this purple button"
                    /> ),
                    headerRight: <Button title= "Right"/>
                }
            default:
                return { title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName }
        }
      }
    },
    otherScreen:{
      screen: OtherScreen
    }
})

// navigationOptions

  navigationOptions: ({navigation})=>{
   //title: (navigation.state.routes[navigation.state.index])["routeName"]  
   //this will fetch the routeName of Tabs in TabNavigation. If you set the routename of the TabNavigation as your Header. 

   //use the following title property,this will fetch the current stack's routeName which will be set as your header in the TabBar.

    //title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName

    switch ((navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName) {
        case "Screen1":
            return {
                title: "Home", 
                headerLeft: (<Button
                    onPress = {()=> alert("hi")} //Here you can able to set the back behaviour.
                    title = "Back"
                    color = "#841584"
                    accessibilityLabel = "Learn more about this purple button"
                /> ),
                headerRight: <Button title= "Right"/>
            }
        case "Screen2":
            return { 
                title: "Profile",
                headerLeft: (<Button
                    onPress = {()=> alert("hi")} 
                    title = "Back"
                    color = "#841584"
                    accessibilityLabel = "Learn more about this purple button"
                /> ),
                headerRight: <Button title= "Right"/>
            }
        default:
            return { title: (navigation.state.routes[navigation.state.index]["routes"])[(navigation.state.routes[navigation.state.index]["index"])].routeName }
    }
  }    

//alert(navigation.state)

{

    "routes":[
        {
            "key":"Screen1",
            "routeName":"Screen1",
            "routes":[
                {
                    "key":"Home",
                    "routeName":"Home",
                }
            ],
            "index":0,
            "isTransitioning":false,
            "key":"id-1530276062643-0"
        },
        {
            "key":"Screen2",
            "routeName":"Screen2",
            "routes":[
                {
                    "key":"Profile",
                    "routeName":"Profile",
                }
            ],
            "index":0,
            "isTransitioning":false,
            "key":"id-1530276062643-0"
        }
    ],
    "index":0,
    "isTransitioning":false,
    "routeName":"tabs",
    "key":"id-1530276062643-0"

}

//(navigation.state.routes[navigation.state.index provided)["routeName "] //(navigation.state.routes[navigation.state.index provided["routes" ])[(navigation.state.routes[navigation.state.indexicious["index" ])] ].routeName

this will give the current route name of the tab inside StackNavigation.

Приведенный выше код установит заголовок в заголовке корневого стека, где находится TabBar, в качестве первого маршрута, поэтому мы устанавливаем заголовок как null для отдельного стека в TabBar. Использование этого способа обеспечит анимацию при переключении экранов в TabBar, поскольку заголовок останется статическим.

Вы можете найти рабочую копию здесь https://www.dropbox.com/s/jca6ssn9zkzh9kn/Archive.zip?dl=0

Загрузите это и выполните следующее.

  1. npm install //to get dependencies

  2. react-native upgrade //to get android and ios folder

  3. react-native link //to link dependencies and libraries

  4. react-native run-ios (or) react-native run-android

    вы можете использовать вышеуказанное, дайте мне знать, если таковые имеются.

@Anthony De Smet, я обновил свой ответ, пожалуйста, проверьте его и дайте мне знать, исправляет ли это ваше.

dhivya s 30.06.2018 10:11

Хотя это работает, вы не можете изменить заголовок заголовка на что-либо иное, кроме routeName, не так ли? например Заголовок «Экран2» всегда будет «Экран2», или вы можете как-то установить его следующим образом: «Заголовок экрана для подпункта XXX»?

Chris 30.06.2018 11:28

@Chris, я обновил свой ответ в соответствии с вашими требованиями. Вы можете добиться этого, используя переключатель case в navigationOptions, который соответствует нашему routeName, чтобы установить заголовок, headerLeft, headerRight и т. д. Таким образом мы можем установить заголовок независимо от routeName.

dhivya s 02.07.2018 14:35

@dhivyas У меня также есть вопрос о вложенных вкладках прямо здесь stackoverflow.com/questions/55888800/…, как вы думаете, мы можем решить с помощью аналогичного решения?

angry kiwi 10.05.2019 12:08

Вы можете проверить это Q stackoverflow.com/questions/56999290/… @dhivyas

DevAS 12.07.2019 04:24

Как я могу это сделать с помощью react-navigation 5?

hakiko 24.02.2020 20:09

Здесь есть хороший пример в официальном документе: reactnavigation.org/docs/screen-options-resolution

Shawn Dao 17.11.2020 14:59

В AppNavigation.js // или там, где вы определили свои маршруты.

let Tabs = createBottomTabNavigator({
    screen1: Screen1,
    screen2: Screen2
})

let Stack = createStackNavigator({
    tabs: Tabs
    otherScreen: OtherScreen
},{
    headerMode:"float", //Render a single header that stays at the top and animates as screens are changed. This is a common pattern on iOS.
    headerTransitionPreset:"fade-in-place" //this will give a slight transition when header icon change.
  }
)

В Screen1.js

class Screen1 extends Component {
 
 static navigationOptions = ({ navigation }) => {

    return {
      ...
      headerLeft: <HeaderLeftImage navigation = {navigation} image = {"Image_For_Screen1"}/>,
      ...
    }
  }
  ...
}

ПРИМЕЧАНИЕ: Вы можете добавить title, headersStyle, headerRight таким же образом, прочтите эту ссылку конфигурация заголовка для более подробной информации.

В Screen2.js делаем аналогично Screen1

class Screen2 extends Component {
 
 static navigationOptions = ({ navigation }) => {

    return {
      ...
      headerLeft: <HeaderLeftImage navigation = {navigation} image = {"Image_For_Screen2"}/>,
      ...
    }
  }
  ...
}

Header.js

export const HeaderLeftImage = (props) => (
    <View style = {{
       'add styles'
    }}>
        <TouchableOpacity onPress = {() => {"Add action here" }}>
            <Image source = {props.image} resizeMethod='resize' style = {{ height: 30, width: 90, resizeMode: 'contain' }} />
        </TouchableOpacity>
    </View>
)

Надеюсь, это поможет, если у вас есть какие-либо сомнения относительно кода, не стесняйтесь спрашивать.

Вы можете проверить это Q stackoverflow.com/questions/56999290/… @RajatGupta

DevAS 12.07.2019 04:25
const RootStack = createStackNavigator(
  {
    Home: {screen: HomeScreen},
    FilterScreen: createMaterialTopTabNavigator({
        Tab1: {screen:Tab1Screen},
        Tab2: {screen: Tab2Screen},
    }),
  },
  {
    mode: 'modal',
    headerMode: 'none',
  }
);


render() {
    return <RootStack/>;
}

Вы можете проверить это Q stackoverflow.com/questions/56999290/… @MahdiBashirpour

DevAS 12.07.2019 04:26

Если вы используете React Navigation <2, то есть ~ 1.5. * Вы можете установить это так.

const Tabs = TabNavigator({
    Tab1:{
         screen: Tab1,
         navigationOptions: ({navigation}) => {
            return { title: "Tab 1 Heading", tabBarLabel:"Tab 1 "}
    },
    }
    Tab2:{
       screen: Tab2
       navigationOptions: ({navigation}) => {
         return { title: "Tab 2 Heading", tabBarLabel:"Tab 2 "}
    }
    }
})

const Stack = StackNavigator({
    tabs: {
      screen: Tabs
      navigationOptions: ({navigation}) => {
        return { title: "Stack"}
      }
    },
    otherScreen: Page
})

Я не уверен, почему они удалили эту функцию, когда вы попробуете то же самое, она не будет работать в последней версии реакции-навигации. Кажется, теперь ключ титровального объекта используется как резервный.

Это может быть полезно некоторым пользователям. При желании вы также можете попробовать понизить версию.

Я обновил React Navigation для своего проекта, думаю, этот способ будет кому-то полезен.

const Tabs = TabNavigator({
     Tab1:{
        screen: Tab1,
        navigationOptions: ({navigation}) => {
           return { tabBarLabel:"Tab 1 "}
        }},
     Tab2:{
        screen: Tab2
        navigationOptions: ({navigation}) => {
           return { tabBarLabel:"Tab 2 "}
        }}
});
Tabs.navigationOptions = ({navigation})=>{
    const { routeName } = navigation.state.routes[navigation.state.index]; //This gives current route
    switch(routeName){
         case "Tab1":
           headerTitle = "Tab 1";
           break;
         case "Tab1":
           headerTitle = "Tab 1";
           break;
    }

 return {
   headerTitle: headerTitle
}
}

Вы можете проверить это Q stackoverflow.com/questions/56999290/… @AshwinMothilal

DevAS 12.07.2019 04:25

Вы можете просто использовать свойство headerShown: false. Я создал рабочий пример версии react-navigation: 5.x.x, вложив разные навигаторы: stack, drawer и bottom-tabs.

Вот ссылка на github:https://github.com/mvpbuddy/react-native-nested-navigators/

Для более подробного обзора вы можете проверить мой блог:https://mvpbuddy.io/blog/detail/how-to-build-an-app-with-nested-stack-drawer-bottom-tab-navigators

Другие вопросы по теме