通过底部导航栏更改片段时恢复片段状态

Posted

技术标签:

【中文标题】通过底部导航栏更改片段时恢复片段状态【英文标题】:Restoring fragment state when changing fragments through bottom navigation bar 【发布时间】:2017-08-04 12:26:34 【问题描述】:

单击导航栏中的项目时,我有底部导航栏,我正在替换片段。我有 3 个片段 A、B、C,因此单击 b 项 B 片段已加载,并且在 B 中我正在调用 3-4 个 API。所以现在如果我去 C 然后再次来到 B 一个新的 B 片段实例被创建并且再次调用这些 API 我如何保存片段实例状态而不是在更改片段时再次调用 API。这是我的代码。

mBottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() 
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) 
            int id = item.getItemId();
            Fragment currentLoaded = fgMan.findFragmentById(R.id.container_body);
            switch (id) 
                case R.id.nearby_fragment:
                    if (!(currentLoaded instanceof SpotFeedMapFragment)) 
                        removeScroll();
                        mNearByFragment = fgMan.findFragmentByTag(NEARBY_FRAGMENT_TAG) != null ? fgMan.findFragmentByTag(NEARBY_FRAGMENT_TAG) : mNearByFragment;
                        fgMan.beginTransaction().setCustomAnimations(R.anim.abc_fade_in, R.anim.abc_fade_out);
                        fgMan.beginTransaction().replace(R.id.container_body, mNearByFragment, NEARBY_FRAGMENT_TAG).commit();
                        fgMan.executePendingTransactions();
                        getSupportActionBar().setTitle(getString(R.string.nearby_fragment));
                    
                    break;
                case R.id.route_fragment:
                    if (!(currentLoaded instanceof BusLocationsFragment)) 
                        if (!inParent) 
                            mRl.removeView(fixLayout);
                            p.addRule(RelativeLayout.BELOW, toolbar.getId());
                            scrollView.setLayoutParams(p);
                            scrollView.addView(fixLayout);
                            mRl.addView(scrollView);
                            inParent = true;
                        
                        //mFragment = new BusLocationsFragment();
                        mBusLocFragment = fgMan.findFragmentByTag(BUS_LOC_FRAGMENT_TAG) != null ? fgMan.findFragmentByTag(BUS_LOC_FRAGMENT_TAG) : mBusLocFragment;
                        fgMan.beginTransaction().setCustomAnimations(R.anim.abc_fade_in, R.anim.abc_fade_out);
                        fgMan.beginTransaction().replace(R.id.container_body, mBusLocFragment, BUS_LOC_FRAGMENT_TAG).commit();
                        fgMan.executePendingTransactions();
                        getSupportActionBar().setTitle(getString(R.string.app_name));
                    
                    break;
                case R.id.newsfeed_activity:
                    if (!(currentLoaded instanceof NewsFeedActivity)) 
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) 
                            removeScroll();
                        
                        mNewsFeedFragment = fgMan.findFragmentByTag(NEWSFEED_FRAGMENT_TAG) != null ? fgMan.findFragmentByTag(NEWSFEED_FRAGMENT_TAG) : mNewsFeedFragment;
                        fgMan.beginTransaction().setCustomAnimations(R.anim.abc_fade_in, R.anim.abc_fade_out);
                        fgMan.beginTransaction().replace(R.id.container_body, mNewsFeedFragment, NEWSFEED_FRAGMENT_TAG).commit();
                        fgMan.executePendingTransactions();
                        getSupportActionBar().setTitle(getString(R.string.news));
                    
                    break;
            
            return true;
        
    );

我已经在onCreateofMainActivity上面初始化了fragments成员变量

【问题讨论】:

只是一个想法,我无法提供代码,因为我现在没有开发工具。不要在片段事务中使用.replace。这将使销毁其实例到后台堆栈。将其替换为.add。当您单击按钮将片段 a 更改为 b 并且片段 b 之前已经打开时,您可以检查您的 backstack 是否片段 b 已经存在并在此处弹出它是一个示例 ***.com/a/9787891/5870896 。确保每次访问它时都将片段添加到 backstack 以保存其实例 任何人都知道隐藏/显示片段事务而不是分离、删除、替换或添加更好吗? (像这样的另一个答案***.com/q/42434392/4074312) 【参考方案1】:

