BottomNavigationView - Как избежать воссоздания фрагментов и их повторного использования
Я хотел бы создать нижнюю панель навигации в своем проекте. У каждого представления есть собственный фрагмент. Проблема заключается в том, что каждый раз, когда я нажимаю кнопку, чтобы изменить вид, например, из recents в избранное, он создает новый фрагмент с совершенно новыми состояниями (например, положение прокрутки, текст изменен, какой бы фрагмент не содержал). Я знаю, что в официальной документации на Android было написано, что нижняя панель навигации должна перезапускать состояния задачи, но я думаю, что это слишком неудобно для пользователей. Я хотел бы иметь аналогичную функциональность, такую как instagram, которую вы меняете из фида, чтобы исследовать и обратно, чтобы прокормить позицию прокрутки, в которой все кэширование изображений сохраняется. Я пробовал практически все способы решить эту проблему, единственное, что сработало, - это установить видимость GONE и установить видимость VISIBLE в соответствии с ситуацией, но я понимаю, что это НЕ ПРАВИЛЬНО, должен быть лучший способ сделать это, и я не говорю о ручном сохранение необходимых экземпляров. Я последовал почти каждому учебнику о донных фрагментах, но интересно то, что никто не заинтересован в том, чтобы использовать его, не вызывая новых каждый раз.
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?
НОВЫЕ ОБНОВЛЕНИЯ:
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()
}
Это потратило мои несколько часов. Надеюсь, это поможет другим!