在选择的底部导航视图项目上重新创建片段
Posted
技术标签:
【中文标题】在选择的底部导航视图项目上重新创建片段【英文标题】:Fragment re-created on bottom navigation view item selected 【发布时间】:2017-07-10 19:34:45 【问题描述】:以下是我选择的底部导航视图项的代码
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener()
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
Fragment fragment = null;
switch (item.getItemId())
case R.id.action_one:
// Switch to page one
fragment = FragmentA.newInstance();
break;
case R.id.action_two:
// Switch to page two
fragment = FragmentB.newInstance();
break;
case R.id.action_three:
// Switch to page three
fragment = FragmentC.newInstance();
break;
getSupportFragmentManager().beginTransaction().replace(R.id.container,fragment,"TAG").commit();
return true;
);
现在我的问题是每次重新创建片段时,不希望每次我也尝试添加 addToBackStack(null) 时都重新创建片段,但这种情况下,按下后退按钮会不断从堆栈中弹出片段,而我没有'不想。
有没有办法在选择的底部导航栏上显示片段而不重新创建片段
【问题讨论】:
为避免这种情况,我创建了一个地图:Map<int, Fragment>
,每个片段都有一个静态 ID。我在Activity
的onCreate
中创建所有片段。但不建议这样做。您可以在backstack
中添加片段,并使用Fragment.class.getSimpleName()
之类的唯一标签,然后使用findFragmentByTag
找到它并再次提交。请务必将每个Fragment
添加一次到backstack
嗨,Raphael,正如我所说,我不想将它添加到 backstack,因为它会干扰背压实现
为此,您可以在 Activity
中覆盖 onBackPressed()
。评论super.onBackPressed()
没有,但是如果用户执行操作,除了这些之外还有一些片段将被添加到堆栈中。我只是不想同时将与底部导航视图关联的片段添加到 backstack 不想重新创建它们
没有点击底部导航栏项目,看它不会一次又一次地创建片段
【参考方案1】:
使用支持库 v26,您可以做到这一点
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment curFrag = mFragmentManager.getPrimaryNavigationFragment();
if (curFrag != null)
fragmentTransaction.detach(curFrag);
Fragment fragment = mFragmentManager.findFragmentByTag(tag);
if (fragment == null)
fragment = new YourFragment();
fragmentTransaction.add(container.getId(), fragment, tag);
else
fragmentTransaction.attach(fragment);
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
【讨论】:
完整的例子你可以看看这个 git repo github.com/okaybroda/FragmentStateManager 当您想在来回移动时保持状态/列表位置等时,您认为这是处理三个目的地之间导航的最佳方式吗?使用这种方法有什么注意事项吗?可能的内存泄漏或任何需要注意的事情?我还没有 100% 了解生命周期 这样做会更好还是以某种方式恢复实例状态是我真正想知道的! 它似乎没有在内存中保存片段;它只是以某种方式使页面片段看起来分层(page1 在 page2 下方查看)。 使用show
hide
而不是attach
detach
效果更好。它不会重新启动片段的生命周期【参考方案2】:
我遇到了同样的问题,最后我找到了解决方案,你可以试试这个代码。这对我有用。
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.FrameLayout;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
public BottomNavigationView bv;
public home_fragment home;
public account_fragment afrag;
public other_fragment other;
public FrameLayout fr;
android.support.v4.app.Fragment current;
//public FragmentTransaction frt;
public static int temp=0;
final Fragment fragment11 = new account_fragment();
final Fragment fragment22 = new home_fragment();
final Fragment fragment33 = new other_fragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment11;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bv=(BottomNavigationView) findViewById(R.id.navigationView);
fm.beginTransaction().add(R.id.main_frame, fragment33, "3").hide(fragment33).commit();
fm.beginTransaction().add(R.id.main_frame, fragment22, "2").hide(fragment22).commit();
fm.beginTransaction().add(R.id.main_frame,fragment11, "1").commit();
bv.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener()
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
switch (item.getItemId())
case R.id.account:
fm.beginTransaction().hide(active).show(fragment11).commit();
active = fragment11;
return true;
case R.id.home1:
fm.beginTransaction().hide(active).show(fragment22).commit();
active = fragment22;
return true;
case R.id.other:
fm.beginTransaction().hide(active).show(fragment33).commit();
active = fragment33;
return true;
return false;
);
bv.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener()
@Override
public void onNavigationItemReselected(@NonNull MenuItem item)
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
);
【讨论】:
向你致敬!真的很感谢你的工作。你让我今天一整天都感觉很好。谢谢! 绝对是我认为最干净的解决方案。而且它很灵活——如果您需要重新创建片段,您只需重新分配相关的片段变量。非常感谢! 但是这个解决方案不可扩展。每次要添加新片段时,都必须修改代码。【参考方案3】:这似乎对我很有效。我没有附加和分离,而是使用显示或隐藏来维护片段状态。
public void changeFragment(Fragment fragment, String tagFragmentName)
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
if (currentFragment != null)
fragmentTransaction.hide(currentFragment);
Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
if (fragmentTemp == null)
fragmentTemp = fragment;
fragmentTransaction.add(R.id.frame_layout, fragmentTemp, tagFragmentName);
else
fragmentTransaction.show(fragmentTemp);
fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
这就是我使用它的方式
private void initViews()
BottomNavigationView bottomNavigationView = findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener
(new BottomNavigationView.OnNavigationItemSelectedListener()
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
Fragment selectedFragment = null;
switch (item.getItemId())
case R.id.explore:
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
toggleViews(true, "");
break;
case R.id.favorite:
changeFragment(new FavoriteFragment(), FavoriteFragment.class
.getSimpleName());
toggleViews(false, "Favorites");
break;
case R.id.venue:
changeFragment(new VenueFragment(), VenueFragment.class.getSimpleName());
toggleViews(false, "Venues");
break;
case R.id.profile:
changeFragment(new ProfileFragment(), ProfileFragment.class
.getSimpleName());
toggleViews(false, "Profile");
break;
return true;
);
//Manually displaying the first fragment - one time only
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
【讨论】:
【参考方案4】:当再次单击当前选定的导航按钮时,此解决方案会阻止重新创建片段。但是,每次用户单击不同的导航按钮时都会创建一个新片段。
只需添加此行即可避免从BottomNavigationView
重新创建Fragment
bottomNavigation.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener()
@Override
public void onNavigationItemReselected(@NonNull MenuItem item)
// do nothing here
);
或者使用 lambda:
bottomNavigation.setOnNavigationItemReselectedListener(item -> );
【讨论】:
你是真正的英雄!你是生命的终结者! @DilipHirapara Dhanyawad mitra.. :DsetOnNavigationItemReselectedListener
已弃用,改为setOnItemReselectedListener
【参考方案5】:
使用replace
时要小心。即使提供内存中已经存在的片段,replace
也会重新启动片段的生命周期。为避免重启,事务对象的方法包括add
、show
和hide
,可用于在不重启的情况下显示正确的片段。
private fun switchFragment(selectedTabIndex: Int)
val previousTabIndex = this.currentTabIndex
this.currentTabIndex = selectedTabIndex
val transaction = supportFragmentManager.beginTransaction()
val tag = fragments[this.currentTabIndex].tag
// if the fragment has not yet been added to the container, add it first
if (supportFragmentManager.findFragmentByTag(tag) == null)
transaction.add(R.id.container, fragments[this.currentTabIndex], tag)
transaction.hide(fragments[previousTabIndex])
transaction.show(fragments[this.currentTabIndex])
transaction.commit()
【讨论】:
您在此处使用了稍微令人困惑的措辞,只是为了澄清一下:在添加到 UI 时,replace 不会重新启动片段的生命周期 - 它会结束当前附加的片段,然后添加新的片段,我们只能假设如果已删除或首次添加,则之前已销毁。所以它肯定会重新开始,是的,但不是因为替换是强迫它。基本上替换 = remove(oldFrag) 然后添加(newFrag)。 什么是currentTabPosition
? android.support.design.widget.BottomNavigationView
似乎没有那个属性。
在使用 findFragmentByTag 时应该非常小心......最好完全避免它:P
@Maxwell 你能澄清一下上面的cmets吗? index 和 currentTabPosition 是什么,如果这个 switchFragment 方法在 BaseActivity 中会怎样。你能发布一个小活动或 git gist 的例子吗?谢谢,因为这个答案似乎不完整。【参考方案6】:
试试这个:
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener()
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
switch (item.getItemId())
case R.id.action_one:
// Switch to page one
if (!(currentFragment instanceof FragmentA))
fragment = FragmentA.newInstance();
break;
case R.id.action_two:
// Switch to page two
if (!(currentFragment instanceof FragmentB))
fragment = FragmentB.newInstance();
break;
case R.id.action_three:
// Switch to page three
if (!(currentFragment instanceof FragmentC))
fragment = FragmentC.newInstance();
break;
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment, "TAG").commit();
return true;
);
这将在您的container
中获取当前片段,如果您再次单击该片段,则不会重新添加该片段。
【讨论】:
当我点击后退按钮时如何重定向到主页...请帮助我先生 你可以在你的主Activity
上Override
函数onBackPressed
。
在你的代码中,如果第一个 FragmentA 被加载,点击第二个而不是第二个 Fragment 加载后,如果用户点击 firstFragment 然后也被重新创建。
@MiteshVanaliya 是的,如果您不想每次都重新创建片段,请存储变量,或者将所有片段放入 ViewPager
并更改当前项。
@Raphael Teyssandier 当前片段返回 null【参考方案7】:
setOnNavigationItemReselectedListener 会是一个更好的解决方案
【讨论】:
我同意你的看法【参考方案8】:像这样使用 setOnNavigationItemReselectedListener:
private BottomNavigationView.OnNavigationItemReselectedListener onNavigationItemReselectedListener
= new BottomNavigationView.OnNavigationItemReselectedListener()
@Override
public void onNavigationItemReselected(@NonNull MenuItem item)
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
;
并使用以下方法调用它:
navigation.setOnNavigationItemReselectedListener(onNavigationItemReselectedListener);
【讨论】:
这如何回答这个问题? @Timuçin 无论您在此函数中执行什么操作,都会在重新选择项目时执行,因此如果您不想创建新片段,请将其留空。 甚至没有进入reselect函数。 @Timuçin 设置重选监听器后,不会在项目重选时触发普通监听器。 2019年,只有在再次选择当前项目时才会触发项目重选。如果当前片段包含垂直列表,则可能对滚动到顶部等有用。【参考方案9】:我发现的最好方法。
private void replace_fragment(Fragment fragment)
String tag = fragment.getClass().getSimpleName();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
Fragment curFrag = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment cacheFrag = getSupportFragmentManager().findFragmentByTag(tag);
if (curFrag != null)
tr.hide(curFrag);
if (cacheFrag == null)
tr.add(R.id.main_frame, fragment, tag);
else
tr.show(cacheFrag);
fragment = cacheFrag;
tr.setPrimaryNavigationFragment(fragment);
tr.commit();
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener()
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
switch (item.getItemId())
case R.id.nav_posts:
replace_fragment(new PostsFragment());
return true;
case R.id.nav_stores:
replace_fragment(new StoresFragment());
return true;
case R.id.nav_chats:
replace_fragment(new DiscussionsFragment());
return true;
case R.id.nav_account:
replace_fragment(new ProfileFragment());
return true;
return false;
;
【讨论】:
【参考方案10】:我改进了@Viven 的答案并用 Kotlin 编写了它。我的版本仅第一次实例化片段,隐藏/显示。我是 Kotlin 的新手,请告诉我是否可以改进。
我们需要持有一个id来标记地图:
private val fragmentTags = hashMapOf(
R.id.action_home to "home_fragment",
R.id.action_profile to "profile_fragment"
)
监听代码:
bottomNavigation.run
setOnNavigationItemSelectedListener menuItem ->
supportFragmentManager.beginTransaction()
.let transaction ->
// hide current fragment
supportFragmentManager.primaryNavigationFragment?.let
// if selected fragment's tag is same, do nothing.
if (it.tag == fragmentTags[menuItem.itemId])
return@setOnNavigationItemSelectedListener true
transaction.hide(it)
var fragment = supportFragmentManager.findFragmentByTag(fragmentTags[menuItem.itemId])
if (fragment == null)
// instantiate fragment for the first time
fragment = when(menuItem.itemId)
R.id.action_home -> HomeFragment()
R.id.action_profile -> ProfileFragment()
else -> null
?.also
// and add it
transaction.add(R.id.frame, it, fragmentTags[menuItem.itemId])
else
// if it's found show it
transaction.show(fragment)
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
.commitNowAllowingStateLoss()
return@setOnNavigationItemSelectedListener true
//bottomNavigation things
itemIconTintList = null
selectedItemId = R.id.action_home
【讨论】:
【参考方案11】:我通过添加一个ViewPager
解决了这个问题,我将所有导航片段都委托给了它。当用户浏览BotoomNavigationView
时,其适配器 (FragmentPagerAdapter
) 不会重新创建片段实例。
为此,您必须完成 5 个简单的步骤:
-
在布局中添加
ViewPager
;
实现它的适配器:
class YourNavigationViewPagerAdapter(fm: FragmentManager,
private val param1: Int,
private val param2: Int)
: FragmentPagerAdapter(fm)
override fun getItem(p0: Int) = when(p0)
0 -> NavigationFragment1.newInstance(param1, param2)
1 -> NavigationFragment2.newInstance(param1, param2)
2 -> NavigationFragment3.newInstance(param1, param2)
else -> null
override fun getCount() = 3
别忘了设置新的适配器:
yourViewPager.adapter = YourNavigationViewPagerAdapter(supportFragmentManager, param1, param2)
将OnNavigationItemSelectedListener
设置为您的BottomNavigationView
,如下所示:
yourBottomNavigationView.setOnNavigationItemSelectedListener
when(it.itemId)
R.id.yourFirstFragmentMenuItem ->
yourViewPager.currentItem = 0
true
R.id.yourSecondFragmentMenuItem ->
yourViewPager.currentItem = 1
true
R.id.yourThirdFragmentMenuItem ->
yourViewPager.currentItem = 2
true
else -> false
将OnPageChangeListener
设置为您的ViewPager
,如下所示:
yourViewPager.addOnPageChangeListener(object :
ViewPager.OnPageChangeListener
override fun onPageScrollStateChanged(p0: Int)
override fun onPageScrolled(p0: Int, p1: Float, p2: Int)
override fun onPageSelected(p0: Int)
yourBottomNavigationView.menu.getItem(p0).isChecked = true
)
享受:)
【讨论】:
【参考方案12】:我为此在 Kotlin 中编写了一个扩展函数。
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()
【讨论】:
【参考方案13】:正确导航涉及多个测试用例,我正在粘贴我的代码并检查所有测试用例。
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch (item.getItemId())
case R.id.dashboard:
Fragment fragment;
fragment = fragmentManager.findFragmentByTag(DashboardFragment.TAG);
if (fragment == null)
fragment = new DashboardFragment();
fragmentTransaction.add(R.id.frame, fragment, DashboardFragment.TAG);
else
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.expenses:
fragment = fragmentManager.findFragmentByTag(ExpenseFragment.TAG);
if (fragment == null)
fragment = new ExpenseFragment();
fragmentTransaction.add(R.id.frame, fragment, ExpenseFragment.TAG);
else
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.vehicle_parts:
Bundle bundle = new Bundle();
bundle.putInt("odometer", vehicle.getOdometer());
bundle.putString("vehicle_id", vehicle.get_id());
fragment = fragmentManager.findFragmentByTag(PartsFragment.TAG);
if (fragment == null)
fragment = new PartsFragment();
fragment.setArguments(bundle);
fragmentTransaction.add(R.id.frame, fragment, PartsFragment.TAG);
else
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.blog:
fragment = fragmentManager.findFragmentByTag(BlogFragment.TAG);
if (fragment == null)
fragment = new BlogFragment();
fragmentTransaction.add(R.id.frame, fragment, BlogFragment.TAG);
else
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
return false;
【讨论】:
【参考方案14】:使用 Cicerone 库轻松处理导航。
https://github.com/terrakok/Cicerone
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem)
switch (menuItem.getItemId())
case R.id.save:
router.replaceScreen("your fragment1");
menuItem.setChecked(true);
break;
case R.id.search_lessons:
router.replaceScreen("your fragment2");
menuItem.setChecked(true);
break;
case R.id.profile_student:
router.replaceScreen("your fragment3");
menuItem.setChecked(true);
break;
return false;
【讨论】:
【参考方案15】:如何使用 BottomNavigationView 在片段已经可见时停止重新创建片段
第一步——
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item)
Fragment fragment = null;
String valuestring;
**if (item.getItemId() == lastSelectedItemId) // added this
Log.e( "both id is same","LULL" );
return true;
**
switch (item.getItemId())
case R.id.navigation_category:
SetActioBarText( getString( R.string.label_actionbar_categories ) );
fragment = new CategoryFragment();
valuestring = "category";
break;
case R.id.navigation_favourite:
SetActioBarText( getString( R.string.label_actionbar_favourites ) );
fragment = new FavouriteFragment();
valuestring = "favourites";
break;
default:
throw new IllegalStateException( "Unexpected value: " + menuItem.getItemId() );
return loadFragment( fragment, valuestring ,menuItem);
现在第 2 步---
private boolean loadFragment(Fragment fragment, String argument,MenuItem item)
if (fragment != null)
transaction = fragmentManager.beginTransaction();
transaction.addToBackStack( argument );
transaction.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_FADE );
transaction.replace( R.id.frame_container, fragment, "demofragment").commitAllowingStateLoss();
lastSelectedItemId= item.getItemId();
return true;
return false;
【讨论】:
【参考方案16】:不需要做这个答案。实际上,您真正需要的是在特定片段中保存视图。
private View view;
if(view == null)
view = inflater.inflate(R.layout.call_fragment_edited, container, false);
所以任何时候当你创建新片段时,你都会看到当前状态
【讨论】:
我们不应该缓存视图,尤其是在片段上以上是关于在选择的底部导航视图项目上重新创建片段的主要内容,如果未能解决你的问题,请参考以下文章