您应该使用 FragmentPagerAdapter 来启动 Fragment,这样当您想在它们之间切换时,会保存 Fragment 的状态。

CutomViewPager viewPager = (CustomViewPager) findViewById(R.id.viewpager1);
ViewPagerAdapter adapter = new ViewPagerAdapter (MainActivity.this.getSupportFragmentManager());
adapter.addFragment(new SpotFeedMapFragment(), "title");
adapter.addFragment(new BusLocationsFragment(), "title");
adapter.addFragment(new NewsFeedActivity(), "title");
viewPager.setAdapter(adapter);

然后在选择的底部导航中,您可以通过简单的命令设置片段

viewPager.setCurrentItem(n);

我的viewpager类如下:

public class CustomViewPager extends ViewPager 

private boolean isPagingEnabled;

public CustomViewPager(Context context) 
    super(context);
    this.isPagingEnabled = true;


public CustomViewPager(Context context, AttributeSet attrs) 
    super(context, attrs);
    this.isPagingEnabled = true;


@Override
public boolean onTouchEvent(MotionEvent event) 
    return this.isPagingEnabled && super.onTouchEvent(event);


//for samsung phones to prevent tab switching keys to show on keyboard
@Override
public boolean executeKeyEvent(KeyEvent event) 
    return isPagingEnabled && super.executeKeyEvent(event);


@Override
public boolean onInterceptTouchEvent(MotionEvent event) 
    return this.isPagingEnabled && super.onInterceptTouchEvent(event);


public void setPagingEnabled(boolean enabled) 
    this.isPagingEnabled = enabled;


在 xml 中,而不是你需要的片段的空布局:

<com.package.util.CustomViewPager
    android:id="@+id/viewpager1"
    android:layout_
    android:layout_ />

自定义 FragmentPagerAdapter 的代码:

private class ViewPagerAdapter extends FragmentPagerAdapter 
    private final SparseArray<WeakReference<Fragment>> instantiatedFragments = new SparseArray<>();
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    ViewPagerAdapter(FragmentManager manager) 
        super(manager);
    

    @Override
    public Fragment getItem(int position) 
        return mFragmentList.get(position);
    

    @Override
    public int getCount() 
        return mFragmentList.size();
    

    void addFragment(Fragment fragment, String title) 
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    

    @Override
    public Object instantiateItem(ViewGroup container, int position) 
        final Fragment fragment = (Fragment) super.instantiateItem(container, position);
        instantiatedFragments.put(position, new WeakReference<>(fragment));
        return fragment;
    

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) 
        instantiatedFragments.remove(position);
        super.destroyItem(container, position, object);
    

    @Nullable
    Fragment getFragment(final int position) 
        final WeakReference<Fragment> wr = instantiatedFragments.get(position);
        if (wr != null) 
            return wr.get();
         else 
            return null;
        
    

    @Override
    public CharSequence getPageTitle(int position) 
        return mFragmentTitleList.get(position);
    

【讨论】:

这太棒了,只需保留viewpager,它会照顾片段状态,然后给出页面限制,最后删除viewpager swipe。 FragmentPagerAdapter 是一个抽象类。应扩展 FragmentPagerAdapter 的自定义片段寻呼机适配器类缺少代码。 @Ranjan 是一个简单的实现。但是,如果您需要,我添加了我的自定义 FragmentPagerAdapter。【参考方案2】:

要恢复/保留片段的状态,您应该使用 ViewPager2,因为它是 ViewPager 的更新版本。

您将获得my GitHub repository 上的代码,其中三个菜单项底部导航栏中具有更多功能。我也在此处提供了一个简单的描述,其中 两个菜单项位于 底部导航栏

分步指南(以恢复/保留 EditText 的状态为例):

第 1 步:

在您的 build.gradle(应用模块)文件中添加 依赖项

dependencies 

    def nav_version = "2.3.0"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"

    implementation 'androidx.viewpager2:viewpager2:1.0.0'


第 2 步:

menu_bottom_navigation.xml 添加到 res/menu:(您也可以将 icons 添加到 菜单项

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_first"
        android:checked="true"
        android:title="First"
        app:showAsAction="always" />
    <item
        android:id="@+id/menu_second"
        android:checked="false"
        android:title="Second"
        app:showAsAction="always" />

</menu>

第 3 步:

activity_main.xml 添加到 res/layout:(将菜单添加到 BottomNavigationView 并放置 ViewPager2

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activityRoot"
    android:layout_
    android:layout_
    android:gravity="bottom"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        android:layout_
        android:layout_
        android:layout_above="@+id/bottom_navigation"
        android:layout_alignParentTop="true"
        android:layout_weight="1"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_
        android:layout_
        android:layout_alignParentBottom="true"
        android:layout_gravity="bottom"
        android:fitsSystemWindows="true"
        app:itemIconSize="20dp"
        android:background="#A8DD44"
        app:menu="@menu/menu_bottom_navigation" />

</LinearLayout>

第 4 步:

fragment_first.xml 添加到 res/layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    android:orientation="vertical"
    android:layout_margin="20dp"
    tools:context="com.example.rough.Fragment.FirstFragment">

    <TextView
        android:layout_
        android:layout_
        android:text="First Fragment"
        android:layout_centerInParent="true"
        android:textSize="30sp" />

    <EditText
        android:layout_
        android:layout_
        android:hint="Write something &amp; it will stay"
        android:ems="13"/>

</LinearLayout>

第 5 步:

fragment_second.xml 添加到 res/layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    android:layout_margin="20dp"
    android:orientation="vertical"
    tools:context="com.example.rough.Fragment.SecondFragment">

    <TextView
        android:layout_
        android:layout_
        android:text="Second Fragment"
        android:layout_centerInParent="true"
        android:textSize="30sp" />

    <EditText
        android:layout_
        android:layout_
        android:hint="Write something &amp; it will stay"
        android:ems="13"/>

</LinearLayout>

第 6 步:

ViewPagerAdapter.java

public class ViewPagerAdapter extends FragmentStateAdapter 
    private final List<Fragment> mFragmentList = new ArrayList<>();

    public ViewPagerAdapter(@NonNull FragmentManager fragmentManager, Lifecycle b ) 
        super(fragmentManager,b);
    

    public void addFragment(Fragment fragment) 
        mFragmentList.add(fragment);
    

    @NonNull
    @Override
    public Fragment createFragment(int position) 
        return mFragmentList.get(position);
    

    @Override
    public int getItemCount() 
        return mFragmentList.size();
    


第 7 步:

FirstFragment.java

public class FirstFragment extends Fragment 


    public FirstFragment() 
        // Required empty public constructor
    


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) 
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false);
    


第 8 步:

SecondFragment.java

public class SecondFragment extends Fragment 


    public SecondFragment() 
        // Required empty public constructor
    


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) 
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false);
    


第 9 步:

MainActivity.java

public class MainActivity extends AppCompatActivity 

    BottomNavigationView bottomNavigationView;

    private ViewPager2 viewPager2;

    FirstFragment firstFragment;
    SecondFragment secondFragment;

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

        viewPager2 = findViewById(R.id.viewpager2);
        bottomNavigationView = findViewById(R.id.bottom_navigation);

        bottomNavigationView.setOnNavigationItemSelectedListener(
                new BottomNavigationView.OnNavigationItemSelectedListener() 
                    @Override
                    public boolean onNavigationItemSelected(@NonNull MenuItem item) 
                        switch (item.getItemId()) 
                            case R.id.menu_first:
                                viewPager2.setCurrentItem(0,false);
                                break;
                            case R.id.menu_second:
                                viewPager2.setCurrentItem(1,false);
                                break;
                        
                        return false;
                    
                );

        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() 
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);

                switch (position) 
                    case 0:
                        bottomNavigationView.getMenu().findItem(R.id.menu_first).setChecked(true);
                        break;
                    case 1:
                        bottomNavigationView.getMenu().findItem(R.id.menu_second).setChecked(true);
                        break;
                
            
        );

        setupViewPager(viewPager2);

    

    private void setupViewPager(ViewPager2 viewPager) 

        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle());

        firstFragment =new FirstFragment();
        secondFragment =new SecondFragment();

        adapter.addFragment(firstFragment);
        adapter.addFragment(secondFragment);

        viewPager.setAdapter(adapter);
    


