1.有问题的代码:
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private HomeFragment mHomeFragment; private FindFragment mFindFragment; private NewFragment mNewFragment; private MessageFragment mMessageFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.home_rb).setOnClickListener(this); findViewById(R.id.find_rb).setOnClickListener(this); findViewById(R.id.new_rb).setOnClickListener(this); findViewById(R.id.message_rb).setOnClickListener(this); // 默认一进入页面就添加主Fragment FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); mHomeFragment = new HomeFragment(); fragmentTransaction.add(R.id.main_tab_fl, mHomeFragment); // 最后记得提交 fragmentTransaction.commit(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.home_rb: homeRbClick(); break; case R.id.find_rb: findRbClick(); break; case R.id.new_rb: newRbClick(); break; case R.id.message_rb: messageRbClick(); break; } } private void homeRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // 替换成当前页面 fragmentTransaction.replace(R.id.main_tab_fl, mHomeFragment); fragmentTransaction.commit(); } private void findRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (mFindFragment == null) { mFindFragment = new FindFragment(); } fragmentTransaction.replace(R.id.main_tab_fl, mFindFragment); fragmentTransaction.commit(); } private void newRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (mNewFragment == null) { mNewFragment = new NewFragment(); } fragmentTransaction.replace(R.id.main_tab_fl, mNewFragment); fragmentTransaction.commit(); } private void messageRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (mMessageFragment == null) { mMessageFragment = new MessageFragment(); } fragmentTransaction.replace(R.id.main_tab_fl, mMessageFragment); fragmentTransaction.commit(); } }
目前的效果是这个样子的,看似没有任何的问题,这个也是最简单的方式
别的思路问题:一般的思路我们会换实现方法,当然其他方式肯定也可以实现如ViewPager+Fragment但是我们需要预加载要不然也会出问题,一旦预加载就需要去访问网络,即使用户可能不切换Fragment就退出App了这个时候其实加载了所有Fragment的数据,而且主页一旦复杂有可能会崩溃或造成内存溢出的问题。
2.源码分析:
add方法其实只是设置了一些必要参数,并没有做任何的处理,这也是说google为什么一定要我们不要忘记commit()的原因:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
public FragmentTransaction add(int containerViewId, Fragment fragment) { doAddOp(containerViewId, fragment, null, OP_ADD); return this; } private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { fragment.mFragmentManager = mManager; // tag可以说是唯一标识我们可以通过它从FragmentManager中找到对应的Fragment if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { throw new IllegalStateException("Can't change tag of fragment " + fragment + ": was " + fragment.mTag + " now " + tag); } fragment.mTag = tag; } if (containerViewId != 0) { if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { throw new IllegalStateException("Can't change container ID of fragment " + fragment + ": was " + fragment.mFragmentId + " now " + containerViewId); } // 把Fragment的ContainerId和FragmentId指定为我们传递过来的布局中的ViewGroup的id。 fragment.mContainerId = fragment.mFragmentId = containerViewId; } // 见名思意 Op是什么?就当是一些基本参数吧 Op op = new Op(); op.cmd = opcmd; op.fragment = fragment; addOp(op); } void addOp(Op op) { if (mHead == null) { mHead = mTail = op; } else { op.prev = mTail; mTail.next = op; mTail = op; } op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; mNumOp++; }
既然add方法只是设置了一些参数而已,那么肯定就在commit()中做了些什么,找啊找啊找啊找,找到这么个方法(有些代码我就省略):
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive){ // ... 省略部分代码 f.onAttach(mHost.getContext()); // 这个方法一调用就会执行Fragment的onAttach(Activity activity)这个生命周期方法 if (f.mParentFragment == null) { mHost.onAttachFragment(f); } if (!f.mRetaining) { f.performCreate(f.mSavedFragmentState); // 执行生命周期onCreate(savedInstanceState); } f.mRetaining = false; if (f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { //从activity中找到我们需要存放Fragment的ViewGroup布局 container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + f.getResources().getResourceName(f.mContainerId) + ") for fragment " + f)); } } // For fragments that are part of the content view // layout, we need to instantiate the view immediately // and the inflater will take care of adding it. f.mView = f.performCreateView(f.getLayoutInflater( f.mSavedFragmentState), null, f.mSavedFragmentState); // 这个方法过后会执行onCreateView()生命周期且f.mView就是我们自己覆盖Fragment返回的View if (f.mView != null) { f.mInnerView = f.mView; // v4包兼容11以下的版本我还是没说错啊 if (Build.VERSION.SDK_INT >= 11) { ViewCompat.setSaveFromParentEnabled(f.mView, false); } else { f.mView = NoSaveStateFrameLayout.wrap(f.mView); } if (container != null) { Animation anim = loadAnimation(f, transit, true, transitionStyle); if (anim != null) { setHWLayerAnimListenerIfAlpha(f.mView, anim); f.mView.startAnimation(anim); } // 如果ViewGroup不等于null就把从onCreateView()生命周期中获得的View添加到该布局中 // 最主要的就是这个方法,其实我们可以把Fragment理解成一个自定义的类 // 通过onCreateView()获取的到View添加到一个FragmentActivity的一个ViewGroup中 // 只不过它有自己的生命周期而已...... container.addView(f.mView); } // 如果是隐藏那就设置为不可见 if (f.mHidden) f.mView.setVisibility(View.GONE); // 执行onViewCreated()生命周期方法 f.onViewCreated(f.mView, f.mSavedFragmentState); } else { f.mInnerView = null; } f.performActivityCreated(f.mSavedFragmentState); if (f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; } // 代码省略...... } } // 后面的我们就不看了,这上面的代码我自己做了一些整合,把它连贯起来了 // 因为我们把add方法写在了Activity中的onCreate()方法中所以做了一些处理......
到这里应该能够了解Fragment的工作流程了吧,接下来我们看replace方法中究竟做了?其实和add差不多只是把int opcmd变成了OP_REPLACE替换操作:
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; }
这个时候去commit会调用mManager.removeFragment(old, transition, transitionStyle)方法把原来的移除,然后把当前的Fragment添加进去,那岂不是每点击一个上一就被销毁了,那之前动画到哪里来了做了写什么事都被干掉重新创建了
View Code
if (mManager.mAdded != null) { for (int i = mManager.mAdded.size() - 1; i >= 0; i--) { Fragment old = mManager.mAdded.get(i); if (old.mContainerId == containerId) { if (old == f) { op.fragment = f = null; } else { if (op.removed == null) { op.removed = new ArrayList<Fragment>(); } op.removed.add(old); old.mNextAnim = exitAnim; if (mAddToBackStack) { old.mBackStackNesting += 1; } mManager.removeFragment(old, transition, transitionStyle); } } } } if (f != null) { f.mNextAnim = enterAnim; mManager.addFragment(f, false); }
3.优化代码:
到这里源码就以完毕有兴趣的小伙伴可以自己仔细去看看源码,接下来我们就来解决问题,我们肯定在调用replace方法的时候希望它不要移除原来的,那怎么办改Android的底层源码吗?那就只能换方法了,思路就是如果该Fragment不存在FragmentManager中我们就去添加,否则我们把之前的隐藏而不是替换移除,把当前的显示即可,最后代码就是:
private void homeRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); List<Fragment> fragments = fragmentManager.getFragments(); for (Fragment fragment : fragments) { fragmentTransaction.hide(fragment); } fragmentTransaction.show(mHomeFragment); fragmentTransaction.commit(); } @OnClick(R.id.find_rb) private void findRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); List<Fragment> fragments = fragmentManager.getFragments(); for (Fragment fragment : fragments) { fragmentTransaction.hide(fragment); } if(mFindFragment == null){ mFindFragment = new FindFragment(); fragmentTransaction.add(R.id.main_tab_fl,mFindFragment); }else { fragmentTransaction.show(mFindFragment); } fragmentTransaction.commit(); } @OnClick(R.id.new_rb) private void newRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); List<Fragment> fragments = fragmentManager.getFragments(); for (Fragment fragment : fragments) { fragmentTransaction.hide(fragment); } if(mNewFragment == null){ mNewFragment = new NewFragment(); fragmentTransaction.add(R.id.main_tab_fl,mNewFragment); }else { fragmentTransaction.show(mNewFragment); } fragmentTransaction.commit(); } @OnClick(R.id.message_rb) private void messageRbClick() { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); List<Fragment> fragments = fragmentManager.getFragments(); for (Fragment fragment : fragments) { fragmentTransaction.hide(fragment); } if(mMessageFragment == null){ mMessageFragment = new MessageFragment(); fragmentTransaction.add(R.id.main_tab_fl,mMessageFragment); }else { fragmentTransaction.show(mMessageFragment); } fragmentTransaction.commit(); }
封装
1 public class FragmentManagerHelper { 2 // 管理类FragmentManager 3 private FragmentManager mFragmentManager; 4 // 容器布局id containerViewId 5 private int mContainerViewId; 6 7 /** 8 * 构造函数 9 * @param fragmentManager 管理类FragmentManager 10 * @param containerViewId 容器布局id containerViewId 11 */ 12 public FragmentManagerHelper(@Nullable FragmentManager fragmentManager, @IdRes int containerViewId) { 13 this.mFragmentManager = fragmentManager; 14 this.mContainerViewId = containerViewId; 15 } 16 17 /** 18 * 添加Fragment 19 */ 20 public void add(Fragment fragment){ 21 // 开启事物 22 FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); 23 // 第一个参数是Fragment的容器id,需要添加的Fragment 24 fragmentTransaction.add(mContainerViewId, fragment); 25 // 一定要commit 26 fragmentTransaction.commit(); 27 } 28 29 /** 30 * 切换显示Fragment 31 */ 32 public void switchFragment(Fragment fragment){ 33 // 开启事物 34 FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); 35 36 // 1.先隐藏当前所有的Fragment 37 List<Fragment> childFragments = mFragmentManager.getFragments(); 38 for (Fragment childFragment : childFragments) { 39 fragmentTransaction.hide(childFragment); 40 } 41 42 // 2.如果容器里面没有我们就添加,否则显示 43 if(!childFragments.contains(fragment)){ 44 fragmentTransaction.add(mContainerViewId,fragment); 45 }else{ 46 fragmentTransaction.show(fragment); 47 } 48 49 // 替换Fragment 50 // fragmentTransaction.replace(R.id.main_tab_fl,mHomeFragment); 51 // 一定要commit 52 fragmentTransaction.commit(); 53 } 54 }