使用 FragmentStatePageAdapter 通过 ViewPager 保留片段的位置
Using FragmentStatePageAdapter to retain position for fragment with ViewPager
我有一个 activity,其中包含一个 TabLayout 并为每个选项卡使用一个片段。在 return 中,该片段包含另一个用于 ViewPager 的片段。这是我的设置:
RoutineActivity.java
public class RoutineActivity extends AppCompatActivity {
private ArrayList<CategoryFragment> categories = new ArrayList<>();
private TabLayout tabs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_routine);
// Set the toolbar as the action bar and add the Up button
Toolbar toolbar = findViewById(R.id.routine_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Get the intent and extract the string array
Intent intent = getIntent();
ArrayList<String> selected =
intent.getStringArrayListExtra(SelectCategoriesActivity.EXTRA_CATEGORIES);
// Set the TabLayout
this.setTabs(selected);
}
// Set the tabs of the TabLayout given a list of tab names
private void setTabs(ArrayList<String> tabs) {
// Setup the tabs and create a fragment for each category
this.tabs = findViewById(R.id.category_tabs);
for (int x = 0; x < tabs.size(); x++) {
TabLayout.Tab tab = this.tabs.newTab().setText(tabs.get(x));
// Select the first tab
if (x == 0) {
this.tabs.addTab(tab, true);
} else {
this.tabs.addTab(tab);
}
// Store the created fragment
CategoryFragment categoryFragment = CategoryFragment.newInstance(tabs.get(x));
this.categories.add(categoryFragment);
}
// Add the first fragment
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_category_container, this.categories.get(0))
.commit();
// Add the tab selected listener
this.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
RoutineActivity.this.replaceFragment(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
// Replace the fragment corresponding to the category
private void replaceFragment(int position) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_category_container, this.categories.get(position));
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
CategoryFragment.java
public class CategoryFragment extends Fragment {
private ViewPager pager;
private ScreenSlidePagerAdapter pagerAdapter;
public CategoryFragment() {} // Required empty public constructor
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<String> selected = new ArrayList<>();
selected.add("1 " + getArguments().get("CATEGORY"));
selected.add("2 " + getArguments().get("CATEGORY"));
selected.add("3 " + getArguments().get("CATEGORY"));
selected.add("4 " + getArguments().get("CATEGORY"));
// Instantiate the PageAdapter
this.pagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager(), selected);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_category, container, false);
// Instantiate ViewPager and set its adapter
this.pager = view.findViewById(R.id.exercise_pager);
this.pager.setAdapter(this.pagerAdapter);
return view;
}
// Return a new instance of this fragment
public static CategoryFragment newInstance(String category) {
Bundle args = new Bundle();
args.putString("CATEGORY", category);
CategoryFragment categoryFragment = new CategoryFragment();
categoryFragment.setArguments(args);
return categoryFragment;
}
// The page adapter
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<String> selected;
public ScreenSlidePagerAdapter(FragmentManager fm, ArrayList<String> data) {
super(fm);
this.selected = data;
}
@Override
public Fragment getItem(int position) {
return ExerciseFragment.newInstance(this.selected.get(position));
}
@Override
public int getCount() {
return this.selected.size();
}
}
}
ExerciseFragment.java
public class ExerciseFragment extends Fragment {
public ExerciseFragment() {} // Required empty public constructor
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(
R.layout.fragment_exercise_page, container, false);
// Change the header to the category
TextView tv = rootView.findViewById(R.id.textView_header);
tv.setText(getArguments().getString("CAT"));
return rootView;
}
public static ExerciseFragment newInstance(String category) {
Bundle args = new Bundle();
args.putString("CAT", category);
ExerciseFragment exerciseFragment = new ExerciseFragment();
exerciseFragment.setArguments(args);
return exerciseFragment;
}
}
LogCat
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.util.SparseArray.get(int)' on a null object reference
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:902)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:216)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1453)
at android.view.View.dispatchRestoreInstanceState(View.java:16886)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3470)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3476)
at android.view.View.restoreHierarchyState(View.java:16864)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:500)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
at android.support.v4.app.FragmentManagerImpl.run(FragmentManager.java:700)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
问题是,当我 select 一个新标签时,一切正常,添加了一个新片段,但是当我返回到上一个标签时,整个应用程序崩溃了。我认为问题在于 ViewPager 如何恢复片段,但我不知道如何修复它。我已将 FragmentStatePagerAdapter
替换为 FragmentPagerAdapter
,问题就消失了。但是,我需要使用前者,因为我将使用很多页面(超过 20 页)。我只需要 CategoryFragment 来保留它停止的片段。有什么想法吗?
我在 SupportFragmentManager 中发现了问题,当您向后移动时,mActive 片段数组的第一个元素为 null,这会产生 NullPointerException。
我不知道为什么,但是通过在提交前添加 ft.addToBackStack(null);
,问题就消失了。
方法则保持为:
// Replace the fragment corresponding to the category
private void replaceFragment(int position) {
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_category_container, this.categories.get(position));
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
我有一个 activity,其中包含一个 TabLayout 并为每个选项卡使用一个片段。在 return 中,该片段包含另一个用于 ViewPager 的片段。这是我的设置:
RoutineActivity.java
public class RoutineActivity extends AppCompatActivity {
private ArrayList<CategoryFragment> categories = new ArrayList<>();
private TabLayout tabs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_routine);
// Set the toolbar as the action bar and add the Up button
Toolbar toolbar = findViewById(R.id.routine_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Get the intent and extract the string array
Intent intent = getIntent();
ArrayList<String> selected =
intent.getStringArrayListExtra(SelectCategoriesActivity.EXTRA_CATEGORIES);
// Set the TabLayout
this.setTabs(selected);
}
// Set the tabs of the TabLayout given a list of tab names
private void setTabs(ArrayList<String> tabs) {
// Setup the tabs and create a fragment for each category
this.tabs = findViewById(R.id.category_tabs);
for (int x = 0; x < tabs.size(); x++) {
TabLayout.Tab tab = this.tabs.newTab().setText(tabs.get(x));
// Select the first tab
if (x == 0) {
this.tabs.addTab(tab, true);
} else {
this.tabs.addTab(tab);
}
// Store the created fragment
CategoryFragment categoryFragment = CategoryFragment.newInstance(tabs.get(x));
this.categories.add(categoryFragment);
}
// Add the first fragment
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_category_container, this.categories.get(0))
.commit();
// Add the tab selected listener
this.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
RoutineActivity.this.replaceFragment(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
// Replace the fragment corresponding to the category
private void replaceFragment(int position) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_category_container, this.categories.get(position));
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
CategoryFragment.java
public class CategoryFragment extends Fragment {
private ViewPager pager;
private ScreenSlidePagerAdapter pagerAdapter;
public CategoryFragment() {} // Required empty public constructor
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<String> selected = new ArrayList<>();
selected.add("1 " + getArguments().get("CATEGORY"));
selected.add("2 " + getArguments().get("CATEGORY"));
selected.add("3 " + getArguments().get("CATEGORY"));
selected.add("4 " + getArguments().get("CATEGORY"));
// Instantiate the PageAdapter
this.pagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager(), selected);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_category, container, false);
// Instantiate ViewPager and set its adapter
this.pager = view.findViewById(R.id.exercise_pager);
this.pager.setAdapter(this.pagerAdapter);
return view;
}
// Return a new instance of this fragment
public static CategoryFragment newInstance(String category) {
Bundle args = new Bundle();
args.putString("CATEGORY", category);
CategoryFragment categoryFragment = new CategoryFragment();
categoryFragment.setArguments(args);
return categoryFragment;
}
// The page adapter
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<String> selected;
public ScreenSlidePagerAdapter(FragmentManager fm, ArrayList<String> data) {
super(fm);
this.selected = data;
}
@Override
public Fragment getItem(int position) {
return ExerciseFragment.newInstance(this.selected.get(position));
}
@Override
public int getCount() {
return this.selected.size();
}
}
}
ExerciseFragment.java
public class ExerciseFragment extends Fragment {
public ExerciseFragment() {} // Required empty public constructor
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(
R.layout.fragment_exercise_page, container, false);
// Change the header to the category
TextView tv = rootView.findViewById(R.id.textView_header);
tv.setText(getArguments().getString("CAT"));
return rootView;
}
public static ExerciseFragment newInstance(String category) {
Bundle args = new Bundle();
args.putString("CAT", category);
ExerciseFragment exerciseFragment = new ExerciseFragment();
exerciseFragment.setArguments(args);
return exerciseFragment;
}
}
LogCat
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.util.SparseArray.get(int)' on a null object reference
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:902)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:216)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1453)
at android.view.View.dispatchRestoreInstanceState(View.java:16886)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3470)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3476)
at android.view.View.restoreHierarchyState(View.java:16864)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:500)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
at android.support.v4.app.FragmentManagerImpl.run(FragmentManager.java:700)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
问题是,当我 select 一个新标签时,一切正常,添加了一个新片段,但是当我返回到上一个标签时,整个应用程序崩溃了。我认为问题在于 ViewPager 如何恢复片段,但我不知道如何修复它。我已将 FragmentStatePagerAdapter
替换为 FragmentPagerAdapter
,问题就消失了。但是,我需要使用前者,因为我将使用很多页面(超过 20 页)。我只需要 CategoryFragment 来保留它停止的片段。有什么想法吗?
我在 SupportFragmentManager 中发现了问题,当您向后移动时,mActive 片段数组的第一个元素为 null,这会产生 NullPointerException。
我不知道为什么,但是通过在提交前添加 ft.addToBackStack(null);
,问题就消失了。
方法则保持为:
// Replace the fragment corresponding to the category
private void replaceFragment(int position) {
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_category_container, this.categories.get(position));
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}