Как скопировать логику навигации в приложении YouTube

Я хочу реализовать в своем приложении логику навигации, как в приложении Youtube. (BottomNavigationView + управление фрагментами). Я хочу этого, потому что эти фрагменты тяжелые, поэтому я хочу, чтобы они были лениво инициализированы, а затем сохранены в бэкстеке, я чувствую, что YouTube делает это таким образом. Я реализовал BottomNagivationView, но у меня проблемы с управлением фрагментами.

Мой код:

bottomNavigationView.setOnTabSelectedListener { position, _ -> 
    setFragment(OnlinePageFragment.Page.values()[position])
}

где страницы перечисляются

enum class Page(index: Int, val klass: Class<*>) {
        ONE(0, OnePageFragment::class.java),
        TWO(1, TwoPageFragment::class.java),
        THREE(2, ThreePageFragment::class.java)
    }

и вот моя функция setFragment

fun setFragment(page: OnlinePageFragment.Page) {
    var fragment: Fragment? = supportFragmentManager.findFragmentByTag(page.klass.name)
    val tag = page.klass.name

    if (fragment == null)
        fragment = OnlinePageFragment.newInstance(page, null)

    val ft = supportFragmentManager.beginTransaction()
    with(ft) {
        replace(R.id.fragmentContainer, fragment, tag)
        addToBackStack(tag)
        commit()
    }

}

override fun onBackPressed() {
    if (supportFragmentManager.backStackEntryCount == 1) finish()
    else super.onBackPressed()
}

И это работает, но не так хорошо, как приложение YouTube. Приложение YouTube имеет какое-то волшебное поведение, т.е. оно сохраняет только одну транзакцию для каждого фрагмента, в то время как мое приложение позволяет создавать «бесконечный» бэкстек транзакций. У вас есть идеи, как это работает в приложении YouTube?


person Stanislaw Baranski    schedule 07.08.2017    source источник


Ответы (3)


Без использования пейджера вы можете управлять им. Я реализовал Пожалуйста, проверьте это. https://github.com/sandeshsk/BackStackFragmentRedirectsToHome

Пожалуйста, обновите, если есть какие-либо проблемы.

Это метод, который назначает фрагмент

public void addFragment(FragmentManager fragmentManager,
                               Fragment fragment,
                               int containerId,boolean isFromHome){

    fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);

    FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
    if(isFromHome){
        fragmentTransaction.replace(containerId,fragment);
    }else{
        fragmentTransaction.add(new HomeFragment(),"Home");
        fragmentTransaction.addToBackStack("Home");
    }
    fragmentTransaction.replace(containerId,fragment).commit();

}

Это ваш прослушиватель элементов навигации

 private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
               if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
                    addFragment(getSupportFragmentManager(), new HomeFragment(), R.id.frame, true);
                }else{
                    getSupportFragmentManager().popBackStack();
                }
                return true;
            case R.id.navigation_dashboard:
                addFragment(getSupportFragmentManager(),new DashboardFragment(),R.id.frame,false);
                return true;
            case R.id.navigation_notifications:
                addFragment(getSupportFragmentManager(),new NotificationFragment(),R.id.frame,false);
                return true;
            case R.id.navigation_setting:
                addFragment(getSupportFragmentManager(),new SettingFragment(),R.id.frame,false);
                return true;
        }
        return false;
    }
};

Метод onBackPressed

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>0){
        navigation.setSelectedItemId(R.id.navigation_home);
    }else {
        super.onBackPressed();
    }
}
person san    schedule 30.06.2018
comment
Я принял этот ответ, так как он показывает фрагменты, как и ожидалось. Но есть проблема. Каждый раз, когда я переключаю фрагмент, HomeFragment создается дважды. - person Stanislaw Baranski; 17.07.2018

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

Корневые фрагменты будут хранить фрагменты контента внутри своих ChildFragmentManagersи реализовывать логику навигации между фрагментами контента.

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

UPD №1
Убедитесь, что корневые фрагменты не уничтожаются внутри ViewPager, используйте setOffscreenPageLimit()

UPD #2. Если вы не хотите использовать ViewPager, вы можете использовать код из этого ответа для управления корневыми фрагментами.

person Ufkoku    schedule 07.08.2017
comment
ViewPager — это моя текущая реализация, и я хотел изменить ее, т.к. слишком тяжело загружать 3 фрагмента одновременно. - person Stanislaw Baranski; 08.08.2017
comment
@StachuBarański, если слишком тяжело загружать 3 фрагмента при запуске, сначала загрузите только первый, а другие заполняются данными, если пользователь проводит пальцем, чтобы увидеть их. - person hardartcore; 08.08.2017
comment
@hardartcore Я не могу этого сделать, так как ViewPager нужно загрузить все 3 фрагмента, чтобы они были видны во время смахивания. - person Stanislaw Baranski; 17.07.2018
comment
@StachuBarański, вы можете это сделать, просто прослушайте setUserVisibleHint(boolean b) и активируйте загрузку контента, когда b равно true (и он не загружается, не загружается или в некоторых других условиях) - person Ufkoku; 17.07.2018
comment
Хорошо, тогда полезно знать :) В любом случае, я выбрал управление корневыми фрагментами, и оно отлично работает (фрагмент создается только один раз, onCreateView вызывается только один раз, и все инициализируется лениво). Смотрите принятый ответ. - person Stanislaw Baranski; 17.07.2018

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

Вот как:

private void setPage(Page page) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    String tag = page.name();

    FragmentTransaction transaction = fragmentManager.beginTransaction();

    // detach everything
    for (Fragment fragment : fragmentManager.getFragments()) {
        transaction.detach(fragment);
    }

    // Retrieve fragment instance, if it was already created
    Fragment fragment = fragmentManager.findFragmentByTag(tag);
    if (fragment == null) { // If not, crate new instance and add it
        try {
            fragment = (Fragment) page.clazz.newInstance();
            transaction.add(R.id.frame, fragment, tag);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    } else { // otherwise just attach it
        transaction.attach(fragment);
    }
    transaction.commit();
}

onBackPressed реализация

@Override
public void onBackPressed() {
    if (!getSupportFragmentManager().findFragmentByTag(Page.HOME.name()).isDetached()) {
        super.onBackPressed();
    } else {
        navigation.setSelectedItemId(R.id.navigation_home);
    }
}
person Stanislaw Baranski    schedule 17.07.2018