首页 > 代码库 > 解决Fragment中使用ViewPager时,ViewPager里的Fragment错位和空白问题。
解决Fragment中使用ViewPager时,ViewPager里的Fragment错位和空白问题。
这两天开始在改OSChina的开源android客户端,打算用Fragment来分离Main这个Activity里的功能。用Fragment嵌套ViewPager+Fragment的时候发现问题。
红色框的是主Fragment,蓝色框是主Fragment内嵌的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