解决Fragment中使用ViewPager时,ViewPager里的Fragment错位和空白问题

时间:2021-06-11 04:31:27

这两天开始在改OSChina的开源android客户端,打算用Fragment来分离Main这个Activity里的功能。用Fragment嵌套ViewPager+Fragment的时候发现问题。

红色框的是主Fragment,蓝色框是主Fragment内嵌的ViewPager+Fragment。

解决Fragment中使用ViewPager时,ViewPager里的Fragment错位和空白问题

例如当”资讯“切换到”问答“的时候,”问答“内的ViewPager+Fragment显示不符合预期,因为里面的Fragment错位了,前面几个显示的是”资讯“里面的Fragment。

而且有些显示Fragment显示空白。检查了下没问题,查看源代码发现是创建FragmentPagerAdapter时用getFragmentManager()传入的FragmentManager都是获取自Activity的同一个FragmentManager。

FragmentManager里用ArrayList自动缓存了Fragment,如果两个主Fragment用同样的布局ID会使得缓存的tag相同,结果会导致子Fragment互相替换。

FragmentPagerAdapter里的源代码:

 1 @Override
2 public Object instantiateItem(ViewGroup container, int position) {
3 if (mCurTransaction == null) {
4 mCurTransaction = mFragmentManager.beginTransaction();
5 }
6
7 final long itemId = getItemId(position);
8
9 // Do we already have this fragment?
10 String name = makeFragmentName(container.getId(), itemId);
11 Fragment fragment = mFragmentManager.findFragmentByTag(name);
12 if (fragment != null) {
13 if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
14 mCurTransaction.attach(fragment);
15 } else {
16 fragment = getItem(position);
17 if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
18 mCurTransaction.add(container.getId(), fragment,
19 makeFragmentName(container.getId(), itemId));
20 }
21 if (fragment != mCurrentPrimaryItem) {
22 fragment.setMenuVisibility(false);
23 fragment.setUserVisibleHint(false);
24 }
25
26 return fragment;
27 }

还有Fragment显示空白的问题,打印所有Fragment的生命周期发现,当”资讯“切换到”问答“的时候主Fragment会执行onCreate到onResume,但是ViewPager里的Fragment却没动静。

调用 notifyDataSetChanged()也无法刷新子Fragment。查看源代码后发现使用getChildFragmentManager()来替代getFragmentManager()获取FragmentManager能解决问题,

因为getChildFragmentManager()会为本Fragment创建一个私有的FragmentManager。

 1 /**
2 * Return the FragmentManager for interacting with fragments associated
3 * with this fragment's activity. Note that this will be non-null slightly
4 * before {@link #getActivity()}, during the time from when the fragment is
5 * placed in a {@link FragmentTransaction} until it is committed and
6 * attached to its activity.
7 *
8 * <p>If this Fragment is a child of another Fragment, the FragmentManager
9 * returned here will be the parent's {@link #getChildFragmentManager()}.
10 */
11 final public FragmentManager getFragmentManager() {
12 return mFragmentManager;
13 }
14
15 /**
16 * Return a private FragmentManager for placing and managing Fragments
17 * inside of this Fragment.
18 */
19 final public FragmentManager getChildFragmentManager() {
20 if (mChildFragmentManager == null) {
21 instantiateChildFragmentManager();
22 if (mState >= RESUMED) {
23 mChildFragmentManager.dispatchResume();
24 } else if (mState >= STARTED) {
25 mChildFragmentManager.dispatchStart();
26 } else if (mState >= ACTIVITY_CREATED) {
27 mChildFragmentManager.dispatchActivityCreated();
28 } else if (mState >= CREATED) {
29 mChildFragmentManager.dispatchCreate();
30 }
31 }
32 return mChildFragmentManager;
33 }
 1 void instantiateChildFragmentManager() {
2 mChildFragmentManager = new FragmentManagerImpl();
3 mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
4 @Override
5 public View findViewById(int id) {
6 if (mView == null) {
7 throw new IllegalStateException("Fragment does not have a view");
8 }
9 return mView.findViewById(id);
10 }
11 }, this);
12 }

同时还会根据本Fragment现在所处的状态来更新私有FragmentManager里所缓存的Fragment。

 1     ArrayList<Fragment> mActive;
2 ArrayList<Fragment> mAdded;
3 ArrayList<Integer> mAvailIndices;
4 ArrayList<BackStackRecord> mBackStack;
5 ArrayList<Fragment> mCreatedMenus;
6
7 public void dispatchStart() {
8 mStateSaved = false;
9 moveToState(Fragment.STARTED, false);
10 }
11
12 public void dispatchResume() {
13 mStateSaved = false;
14 moveToState(Fragment.RESUMED, false);
15 }
16
17 public void dispatchPause() {
18 moveToState(Fragment.STARTED, false);
19 }
20
21 public void dispatchStop() {
22 // See saveAllState() for the explanation of this. We do this for
23 // all platform versions, to keep our behavior more consistent between
24 // them.
25 mStateSaved = true;
26
27 moveToState(Fragment.STOPPED, false);
28 }
29
30 public void dispatchReallyStop() {
31 moveToState(Fragment.ACTIVITY_CREATED, false);
32 }
33
34 public void dispatchDestroyView() {
35 moveToState(Fragment.CREATED, false);
36 }
37
38 public void dispatchDestroy() {
39 mDestroyed = true;
40 execPendingActions();
41 moveToState(Fragment.INITIALIZING, false);
42 mActivity = null;
43 mContainer = null;
44 mParent = null;
45 }
46
47 void moveToState(int newState, int transit, int transitStyle, boolean always) {
48 if (mActivity == null && newState != Fragment.INITIALIZING) {
49 throw new IllegalStateException("No activity");
50 }
51
52 if (!always && mCurState == newState) {
53 return;
54 }
55
56 mCurState = newState;
57 if (mActive != null) {
58 boolean loadersRunning = false;
59 for (int i=0; i<mActive.size(); i++) {
60 Fragment f = mActive.get(i);
61 if (f != null) {
62 moveToState(f, newState, transit, transitStyle, false); //更新Fragment
63 if (f.mLoaderManager != null) {
64 loadersRunning |= f.mLoaderManager.hasRunningLoaders(); //是否在装载中
65 }
66 }
67 }
68
69 if (!loadersRunning) {
70 startPendingDeferredFragments(); //不是的话启动更新
71 }
72
73 if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) {
74 mActivity.supportInvalidateOptionsMenu(); // 刷新选项菜单
75 mNeedMenuInvalidate = false;
76 }
77 }
78 }
79
80 void startPendingDeferredFragments() {
81 if (mActive == null) return;
82
83 for (int i=0; i<mActive.size(); i++) {
84 Fragment f = mActive.get(i);
85 if (f != null) {
86 performPendingDeferredStart(f);
87 }
88 }
89 }
90
91

