BottomNavigationView - Как избежать воссоздания фрагментов и их повторного использования

Я хотел бы создать нижнюю панель навигации в своем проекте. У каждого представления есть собственный фрагмент. Проблема заключается в том, что каждый раз, когда я нажимаю кнопку, чтобы изменить вид, например, из recents в избранное, он создает новый фрагмент с совершенно новыми состояниями (например, положение прокрутки, текст изменен, какой бы фрагмент не содержал). Я знаю, что в официальной документации на Android было написано, что нижняя панель навигации должна перезапускать состояния задачи, но я думаю, что это слишком неудобно для пользователей. Я хотел бы иметь аналогичную функциональность, такую как instagram, которую вы меняете из фида, чтобы исследовать и обратно, чтобы прокормить позицию прокрутки, в которой все кэширование изображений сохраняется. Я пробовал практически все способы решить эту проблему, единственное, что сработало, - это установить видимость GONE и установить видимость VISIBLE в соответствии с ситуацией, но я понимаю, что это НЕ ПРАВИЛЬНО, должен быть лучший способ сделать это, и я не говорю о ручном сохранение необходимых экземпляров. Я последовал почти каждому учебнику о донных фрагментах, но интересно то, что никто не заинтересован в том, чтобы использовать его, не вызывая новых каждый раз.

enter image description here

enter image description here

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, FirstFragment.newInstance());
fragmentTransaction.commit();

bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Fragment fragment = null;
        switch (item.getItemId()) {
            case R.id.menu_dialer:
                fragment = FirstFragment.newInstance();
                break;
            case R.id.menu_email:
                fragment = SecondFragment.newInstance();
                break;
            case R.id.menu_map:
                fragment = ThirdFragment.newInstance();
                break;
        }
        if (fragment != null) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.frameLayout, fragment);
            fragmentTransaction.commit();
        }
        return true;
    }
});

Я также пробовал решения onAttach и Deattach, но снова не добился успеха.

VIDEO LINK: новая, я попробовал версию Nino Handler, она работает только при нажатии на ту же кнопку фрагмента

Может быть, это связано с тем, что я использую версию canary или что-то не так в моих зависимостях от gradle? enter image description here

NEW UPDATES:

НОВЫЕ ОБНОВЛЕНИЯ:

public class MainActivity extends AppCompatActivity {

    private TextView mTextMessage;


    private static final String TAG_FRAGMENT_ONE = "fragment_one";
    private static final String TAG_FRAGMENT_TWO = "fragment_two";

    private FragmentManager fragmentManager;
    private Fragment currentFragment;

    String TAG = "babken";
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        Fragment fragment = null;
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                   fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FragmentFirst.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);

                    break;
                case R.id.navigation_dashboard:

                     fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = FragmentSecond.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);

                    break;
            }
            return true;

        }
    };

    private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
        if (!fragment.equals(currentFragment)) {
            fragmentManager
                    .beginTransaction()
                    .replace(R.id.armen, fragment, tag)
                    .commit();
            currentFragment = fragment;
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
        if (fragment == null) {
            fragment = FragmentFirst.newInstance();
        }
        replaceFragment(fragment, TAG_FRAGMENT_ONE);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    }

}

Ответы

Ответ 1

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

Ответ 2

У меня была похожая проблема, но этот код решил мою проблему.

public class MainActivity extends AppCompatActivity {

final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
    fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
    fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();

}


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

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
                fm.beginTransaction().hide(active).show(fragment1).commit();
                active = fragment1;
                return true;

            case R.id.navigation_dashboard:
                fm.beginTransaction().hide(active).show(fragment2).commit();
                active = fragment2;
                return true;

            case R.id.navigation_notifications:
                fm.beginTransaction().hide(active).show(fragment3).commit();
                active = fragment3;
                return true;
        }
        return false;
    }
};

Надеюсь это поможет.

Источник: BottomNavigationView с фрагментами (без воссоздания фрагмента).

Ответ 3

Я не буду хранить экземпляры фрагментов по всему миру. Вместо этого добавьте тег к фрагменту при их создании

getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, new PlaceholderFragment(), TAG_PLACEHOLDER)
            .commit();

Тогда вы всегда можете получить его так:

Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
    if (fragment == null) {
        fragment = new PlaceholderFragment();
    }
    getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, fragment, TAG_PLACEHOLDER)
            .commit();

ОБНОВЛЕНИЕ: я обновил свой ответ и предоставил полное решение:

private static final String TAG_FRAGMENT_ONE = "fragment_one";
private static final String TAG_FRAGMENT_TWO = "fragment_two";
private static final String TAG_FRAGMENT_THREE = "fragment_three";

private FragmentManager fragmentManager;
private Fragment currentFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // instantiate the fragment manager
    fragmentManager = getSupportFragmentManager();

    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
    if (fragment == null) {
        fragment = FirstFragment.newInstance();
    }
    replaceFragment(fragment, TAG_FRAGMENT_ONE);

    bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
    bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            Fragment fragment = null;
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    // I'm aware that this code can be optimized by a method which accepts a class definition and returns the proper fragment
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FirstFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);
                    break;
                case R.id.menu_email:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = SecondFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);
                    break;
                case R.id.menu_map:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_THREE);
                    if (fragment == null) {
                        fragment = ThirdFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_THREE);
                    break;
            }
            return true;
        }
    });
}

private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
    if (!fragment.equals(currentFragment)) {
        fragmentManager
            .beginTransaction()
            .replace(R.id.frameLayout, fragment, tag)
            .commit();
        currentFragment = fragment;
    }
}

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ: Если вы хотите быть уверенным, что состояния фрагментов не изменяются, и если вы также хотите, чтобы иметь возможность прокручивать фрагменты, вы должны рассмотреть возможность использования ViewPager с FragmentStatePagerAdapter и изменить текущий фрагмент в адаптере с каждым событием click

Ответ 4

Во всех предыдущих ответах используется fragmentTransaction.replace(...). Это заменит текущий фрагмент, уничтожив его (что вызывает проблему). Поэтому все эти решения на самом деле не будут работать.

Это самое близкое, что я мог бы решить как решение этой проблемы:

private void selectContentFragment(Fragment fragmentToSelect)
{
    FragmentTransaction fragmentTransaction = this.getSupportFragmentManager().beginTransaction();

    if (this.getSupportFragmentManager().getFragments().contains(fragmentToSelect)) {
        // Iterate through all cached fragments.
        for (Fragment cachedFragment : this.getSupportFragmentManager().getFragments()) {
            if (cachedFragment != fragmentToSelect) {
                // Hide the fragments that are not the one being selected.
                fragmentTransaction.hide(cachedFragment);
            }
        }
        // Show the fragment that we want to be selected.
        fragmentTransaction.show(fragmentToSelect);
    } else {
        // The fragment to be selected does not (yet) exist in the fragment manager, add it.
        fragmentTransaction.add(R.id.fragment_container, fragmentToSelect);
    }

    fragmentTransaction.commit();
}

Чтобы сделать эту работу, вы должны отслеживать фрагменты в массиве (или в отдельных переменных) в своей деятельности. Я для справки предварительно создавал все фрагменты в SparseArray.

Ответ 5

Вы можете использовать методы attach() и detach():

private FirstFragment firstFragment = FirstFragment.newInstance();
private SecondFragment secondFragment= SecondFragment.newInstance();
private ThirdFragment thirdFragment = ThirdFragment.newInstance();

navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    changeFragment(firstFragment, "firstFragment");
                    return true;
                case R.id.menu_email:
                    changeFragment(secondFragment, "secondFragment");
                    return true;
                case R.id.menu_map:
                    changeFragment(thirdFragment, "thirdFragment");
                    return true;
            }
            return false;
        }
    });

public void changeFragment(Fragment fragment, String tagFragmentName) {

    FragmentManager mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
    if (currentFragment != null) {
        fragmentTransaction.detach(currentFragment);
    }

    Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
    if (fragmentTemp == null) {
        fragmentTemp = fragment;
        fragmentTransaction.add(R.id.content, fragmentTemp, tagFragmentName);
    } else {
        fragmentTransaction.attach(fragmentTemp);
    }

    fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
    fragmentTransaction.setReorderingAllowed(true);
    fragmentTransaction.commitNowAllowingStateLoss();
}

Ответ 6

Я написал функцию расширения Kotlin для класса FragmentManager

fun FragmentManager.switch(containerId: Int, newFrag: Fragment, tag: String) {

    var current = findFragmentByTag(tag)
    beginTransaction()
        .apply {

            //Hide the current fragment
            primaryNavigationFragment?.let { hide(it) }

            //Check if current fragment exists in fragmentManager
            if (current == null) {
                current = newFrag
                add(containerId, current!!, tag)
            } else {
                show(current!!)
            }
        }
        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .setPrimaryNavigationFragment(current)
        .setReorderingAllowed(true)
        .commitNowAllowingStateLoss()
}

Это может быть supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG) из onNavigationItemSelected

Ответ 7

Как насчет этого. Вы объявляете фрагменты в классе.

Fragment firstFragment,secondFragment,thirdFragment;

то в корпусе коммутатора вы можете указать код:

switch (item.getItemId()) {
    case R.id.menu_dialer:
        if(firstFragment != null) {
            fragment = firstFragment;
        }else{
            fragment = FirstFragment.newInstance();
        }
        break;
    case R.id.menu_email:
        // the same ...
        break;
    case R.id.menu_map:
        //the same ...
        break;
}

Ответ 8

Создайте три фрагмента в качестве членов класса и повторно используйте их.

public class MainActivity extends AppCompatActivity {
    private final Fragment mFirstFragment = new FirstFragment();
    private final Fragment mSecondFragment = new SecondFragment();
    private final Fragment mThirdFragment = new ThirdFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...... 
        ......
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, mFirstFragment);
        fragmentTransaction.commit();

        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (bottomNavigationView.getSelectedItemId() != item.getItemId()) {
                    switch (item.getItemId()) {
                        R.id.menu_dialer:
                            replaceFragment(mFirstFragment);
                            break;
                        case R.id.menu_email:
                            replaceFragment(mSecondFragment);
                            break;
                        case R.id.menu_map:
                            replaceFragment(mThirdFragment);
                            break;
                    }
                }
                return true;
            }
        });
    }

    private void replaceFragment(Fragment fragment) {
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment);
        fragmentTransaction.commit();
    }

}

Ответ 9

Решение с скрытием/отображением фрагментов плохо, потому что жрёт память. А также, если в других фрагментах идет сложная работа (например, загрузка данных с помощью API), все это загружается одновременно, поэтому при запуске приложения это займет много времени. Я думаю, что это хорошая идея, чтобы сохранить фрагменты только тогда, когда вы нажимаете их. В случае повторного перехода - существующий объект называется. Скорее всего, есть какие-то подводные камни, напишите, если встретите;) (п.с. я не носитель языка)

Основная деятельность

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

private Fragment fragment1;
private Fragment fragment2;
private Fragment fragment3;

private FragmentManager fragmentManager;

private RelativeLayout fullLayout;
private BottomNavigationView bottomNavigationMenuView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fullLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.activity_body, null);
    super.setContentView(R.layout.activity_body);


    // Bottom navigation
    initBottomNavigation();

    // Init fragments
    initFragments();
}

private void initFragments() {
    fragmentManager = getSupportFragmentManager();
    fragment1 = new Fragment1();
    replaceFragment(fragment1);
}

protected void initBottomNavigation() {
    bottomNavigationMenuView = findViewById(R.id.bottomNavigation);
    bottomNavigationMenuView.setOnNavigationItemSelectedListener(this);
}

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
    switch (menuItem.getItemId()) {
        case R.id.bottom_menu_home:
            if (fragment1 == null) {
                fragment1 = new Fragment1();
            }
            replaceFragment(fragment1);
            return true;

        case R.id.bottom_menu_add:
            if (fragment2 == null) {
                fragment2 = new Fragment2();
            }
            replaceFragment(fragment2);
            return true;

        case R.id.bottom_menu_profile:
            if (fragment3 == null) {
                fragment3 = new Fragment3();
            }
            replaceFragment(fragment3);
            return true;
    }

    return true;
}

private void replaceFragment(@NonNull Fragment fragment) {
    fragmentManager
            .beginTransaction()
            .replace(R.id.mainContainer, fragment, fragment.getTag())
            .commit();
}

Ответ 10

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

Поэтому я изменил его код и получил решение, используя следующий код:

 private fun selectContentFragment(fragmentToSelect: Fragment) {
        val fragmentTransaction = fragmentManager?.beginTransaction()
        if (fragmentManager?.fragments?.contains(fragmentToSelect)!!) {
            // Show the fragment that we want to be selected.
            fragmentTransaction?.show(fragmentToSelect)
        } else {
            // The fragment to be selected does not (yet) exist in the fragment manager, add it.
            fragmentTransaction?.add(R.id.container, fragmentToSelect)
        }
        // Iterate through all cached fragments.
        for (cachedFragment in fragmentManager?.fragments!!) {
            if (cachedFragment !== fragmentToSelect) {
                // Hide the fragments that are not the one being selected.
                // Uncomment following line and change the name of the fragment if your host isn't an activity and a fragment otherwise whole view will get hidden.
                // if (!cachedFragment.toString().contains("HomeContainerFragment"))

                fragmentTransaction?.hide(cachedFragment)
            }
        }
        fragmentTransaction?.commit()
    }

Это потратило мои несколько часов. Надеюсь, это поможет другим!