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

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

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

例如当”资讯“切换到”问答“的时候,”问答“内的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