Компонент архитектуры навигации Android - получить текущий видимый фрагмент

95

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

val fragment:MyFragment = supportFragmentManager.findFragmentByTag(tag):MyFragment

Теперь в моем основном макете деятельности есть что-то вроде:

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

Как я могу получить текущий отображаемый фрагмент с помощью компонента навигации? Делать

supportFragmentManager.findFragmentById(R.id.nav_host)

возвращает a, NavHostFragmentи я хочу получить показанный мной MyFragment.

Спасибо.

Алин
источник
4
Невозможно получить текущий фрагмент stackoverflow.com/a/50689587/1268507 Чего вы пытаетесь достичь? возможно, есть какое-то другое решение.
Alex
Мне нужно было из моего фрагмента запустить замысел камеры, сделать снимок и использовать полученное изображение. Для этого я начал деятельность и ждал результата в onActivityResultсвоей основной деятельности. Так как получить фрагмент не удалось, я просто переместил все это в сам фрагмент и вроде работает.
Alin
Вы хотите выполнить операцию с этим объектом-фрагментом. Или просто хотите, какой фрагмент показан?
Ranjan

Ответы:

101

На данный момент мне удалось найти способ, и он выглядит следующим образом:

NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host);
navHostFragment.getChildFragmentManager().getFragments().get(0);

Если вы, конечно, знаете, что это первый фрагмент. Я все еще ищу способ без этого. Я согласен, что это не лучший способ, но на данный момент это должно быть что-то важное.

Андрей Тоадер
источник
6
Приятно видеть рабочее решение. Я отказался от этого и начал использовать общую модель представления между активностью и фрагментом. Это означает, что я могу передавать события между ними без необходимости обратных вызовов или прямого доступа.
Алин
Как упоминалось в этом выпуске отчета об ошибке issueetracker.google.com/issues/119800853, это неправильный метод. Пожалуйста, подумайте об изменении правильного ответа!
Николас Пьетри
2
Как уже упоминалось, это не решение, это обходной путь, пока не будет найдено правильное решение.
Андрей
1
Это работает, большое спасибо. Я попробовал getPrimaryNavigationFragment (), как было предложено в системе отслеживания ошибок, но это не помогло.
Джерри Ху
1
На основе этого решения я создал функцию kotlin, которая может проверять, имеет ли текущий фрагмент определенный тип (вместо идентификатора): navHostFragment!!.childFragmentManager.fragments.any { it.javaClass == fragmentClass && it.isVisible }работает очень похоже. Надеюсь, это кому-нибудь поможет!
П. Куиджперс,
49

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

navController.currentDestination?.getId()

Он возвращает идентификатор в файле навигации. Надеюсь это поможет.

slhddn
источник
Спасибо за ваш ответ. Мне нужно было получить фрагмент, чтобы я мог с ним взаимодействовать. Поскольку navController не может его вернуть, я закончил использовать общую модель просмотра для разговора между активностью и ее фрагментами
Алин
7
Я ожидал, что возвращенный идентификатор совпадает с идентификатором в nav xml, но это не так, поэтому, другими словами, вы не можете проверить navController.getCurrentDestination (). getId () == R.id.myfrag
Лерес Альдтай,
4
@LeresAldtai Соответствует идентификатору фрагмента в файле навигации.
slhddn 07
1
Спасибо @slhddn. Ваш комментарий помог мне и смог получить идентификатор из фрагмента и выполнить эту работу. Добавил сюда ответ с моим кодом на случай, если это будет полезно кому-то другому. Ура.
Франсислейни Кампос,
31

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

val navHost = supportFragmentManager.findFragmentById(R.id.main_nav_fragment)
    navHost?.let { navFragment ->
        navFragment.childFragmentManager.primaryNavigationFragment?.let {fragment->
                //DO YOUR STUFF
        }
    }
}
Николя Пьетри
источник
2
Этот метод теперь задокументирован в fragmentManager issueetracker.google.com/issues/119800853
Николас Пьетри,
это кажется небольшой проблемой. обработка навигации вверх, мне нужно настроить панель действий, чтобы получить вызов onSupportNaigateUp ()
filthy_wizard
18