根据Fragment所处的状态,启动和恢复Fragment的视图。

  1 void moveToState(Fragment f, int newState, int transit, int transitionStyle,
2 boolean keepActive) {
3 // Fragments that are not currently added will sit in the onCreate() state.
4 if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
5 newState = Fragment.CREATED;
6 }
7 if (f.mRemoving && newState > f.mState) {
8 // While removing a fragment, we can't change it to a higher state.
9 newState = f.mState;
10 }
11 // Defer start if requested; don't allow it to move to STARTED or higher
12 // if it's not already started.
13 if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
14 newState = Fragment.STOPPED;
15 }
16 if (f.mState < newState) {
17 // For fragments that are created from a layout, when restoring from
18 // state we don't want to allow them to be created until they are
19 // being reloaded from the layout.
20 if (f.mFromLayout && !f.mInLayout) {
21 return;
22 }
23 if (f.mAnimatingAway != null) {
24 // The fragment is currently being animated... but! Now we
25 // want to move our state back up. Give up on waiting for the
26 // animation, move to whatever the final state should be once
27 // the animation is done, and then we can proceed from there.
28 f.mAnimatingAway = null;
29 moveToState(f, f.mStateAfterAnimating, 0, 0, true);
30 }
31 switch (f.mState) {
32 case Fragment.INITIALIZING:
33 if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
34 if (f.mSavedFragmentState != null) {
35 f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
36 FragmentManagerImpl.VIEW_STATE_TAG);
37 f.mTarget = getFragment(f.mSavedFragmentState,
38 FragmentManagerImpl.TARGET_STATE_TAG);
39 if (f.mTarget != null) {
40 f.mTargetRequestCode = f.mSavedFragmentState.getInt(
41 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
42 }
43 f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
44 FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
45 if (!f.mUserVisibleHint) {
46 f.mDeferStart = true;
47 if (newState > Fragment.STOPPED) {
48 newState = Fragment.STOPPED;
49 }
50 }
51 }
52 f.mActivity = mActivity;
53 f.mParentFragment = mParent;
54 f.mFragmentManager = mParent != null
55 ? mParent.mChildFragmentManager : mActivity.mFragments;
56 f.mCalled = false;
57 f.onAttach(mActivity);
58 if (!f.mCalled) {
59 throw new SuperNotCalledException("Fragment " + f
60 + " did not call through to super.onAttach()");
61 }
62 if (f.mParentFragment == null) {
63 mActivity.onAttachFragment(f);
64 }
65
66 if (!f.mRetaining) {
67 f.performCreate(f.mSavedFragmentState);
68 }
69 f.mRetaining = false;
70 if (f.mFromLayout) {
71 // For fragments that are part of the content view
72 // layout, we need to instantiate the view immediately
73 // and the inflater will take care of adding it.
74 f.mView = f.performCreateView(f.getLayoutInflater(
75 f.mSavedFragmentState), null, f.mSavedFragmentState);
76 if (f.mView != null) {
77 f.mInnerView = f.mView;
78 f.mView = NoSaveStateFrameLayout.wrap(f.mView);
79 if (f.mHidden) f.mView.setVisibility(View.GONE);
80 f.onViewCreated(f.mView, f.mSavedFragmentState);
81 } else {
82 f.mInnerView = null;
83 }
84 }
85 case Fragment.CREATED:
86 if (newState > Fragment.CREATED) {
87 if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
88 if (!f.mFromLayout) {
89 ViewGroup container = null;
90 if (f.mContainerId != 0) {
91 container = (ViewGroup)mContainer.findViewById(f.mContainerId);
92 if (container == null && !f.mRestored) {
93 throwException(new IllegalArgumentException(
94 "No view found for id 0x"
95 + Integer.toHexString(f.mContainerId) + " ("
96 + f.getResources().getResourceName(f.mContainerId)
97 + ") for fragment " + f));
98 }
99 }
100 f.mContainer = container;
101 f.mView = f.performCreateView(f.getLayoutInflater(
102 f.mSavedFragmentState), container, f.mSavedFragmentState);
103 if (f.mView != null) {
104 f.mInnerView = f.mView;
105 f.mView = NoSaveStateFrameLayout.wrap(f.mView);
106 if (container != null) {
107 Animation anim = loadAnimation(f, transit, true,
108 transitionStyle);
109 if (anim != null) {
110 f.mView.startAnimation(anim);
111 }
112 container.addView(f.mView);
113 }
114 if (f.mHidden) f.mView.setVisibility(View.GONE);
115 f.onViewCreated(f.mView, f.mSavedFragmentState);
116 } else {
117 f.mInnerView = null;
118 }
119 }
120
121 f.performActivityCreated(f.mSavedFragmentState);
122 if (f.mView != null) {
123 f.restoreViewState(f.mSavedFragmentState);
124 }
125 f.mSavedFragmentState = null;
126 }
127 case Fragment.ACTIVITY_CREATED:
128 case Fragment.STOPPED:
129 if (newState > Fragment.STOPPED) {
130 if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
131 f.performStart();
132 }
133 case Fragment.STARTED:
134 if (newState > Fragment.STARTED) {
135 if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
136 f.mResumed = true;
137 f.performResume();
138 f.mSavedFragmentState = null;
139 f.mSavedViewState = null;
140 }
141 }
142 } else if (f.mState > newState) {
143 switch (f.mState) {
144 case Fragment.RESUMED:
145 if (newState < Fragment.RESUMED) {
146 if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
147 f.performPause();
148 f.mResumed = false;
149 }
150 case Fragment.STARTED:
151 if (newState < Fragment.STARTED) {
152 if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
153 f.performStop();
154 }
155 case Fragment.STOPPED:
156 if (newState < Fragment.STOPPED) {
157 if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f);
158 f.performReallyStop();
159 }
160 case Fragment.ACTIVITY_CREATED:
161 if (newState < Fragment.ACTIVITY_CREATED) {
162 if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
163 if (f.mView != null) {
164 // Need to save the current view state if not
165 // done already.
166 if (!mActivity.isFinishing() && f.mSavedViewState == null) {
167 saveFragmentViewState(f);
168 }
169 }
170 f.performDestroyView();
171 if (f.mView != null && f.mContainer != null) {
172 Animation anim = null;
173 if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
174 anim = loadAnimation(f, transit, false,
175 transitionStyle);
176 }
177 if (anim != null) {
178 final Fragment fragment = f;
179 f.mAnimatingAway = f.mView;
180 f.mStateAfterAnimating = newState;
181 anim.setAnimationListener(new AnimationListener() {
182 @Override
183 public void onAnimationEnd(Animation animation) {
184 if (fragment.mAnimatingAway != null) {
185 fragment.mAnimatingAway = null;
186 moveToState(fragment, fragment.mStateAfterAnimating,
187 0, 0, false);
188 }
189 }
190 @Override
191 public void onAnimationRepeat(Animation animation) {
192 }
193 @Override
194 public void onAnimationStart(Animation animation) {
195 }
196 });
197 f.mView.startAnimation(anim);
198 }
199 f.mContainer.removeView(f.mView);
200 }
201 f.mContainer = null;
202 f.mView = null;
203 f.mInnerView = null;
204 }
205 case Fragment.CREATED:
206 if (newState < Fragment.CREATED) {
207 if (mDestroyed) {
208 if (f.mAnimatingAway != null) {
209 // The fragment's containing activity is
210 // being destroyed, but this fragment is
211 // currently animating away. Stop the
212 // animation right now -- it is not needed,
213 // and we can't wait any more on destroying
214 // the fragment.
215 View v = f.mAnimatingAway;
216 f.mAnimatingAway = null;
217 v.clearAnimation();
218 }
219 }
220 if (f.mAnimatingAway != null) {
221 // We are waiting for the fragment's view to finish
222 // animating away. Just make a note of the state
223 // the fragment now should move to once the animation
224 // is done.
225 f.mStateAfterAnimating = newState;
226 newState = Fragment.CREATED;
227 } else {
228 if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
229 if (!f.mRetaining) {
230 f.performDestroy();
231 }
232
233 f.mCalled = false;
234 f.onDetach();
235 if (!f.mCalled) {
236 throw new SuperNotCalledException("Fragment " + f
237 + " did not call through to super.onDetach()");
238 }
239 if (!keepActive) {
240 if (!f.mRetaining) {
241 makeInactive(f);
242 } else {
243 f.mActivity = null;
244 f.mFragmentManager = null;
245 }
246 }
247 }
248 }
249 }
250 }
251
252 f.mState = newState;
253 }

我这里有support V4、V7、V13包的源代码。共享在百度云盘,需要的可以去下载。http://pan.baidu.com/s/1ntkbxpB