简体   繁体   English

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

[英]Restoring fragment state when changing fragments through bottom navigation bar

I have bottom navigation bar on click of item in navigation bar i am replacing fragments.单击导航栏中的项目时,我有底部导航栏,我正在替换片段。 I have 3 fragments A,B,C so on click of b item B fragment is loaded and in B i am calling 3-4 APIs.我有 3 个片段 A、B、C 等点击 b 项 B 片段被加载,在 B 中我正在调用 3-4 个 API。 So now if i go to C and then again come to B a new instance of B Fragment is created and again those APIs are called how can i save the fragment instance state and not call APIs again while changing fragments.所以现在如果我从 go 到 C 然后再次来到 B 创建 B 片段的新实例并再次调用这些 API,我如何保存片段实例 Z9ED39E2EA931586B6A985A6942EF7 而再次调用 API 而不更改片段。 This is my code.这是我的代码。

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

I have already initialized fragments member variables above in onCreate of MainActivity我已经在MainActivityonCreate中初始化了 Fragments 成员变量

You should use a FragmentPagerAdapter to initiate the fragments so when you want to switch in between them, the state of the fragments will be saved.您应该使用 FragmentPagerAdapter 来启动片段,这样当您想在它们之间切换时,片段的状态将被保存。

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

then in the bottom navigation selected you can set fragment by simple command然后在选择的底部导航中,您可以通过简单的命令设置片段

viewPager.setCurrentItem(n);

my viewpager class is as follows:我的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;
}
}

in the xml instead of a empty layout for fragemnt u need:在 xml 中,而不是为 fragemnt 使用空布局,您需要:

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

Code for custom FragmentPagerAdapter:自定义 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);
    }
}

I used bottom navigation bar and I did it by customizing viewpager and I disable the swipe navigation.我使用了bottom navigation bar ,我通过自定义 viewpager 来做到这一点,并禁用了滑动导航。 Each time user clicks bottom item, set relevant fragment in viewpager.每次用户单击底部项目时,在 viewpager 中设置相关片段。 Viewpager control state of fragment, so no need control state. Viewpager控制fragment的状态,所以不需要控制状态。

Custom 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;
    }
}

If you still want to control state of fragment, you can see my answer in this link How to save fragment state in android?如果你仍然想控制片段的状态,你可以在这个链接中看到我的回答How to save fragment state in android?

To restore/retain a fragment's state you should use ViewPager2 as it is the updated version of ViewPager .要恢复/保留一个片段的状态,你应该使用ViewPager2,因为它是ViewPager的更新版本。

You will get the code on my GitHub repository with three menu items in the Bottom Navigation Bar with more functionality.您将在我的GitHub存储库中获得代码,底部导航栏中的三个菜单项具有更多功能。 I am also providing a simple description here with two menu items in the Bottom Navigation Bar .我还在这里提供了一个简单的描述,其中包含底部导航栏中的两个菜单项

Step by step guide (to restore/retain an EditText's state as an example):分步指南(以恢复/保留 EditText 的状态为例):

Step 1:第1步:

Add dependencies in your build.gradle (app module) file: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'

}

Step 2:第2步:

Add menu_bottom_navigation.xml to res/menu : (You may also add icons to menu items )menu_bottom_navigation.xml添加到res/menu :(您也可以向菜单项添加图标

<?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>

Step 3:第 3 步:

Add activity_main.xml to res/layout : (adding menu to BottomNavigationView and placing ViewPager2 )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_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        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_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_gravity="bottom"
        android:fitsSystemWindows="true"
        app:itemIconSize="20dp"
        android:background="#A8DD44"
        app:menu="@menu/menu_bottom_navigation" />

</LinearLayout>

Step 4:第四步:

Add fragment_first.xml to res/layout :fragment_first.xml添加到res/layout

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

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

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

</LinearLayout>

Step 5:第 5 步:

Add fragment_second.xml to res/layout :fragment_second.xml添加到res/layout

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

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

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

</LinearLayout>

Step 6:第 6 步:

ViewPagerAdapter.java : 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();
    }

}

Step 7:第 7 步:

FirstFragment.java : 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);
    }

}

Step 8:第 8 步:

SecondFragment.java : 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);
    }

}

Step 9:第 9 步:

MainActivity.java :主活动.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);
    }

}

Solution Using Navigation Component使用导航组件的解决方案

Use these versions, Multi stack support is only available from these versions使用这些版本,多堆栈支持仅在这些版本中可用

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

First, you need to create nav graphs for each fragment in the bottom navigation首先,您需要为底部导航中的每个片段创建导航图

Filename: first.xml文件名: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>

create nav graph for all tabs and setup bottom navigation menu using these ids使用这些 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"/>

.
.

.

include these nav graphs inside the main nav graph and use this main graph in FragmentContainerView将这些导航图包含在主导航图中,并在 FragmentContainerView 中使用此主图

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

Setup the bottom navigation设置底部导航

  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)

Refer to the following link for more info有关更多信息,请参阅以下链接

Navigation multiple back stacks 导航多个返回堆栈

You shouldn't have to bring ViewPager in to accomplish this.您不必引入 ViewPager 来完成此操作。 I have one activity with a BottomNavigationView and a fragment container.我有一个带有BottomNavigationView和片段容器的活动。 When a user clicks a navigation tab, I do the following:当用户单击导航选项卡时,我执行以下操作:

final Fragment existing = getSupportFragmentManager().findFragmentByTag(fragmentName);
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
getSupportFragmentManager().getFragments().forEach(transaction::hide);
if (existing != null) {
    transaction.show(existing);
} else {
    transaction.add(R.id.new_main_fragment_frame, instantiate, fragmentName);
}
transaction.commit();

I'm handling my own back press at the activity level by overriding onBackPressed and this is working well.我通过覆盖onBackPressed在活动级别处理我自己的背压,这运行良好。 The fragments lazy load in and then are re-used if the user is switching between tabs.如果用户在选项卡之间切换,片段会延迟加载,然后重新使用。

The simplest solution for that is to override "OnCreate()" method in your Fragment B and call you APIs in "OnCreate()" method instead of "OnCreateView()".最简单的解决方案是覆盖片段 B 中的“OnCreate()”方法,并在“OnCreate()”方法而不是“OnCreateView()”中调用 API。

Hope it will work for you!希望它对你有用!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM