BottomNavigationView - 如何避免重新创建片段并重用它们

时间:2017-07-16 16:12:35

标签: java android android-fragments bottomnavigationview

我想在我的项目中创建一个底部导航栏。每个视图都有自己的片段。问题是,每次我点击按钮更改视图,例如从最近更改为收藏夹,它会创建具有全新状态的新片段(例如滚动位置,文本更改我的片段包含的任何内容)。我知道在官方Android文档中有人写道底部导航栏应该重置任务状态,但我认为这对用户来说太不舒服了。 我希望有一些像instagram这样的功能,你可以从feed更改为探索,然后返回到滚动位置,图像缓存一切都保存。我几乎尝试了解决这个问题的方法,唯一有效的方法就是根据情况设置可视性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 : new i tried Nino Handler version it only works when i tap on the same fragment button

也许我已经连接了我正在使用金丝雀版本或我的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);
    }

}

12 个答案:

答案 0 :(得分:11)

我有类似的问题,但是这段代码解决了我的问题。

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 With Fragments (No fragment recreation).

答案 1 :(得分:2)

我不会全局保留片段实例。 而是在创建片段时为片段添加标签

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并使用每次单击事件更改适配器中的当前片段

答案 2 :(得分:1)

所以我长期以来一直在研究这个问题并且发现没有办法重复使用自动保存状态的片段,你必须手动保存你需要的状态然后在创建新片段时检索它们但是滚动怎么样位置太硬,甚至在某些情况下也无法保存滚动视图位置状态(例如回收者视图)。所以我使用了名为VISIBILITY的概念,当我点击按钮时,片段变得可见,其他人会自动隐藏。

答案 3 :(得分:1)

您可以使用attach()和detach()方法:

\ sigma -> aAux . sigma :: (s -> Aux a s) -> (s -> a)

答案 4 :(得分:1)

我为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()
}

supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG)中的onNavigationItemSelected可以调用

答案 5 :(得分:0)

那怎么样。 您在类中声明片段

Fragment firstFragment,secondFragment,thirdFragment;

然后在 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;
}

答案 6 :(得分:0)

创建三个片段作为类的成员并重用它们。

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();
    }

}

答案 7 :(得分:0)

之前的所有答案均使用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();
}

要使其工作,您应该在Activity中跟踪数组(或单独的变量)中的片段。我参考预先实例化了SparseArray中的所有片段。

答案 8 :(得分:0)

Thomas的回答几乎对我有帮助,但有一个问题,就是每当我第一次打开新片段时,它们就会重叠,但是一旦按菜单按钮再次打开它们,它们就不会重叠。

所以我修改了他的代码,并使用以下代码获得了解决方案:

 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()
    }

确保您没有每次都传递该片段的新实例。

这将起作用:

selectContentFragment(
                    when (item.itemId) {
                        R.id.home -> frag1
                        R.id.photoGallery -> frag2
                        else -> Home()
                    }
            )

其中frag1frag2是全局变量,定义为:

 val frag1 = Home()
 val frag2 = PhotoGallery()

这将不起作用:

selectContentFragment(
                    when (item.itemId) {
                        R.id.home -> Home()
                        R.id.photoGallery -> PhotoGallery()
                        else -> Home()
                    }
            )

这浪费了我几个小时。希望对别人有帮助!

答案 9 :(得分:0)

**此代码对我很有帮助。就像YouTube一样。 **

private Deque<Integer> fragmentIds = new ArrayDeque<>(3);
int itemId;
private HomeFragment homeFragment = new HomeFragment();
private FavouriteFragment favouriteFragment = new FavouriteFragment();
private NearmeFragment nearmeFragment = new NearmeFragment();
BottomNavigationView bottomNavigationView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    fragmentIds.push(R.id.action_home);
    showTabWithoutAddingToBackStack(homeFragment);
    bottomNavigationView = findViewById(R.id.bottom_navigation);
    bottomNavigationView.setOnNavigationItemSelectedListener(onNavigationItemClicked);
}

