java BottomNavigationView - 如何避免重新创建 Fragment 并重用它们
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45130713/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
BottomNavigationView - How to avoid recreation of Fragments and reuse them
提问by Armen Hovhannisian
I would like to make a bottom navigation bar in my project. Every view has it's own fragment. The problem is that every time i click on the button to change the view for example from recents to favorites it creates new fragment with completely new states(e.g. scroll position, text changed whatever my fragment contains). I know that in official Android documentation there was written that bottom navigation bar should reset the task states, but i think it is too uncomfortable for users. I would like to have kind of similar functionality like instagram that you change from feed to explore and back to feed the scroll position the image caches everything remains saved. I tried almost every way to solve this problem the only thing that worked is by setting visibility GONE and setting visibility VISIBLE according to situation but i understand that it is not RIGHT way there should be better way of doing this and i am not talking about manually saving needed instances. I followed almost every tutorial about bottom nav fragments but the interesting thing is that no one is interested to make it use without calling new every time.
我想在我的项目中制作一个底部导航栏。每个视图都有它自己的片段。问题是,每次我单击按钮更改视图时,例如从最近的视图更改为收藏夹时,它都会创建具有全新状态的新片段(例如,滚动位置,无论我的片段包含什么,文本都会更改)。我知道在官方的 Android 文档中写到底部导航栏应该重置任务状态,但我认为这对用户来说太不舒服了。我希望有一种类似的功能,例如 Instagram,您可以将其从提要更改为探索,然后再回馈滚动位置,图像缓存的所有内容都保持保存状态。我几乎尝试了所有方法来解决这个问题,唯一有效的方法是根据情况设置可见性 GONE 和设置可见性可见性,但我明白这不是正确的方法,应该有更好的方法来做到这一点,我不是在谈论手动保存所需的实例。我几乎遵循了所有关于底部导航片段的教程,但有趣的是,没有人有兴趣在每次不调用 new 的情况下使用它。
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;
}
});
I also tried the onAttach and Deattach solutions but again no success.
我也尝试了 onAttach 和 Deattach 解决方案,但还是没有成功。
VIDEO LINK : new i tried Nino Handler version it only works when i tap on the same fragment button
视频链接:新的我尝试了 Nino Handler 版本,它仅在我点击同一个片段按钮时才有效
Maybe it is connected that i am using canary version or something wrong in my gradle dependencies?
也许这与我使用的是 Canary 版本或我的 gradle 依赖项有问题有关?
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);
}
}
回答by Bukunmi
I had similar issue, but this code solved my problem.
我有类似的问题,但这段代码解决了我的问题。
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;
}
};
Hope this helps.
希望这可以帮助。
source : BottomNavigationView With Fragments (No fragment recreation).
回答by luckyhandler
I wouldn't keep the fragment instances globally. Instead add a tag to the fragment when creating them
我不会全局保留片段实例。而是在创建片段时向片段添加标签
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, new PlaceholderFragment(), TAG_PLACEHOLDER)
.commit();
Then you can always retrieve it like this:
然后你总是可以像这样检索它:
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
if (fragment == null) {
fragment = new PlaceholderFragment();
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment, TAG_PLACEHOLDER)
.commit();
UPDATE: I updated my answer and to provide a complete solution:
更新:我更新了我的答案并提供了一个完整的解决方案:
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;
}
}
ADDITIONAL INFO: If you want to be sure that the fragment states don't change and if you also want to be able to swipe the fragments you should consider using a ViewPagerwith a FragmentStatePagerAdapterand change the current fragment in the adapter with every click event
附加信息:如果您想确保片段状态不会改变,并且如果您还希望能够滑动片段,您应该考虑使用带有FragmentStatePagerAdapter的ViewPager并在每次单击事件时更改适配器中的当前片段
回答by Armen Hovhannisian
So I have been researching for this for a long time and figured out that there is no way to reuse fragment with AUTOMATICALLY saved states you have to save your needed states manually then retrieve them whenever a new fragment is created but what about scroll position it is too hard and even in some cases it is impossible to save scroll view position state(e.g. recycler view). So i used the concept called VISIBILITY when i click on the button that fragment becomes visible others hide automatically.
所以我已经为此研究了很长时间,并发现无法重用具有自动保存状态的片段,您必须手动保存所需的状态,然后在创建新片段时检索它们,但是滚动位置呢?太难了,甚至在某些情况下也无法保存滚动视图位置状态(例如回收站视图)。因此,当我单击片段变为可见的按钮时,我使用了名为 VISIBILITY 的概念,其他片段会自动隐藏。
回答by Thomas Neuteboom
All the previous answers are using fragmentTransaction.replace(...)
. This will replace the current fragment by destroying it (which is causing the problem). Therefore all those solutions won't actually work.
以前的所有答案都使用fragmentTransaction.replace(...)
. 这将通过销毁它来替换当前片段(这会导致问题)。因此,所有这些解决方案实际上都不起作用。
This is the closest thing I could get to as a solution for this problem:
这是我能找到的最接近这个问题的解决方案:
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();
}
To make this work, you should keep track of the fragments in an array (or in separate variables) in your Activity. I for reference pre-instantiated all fragments in a SparseArray.
为了使这项工作有效,您应该在 Activity 中跟踪数组(或单独变量中)中的片段。我作为参考预先实例化了 SparseArray 中的所有片段。
回答by Albert
You can use attach() and detach() methods:
您可以使用 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();
}
回答by Sharukh Mohammed
I wrote a Kotlin Extension function for FragmentManager class
我为 FragmentManager 类编写了一个 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()
}
This can be called by supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG)
from onNavigationItemSelected
这可以通过supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG)
from调用onNavigationItemSelected
回答by Paras Sidhu
Thomas's answer nearly helped me but had an issue that whenever I open new fragments the very first time, they overlapped but doesn't get overlapped once I open them again by pressing the menu buttons.
托马斯的回答几乎帮助了我,但有一个问题,即每当我第一次打开新片段时,它们会重叠,但一旦我按下菜单按钮再次打开它们,它们就不会重叠。
So I modified his code and obtained the solution using the following code:
所以我修改了他的代码并使用以下代码获得了解决方案:
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()
}
Make sure you're not passing the fragment's new instance every time.
确保您没有每次都传递片段的新实例。
This will work:
这将起作用:
selectContentFragment(
when (item.itemId) {
R.id.home -> frag1
R.id.photoGallery -> frag2
else -> Home()
}
)
where frag1
and frag2
are global variables defined as:
其中frag1
和frag2
是定义为的全局变量:
val frag1 = Home()
val frag2 = PhotoGallery()
This will not work:
这将不起作用:
selectContentFragment(
when (item.itemId) {
R.id.home -> Home()
R.id.photoGallery -> PhotoGallery()
else -> Home()
}
)
It wasted my several hours. Hope it helps others!
这浪费了我的几个小时。希望它能帮助别人!
回答by Relish Wang
How about that. You declare the fragmentsin the class.
那个怎么样。您在类中声明片段。
Fragment firstFragment,secondFragment,thirdFragment;
then in the switch-caseyou can code as that:
然后在switch-case 中你可以这样编码:
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;
}
回答by kws
Create the three fragments as members of the class and reuse them.
创建三个片段作为类的成员并重用它们。
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();
}
}