Это кажется самым чистым решением. Обновление: изменена функция расширения на свойство. Спасибо Игорю Войде .

1. Создайте следующее расширение

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager

val FragmentManager.currentNavigationFragment: Fragment?
    get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()

2. Activityс NavHostFragmentиспользованием его как это:

val currentFragment = supportFragmentManager.currentNavigationFragment
Александар Илич
источник
2
Отлично - я бы это изменил - я бы определил расширение как свойство FragmentManager.currentNavigationFragment(а не как функцию)
Игорь Войда,
16

Использование addOnDestinationChangedListenerв деятельности, имеющейNavHostFragment

Для Java

 NavController navController= Navigation.findNavController(MainActivity.this,R.id.your_nav_host);

        navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
                Log.e(TAG, "onDestinationChanged: "+destination.getLabel());
            }
        });

Для Котлина

var navController :NavController=Navigation.findNavController(this, R.id.nav_host_fragment)
    navController.addOnDestinationChangedListener { controller, destination, arguments ->
        Log.e(TAG, "onDestinationChanged: "+destination.label);

    }
Мукул Бхардвадж
источник
8

это может быть поздно, но для тех, кто все еще ищет

val currentFragment = NavHostFragment.findNavController(nav_host_fragment).currentDestination?.id

тогда вы можете сделать что-то вроде

if(currentFragment == R.id.myFragment){
     //do something
}

обратите внимание, что это для котлина, Java-код должен быть почти таким же

МоТахир
источник
2
Вот код Java, stackoverflow.com/a/60539704/4291272
Фейсал Шейх
5
navController().currentDestination?.id

даст вам текущий Fragmentидентификатор, где

private fun navController() = Navigation.findNavController(this, R.id.navHostFragment)

этот идентификатор - это идентификатор, который вы указали в своем Navigation Graph XMLфайле под fragmentтегом. Вы также можете сравнить, currentDestination.labelесли хотите.

Абхишек Бансал
источник
4

Нашел рабочее решение.

private fun getCurrentFragment(): Fragment? {
    val currentNavHost = supportFragmentManager.findFragmentById(R.id.nav_host_id)
    val currentFragmentClassName = ((this as NavHost).navController.currentDestination as FragmentNavigator.Destination).className
    return currentNavHost?.childFragmentManager?.fragments?.filterNotNull()?.find {
        it.javaClass.name == currentFragmentClassName
    }
}
Томас Кун
источник
3

Из Activity, который использует NavHostFragment, вы можете использовать приведенный ниже код для получения экземпляра Active Fragment.

val fragmentInstance = myNavHostFragment?.childFragmentManager?.primaryNavigationFragment
Jzarsuelo
источник
3

Мое требование - получить текущий видимый фрагмент из родительской активности, мое решение

 private LoginFragment getCurrentVisibleFragment() {
        NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().getPrimaryNavigationFragment();
        FragmentManager fragmentManager = navHostFragment.getChildFragmentManager();
        Fragment loginFragment = fragmentManager.getPrimaryNavigationFragment();
        if(loginFragment instanceof LoginFragment){
            return (LoginFragment)loginFragment;
        }
        return null;
    }

Это может помочь кому-то с той же проблемой.

Клиффорд Пай
источник
есть Kotlin lang?
Игорь Ганапольский,
1
Это отлично работает для меня, не уверен, что с другими ответами? Может быть, раньше это не работало, но теперь работает как шарм.
Wess
2

В соответствии с ответом и комментарием @slhddn вот как я его использую и получаю идентификатор фрагмента из файла nav_graph.xml :

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    setSupportActionBar(findViewById(R.id.toolbar))

    val navController = findNavController(this, R.id.nav_host_fragment)

    ivSettingsCog.setOnClickListener {

        if (navController.currentDestination?.id == R.id.updatesFragment) // Id as per set up on nav_graph.xml file
        {
            navController.navigate(R.id.action_updatesFragment_to_settingsFragment)
        }

    }

}

}
Франсислейни Кампос
источник
1

Можете ли вы уточнить, getChildFragmentManager()позвонив по телефону