private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemClicked = new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        itemId= item.getItemId();
        if(fragmentIds.contains(itemId)){
            fragmentIds.remove(itemId);
        }
        fragmentIds.push(itemId);
        showTabWithoutAddingToBackStack(getFragment(item.getItemId()));
        return true;
    }
};

private Fragment getFragment(int fragmentId) {
    switch (fragmentId) {
        case R.id.action_home:
            return homeFragment;
        case R.id.action_favorites:
            return favouriteFragment;
        case R.id.action_nearme:
            return nearmeFragment;
    }
    return homeFragment;
}

private void showTabWithoutAddingToBackStack(Fragment fragment) {
    getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName()).commit();
}

@Override
public void onBackPressed() {
    if(fragmentIds.getLast() != R.id.action_home){
            fragmentIds.addLast(R.id.action_home);
    }
    fragmentIds.pop();
    bottomNavigationView.getMenu().getItem(fragmentIds.size()-1).setChecked(true);
    if (!fragmentIds.isEmpty()) {
        showTabWithoutAddingToBackStack(getFragment(fragmentIds.peek()));
    } else {
        finish();
    }
}

答案 10 :(得分:0)

对于Kotlin用户,可以帮助以下代码:

首先创建一个FragmentManager扩展类

fun FragmentManager.replace(containerId: Int, fragment: 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 = fragment
            add(containerId, current!!, tag)
        } else {
            show(current!!)
        }
    }
    .setTransition(FragmentTransaction.TRANSIT_ENTER_MASK)
    .setPrimaryNavigationFragment(current)
    .setReorderingAllowed(true)
    .commitNowAllowingStateLoss()

}

现在只需在您的

上调用它即可
 onNavigationItemSelected



supportFragmentManager.replace(R.id.fragment_id,YourFragmentClass,yourFragment.TAG) 

答案 11 :(得分:0)

我遇到了同样的问题,但我通过创建多个片段主机解决了它,然后在 fragmentHost 中添加了片段。当底部导航 itemSelected 时,我只是使所需的主机片段可见,而其他主机片段主机的可见性消失了。

这种方法可能是错误的,但对我来说非常有效。 而且我们不必手动处理片段的状态,只需要处理活动的背压。 但是这种方法不会暂停片段。

而且我不知道如何处理片段的暂停和恢复,所以请回复我。

也许这对你有帮助。

这是我的 fragmentHosts 主页活动:

<androidx.fragment.app.FragmentContainerView
        android:id="@+id/homeFragmentHost"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_NavBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"/>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/libraryFragmentHost"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_NavBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"/>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/myStuffFragmentHost"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_NavBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"/>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/moreFragmentHost"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_NavBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_NavBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/app_bottom_nav_menu"
        tools:ignore="BottomAppBar" />

主页活动 onCreate:

supportFragmentManager.beginTransaction().replace(R.id.homeFragmentHost,HomeFragment()).commitNow()
    supportFragmentManager.beginTransaction().replace(R.id.libraryFragmentHost,LibraryFragment()).commitNow()
    supportFragmentManager.beginTransaction().replace(R.id.myStuffFragmentHost,MyStuffFragment()).commitNow()
    supportFragmentManager.beginTransaction().replace(R.id.moreFragmentHost,MoreFragment()).commitNow()
    homeFragmentHost.visibility = View.VISIBLE

底部导航项目选择的监听器

    override fun onNavigationItemSelected(item: MenuItem): Boolean {
    return when(item.itemId){
        R.id.homeFragment -> loadFragmentHost(homeFragmentHost)
        R.id.libraryFragment -> loadFragmentHost(libraryFragmentHost)
        R.id.myStuffFragment -> loadFragmentHost(myStuffFragmentHost)
        R.id.moreFragment -> loadFragmentHost(moreFragmentHost)
        else -> false
    }

loadFragmentHost 函数

    private fun loadFragmentHost(view:FragmentContainerView): Boolean {
    val list = arrayListOf(homeFragmentHost,libraryFragmentHost,myStuffFragmentHost,moreFragmentHost)
    list.remove(view)
    view.visibility = View.VISIBLE
    list.forEach {
        it.visibility = View.GONE
    }
    return true
}