【讨论】:

它工作得很好我整天都在寻找它,现在终于得到了这个很棒的解释!衷心感谢 我在viewPager的地方实现了viewPager2,只是为了禁用滑动功能,任何想要相同的人都可以使用此行来实现最终结果:- viewpager2.setUserInputEnabled(false); 【参考方案3】:

我使用了bottom navigation bar,并通过自定义 viewpager 来做到这一点,并禁用了滑动导航。每次用户单击底部项目时,在 viewpager 中设置相关片段。 Fragment的Viewpager控制状态,所以不需要控制状态。

自定义 ViewPager

public class BottomNavigationViewPager extends ViewPager 

    private boolean enabled;

    public BottomNavigationViewPager(Context context, AttributeSet attrs) 
        super(context, attrs);
        this.enabled = false;
    

    @Override
    public boolean onTouchEvent(MotionEvent event) 
        if (this.enabled) 
            return super.onTouchEvent(event);
        

        return false;
    

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) 
        if (this.enabled) 
            return super.onInterceptTouchEvent(event);
        

        return false;
    

    /**
     * Enable or disable the swipe navigation
     * @param enabled
     */
    public void setPagingEnabled(boolean enabled) 
        this.enabled = enabled;
    

如果你仍然想控制片段的状态,你可以在这个链接中看到我的回答 How to save fragment state in android?

【讨论】:

我相信上面的link 是我正在寻找的答案。谢谢【参考方案4】:

使用导航组件的解决方案

使用这些版本,多栈支持仅在这些版本中可用

versions.fragment = "1.4.0-alpha01"
versions.navigation = "2.4.0-alpha01"

首先,您需要为底部导航中的每个片段创建导航图

文件名:first.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/first"
    app:startDestination="@id/homeFragment"
    tools:ignore="UnusedNavigation">

    <fragment
        android:id="@+id/homeFragment"
        android:label="@string/fragment_A_title"
        android:name="com.app.company.HomeFragment"
    >
    </fragment>
</navigation>

为所有标签创建导航图并使用这些 ID 设置底部导航菜单

<item android:title="@string/title_one"
    android:id="@+id/first"
    android:icon="@drawable/ic_icon_24"/>

<item android:title="@string/title_two"
    android:id="@+id/second"
    android:icon="@drawable/ic_icon_24"/>

.
.

.

将这些导航图包含在主导航图中并在 FragmentContainerView 中使用此主图

<include app:graph="@navigation/first"/>
<include app:graph="@navigation/second"/>
<include app:graph="@navigation/third"/>

设置底部导航

  val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_host) as NavHostFragment
        navController = navHostFragment.navController


        val controller = binding.bottonNavigation.setupWithNavController(navController)


        appBarConfig = AppBarConfiguration(
            setOf(
                R.id.homeFragment,
                R.id.secondFragment,
                R.id.thirdFragment
            )
        )
        setupActionBarWithNavController(navController, appBarConfig)

更多信息请参考以下链接

Navigation multiple back stacks

【讨论】:

【参考方案5】:

最简单的解决方案是在 Fragment B 中覆盖“OnCreate()”方法,并在“OnCreate()”方法而不是“OnCreateView()”中调用 API。

希望它对你有用!

【讨论】:

但是 oncreateView() 总是会被调用 这就是我们将所有代码从 OnCreateView 转移到 OnCreate() 的原因 @Zohaib ...那很好,甚至在他问什么时检查问题?他的问题是将状态保持为特定的视图状态,据我了解,BottomNavigationView 目前不支持它。

以上是关于通过底部导航栏更改片段时恢复片段状态的主要内容,如果未能解决你的问题,请参考以下文章

在片段更改时恢复窗口插图

底部应用栏在使用片段导航时向上/向下滑动(导航架构组件)

如何在更改全屏/上方导航时使用导航组件 navhostfragment

Google 相册应用底部导航栏行为

如何在android中使用底部导航视图时恢复片段状态?

使用底部导航栏防止片段刷新