NavHostFragment fragment = supportFragmentManager.findFragmentById(R.id.nav_host);
MyFragment frag = (MyFragment) fragment.getChildFragmentManager().findFragmentByTag(tag);
Шиам Шабир Химель
источник
1

Сделать :

• в какой-то кнопке в вашем фрагменте:

val navController = findNavController(this)
  navController.popBackStack()

• В вашей деятельности onBackPressed ()

override fun onBackPressed() {
        val navController = findNavController(R.id.nav_host_fragment)
        val id = navController.currentDestination?.getId()
        if (id == R.id.detailMovieFragment){
            navController.popBackStack()
            return
        }

        super.onBackPressed()
    } 
Пауло Линьярес - Packapps
источник
1

Если вам нужен экземпляр вашего фрагмента, вы можете использовать:

(Активность) => supportFragmentManager.fragments.first().childFragmentManager.fragments.first()

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

Ailelame
источник
1

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

public Fragment getFunctionalFragment (String tag_name)
{
    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
    FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager();

    Fragment fragment=null;
    List fragment_list = navHostManager.getFragments();

    for (int i=0; i < fragment_list.size() ; i ++ )
    {
        if ( fragment_list.get(i) instanceof HomeFragment && tag_name.equals("frg_home")) {
            fragment = (HomeFragment) fragment_list.get(i);
            break;
        }
    }

    return fragment;
}
Картик
источник
1
Добро пожаловать в SO! Пожалуйста, прежде чем размещать свой ответ, проверьте его ... Код не понятен. Другая проблема: если вы просто приводите код, немного объясните свое решение, максимум, когда есть еще 16 ответов, поэтому вам следует объяснить плюсы своего ответа.
Дэвид Гарсия Бодего
1

Первый фрагмент во фрагменте navhost - это текущий фрагмент

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
if (navHostFragment != null) 
{Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);}
Эрен Тюфекчи
источник
Не могли бы вы подробнее рассказать о своем ответе? Может быть, объяснив свой код.
Двадцать
Ответы только на код не приветствуются. Объясните, почему и как ваш ответ решает проблему.
Игорь Ф.
1

Сначала вы получаете текущий фрагмент по идентификатору, затем вы можете найти фрагмент в зависимости от этого идентификатора.

int id = navController.getCurrentDestination().getId();
Fragment fragment = getSupportFragmentManager().findFragmentById(id);
Абу-Бакр
источник
0

Я объединил Андрей Toaders ответ и Mukul Bhardwajs ответ с OnBackStackChangedListener.

В качестве атрибута:

private FooFragment fragment;

В моем onCreate

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host);
FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager();
FragmentManager.OnBackStackChangedListener listener = () -> {
        List<Fragment> frags = navHostManager.getFragments();
        if(!frags.isEmpty()) {
            fragment = (FooFragment) frags.get(0);
        }
};
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
        if(destination.getId()==R.id.FooFragment){
            navHostManager.addOnBackStackChangedListener(listener);
        } else {
            navHostManager.removeOnBackStackChangedListener(listener);
           fragment = null;
        }
});

Таким образом я могу получить фрагмент, который будет отображаться позже. Можно даже зацикливаться fragsи проверять несколько фрагментов с помощью instanceof.

Xerusial
источник
0

Мне нужно сделать это, чтобы настроить нижний вид навигации, и я сделал это следующим образом:

val navController = Navigation.findNavController(requireActivity(), R.id.nav_tabs_home)
        val bottomNavBar: BottomNavigationView = nav_content_view
        NavigationUI.setupWithNavController(bottomNavBar, navController)
Роке Руэда
источник
0

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

Согласно исходному вопросу, если ваш NavHostFragmentопределяется как ...

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

Тогда в своей основной деятельности onCreate(..)...

nav_host.childFragmentManager.registerFragmentLifecycleCallbacks(
    object:FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentViewCreated(
            fm: FragmentManager, f: Fragment, v: View,
            savedInstanceState: Bundle?
        ) {
            // callback method always called whenever a
            // fragment is added, or, a back-press returns
            // to the start-destination
        }
    }
)

Другие методы обратного вызова могут быть переопределены в соответствии с вашими требованиями.

Мигельт
источник
0

Это решение будет работать в большинстве ситуаций. Попробуйте следующее:

val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment?.parentFragment as MainFragment

или это:

val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment as MainFragment
user2288580
источник
0

Этот код работает у меня

NavDestination current = NavHostFragment.findNavController(getSupportFragmentManager().getPrimaryNavigationFragment().getFragmentManager().getFragments().get(0)).getCurrentDestination();

switch (current.getId()) {
    case R.id.navigation_saved:
        // WRITE CODE
        break;
}
Фейсал Шейх
источник
0

Это работает для меня

(navController.currentDestination as FragmentNavigator.Destination).className 

Он вернет canonicalNameваш фрагмент

Inc
источник
Это похоже на Отражение
Игорь Ганапольский
0

это может быть поздно, но может помочь кому-то позже.

если вы используете вложенную навигацию, вы можете получить такой идентификатор в Kotlin

val nav = Navigation.findNavController(requireActivity(), R.id.fragment_nav_host).currentDestination

и это один из фрагментов в fragment_nav_host.xml

<fragment
    android:id="@+id/sampleFragment"
    android:name="com.app.sample.SampleFragment"
    android:label="SampleFragment"
    tools:layout="@layout/fragment_sample" />

и вы можете проверить все, что вам нужно, вот так

if (nav.id == R.id.sampleFragment || nav.label == "SampleFragment"){}
Payam Mf
источник
0

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

Следуй этим шагам:

  1. Создайте слушателя внутри фрагмента, который вы хотите получить экземпляр из активности onCreate ()

    public class HomeFragment extends Fragment {
    
    private OnCreateFragment createLIstener;
    
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        createLIstener.onFragmentCreated(this);
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        //findViews(root);
        return root;
    }
    
    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_main, menu);
    
    }
    
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId()==R.id.action_show_filter){
            //do something
        }
        return super.onOptionsItemSelected(item);
    }
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            createLIstener = (OnCreateFragment) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
            }
        }
    
        public interface OnCreateFragment{
            void onFragmentCreated(Fragment f);
        }
    }
    
  2. Реализуйте свой слушатель в хосте Fragment / Activity

    public class MainActivity extends AppCompatActivity implements HomeFragment.OnCreateFragment {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            BottomNavigationView navView = findViewById(R.id.nav_view);
           // Passing each menu ID as a set of Ids because each
           // menu should be considered as top level destinations.
            AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                    R.id.navigation_home,
                    R. ----)
            .build();
            NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
            NavigationUI.setupWithNavController(navView, navController);
        }
    
        @Override
        public void onFragmentCreated(Fragment f) {
            if (f!=null){
                H.debug("fragment created listener: "+f.getId());
                f.setHasOptionsMenu(true);
            }else {
                H.debug("fragment created listener: NULL");
            }
        }
    }
    

И это все, что вам нужно для работы. Хоп это помочь кому-то

Экклезиаст Панда
источник
0

Лучший способ, которым я мог разобраться с использованием 1 фрагмента, Kotlin, Navigation:

В файл макета добавьте тег: "android: tag =" main_fragment_tag ""

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:tag="main_fragment_tag"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

О вашей деятельности:

val navHostFragment = supportFragmentManager.findFragmentByTag("main_fragment_tag") as NavHostFragment
val mainFragment = navHostFragment.childFragmentManager.fragments[0] as MainFragment
mainFragment.mainFragmentMethod()
Patel Dhruval
источник
0
val fragment: YourFragment? = yourNavHost.findFragment()

Примечание: findFragmentфункция расширения находится в androidx.fragment.appпакете, и это общая функция

Нарек Айрапетян
источник
-2

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

//method in MainActivity
private static Fragment fragment;

public void setFragment(Fragment fragment){
this.fragment = fragment;
}

//And from your fragment class, you can call
((<yourActivity in which fragment present>)getActivity()).setFragment(<your fragment object>);

//if you want to set it directly;
 ((<yourActivity in which fragment present>)getActivity()).fragment=<your fragment object>;

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

Ранджан
источник
2
Спасибо за ваше время. Я искал что-то для использования в самом компоненте навигации.
Alin