FragmentTransaction add 和 replace 区别

2024-06-24 04:18

本文主要是介绍FragmentTransaction add 和 replace 区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace ,对这两个方法的区别一直有点疑惑。我觉得使用 add 的话,在按返回键应该是回退到上一个 Fragment,而使用 replace 的话,那个别 replace 的就已经不存在了,所以就不会回退了。但事实不是这样子的。add 和 replace 影响的只是界面,而控制回退的,是事务。

public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag)

Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) into a container view of the activity.

add 是把一个fragment添加到一个容器 container 里。

public abstract FragmentTransaction replace (int containerViewId, Fragment fragment, String tag)

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

replace 是先remove掉相同id的所有fragment,然后在add当前的这个fragment。

在大部分情况下,这两个的表现基本相同。因为,一般,咱们会使用一个FrameLayout来当容器,而每个Fragment被add 或者 replace 到这个FrameLayout的时候,都是显示在最上层的。所以你看到的界面都是一样的。但是,使用add的情况下,这个FrameLayout其实有2层,多层肯定要比一层的来得浪费,所以还是推荐使用replace。当然有时候还是需要使用add的。比如要实现轮播图的效果,每个轮播图都是一个独立的Fragment,而他的容器FrameLayout需要add多个Fragment,这样他就可以根据提供的逻辑进行轮播了。

而至于返回键的时候,这个跟事务有关,跟使用add还是replace没有任何关系。

2015.08.04 更新。

上面是通过文档上分析的,下面是代码实现。FragmentManager 是一个抽象类,实现类是 FragmentManagerImpl ,跟 FragmentManager 在同一个类文件里。FragmentTransaction 也是一个抽象类,具体实现是 BackStackRecord 。BackStackRecord 其实是一个封装了一个队列。咱们看 add 方法和 replace 方法。

add 方法和 replace 方法都是把一个操作 OP_XX 放入到队列里,Op 是其内部封装的一个操作的类。在 BackStackRecord 的 run 方法里,每次会从队列的头(mHead)获取一个操作 Op ,如果 Op 操作是 add ,则调用 FragmentManager 的 addFragment() 方法,如果 Op 操作是 replace ,则先调用 FragmentManager 的 removeFragment() 方法,然后再调用 addFragment() 方法。

下面是 add 方法。

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {doAddOp(containerViewId, fragment, tag, OP_ADD);return this;
}

下面是 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;
}

add 和 replace 方法都是调用的 doAddOp 方法。也就是把一个操作 Op 添加到队列。

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {fragment.mFragmentManager = mManager;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.mContainerId = fragment.mFragmentId = containerViewId;}Op op = new Op();op.cmd = opcmd;op.fragment = fragment;addOp(op);
}

run 方法才是真正执行的方法。什么时候执行先不考虑,只需要知道一系列的操作会一次执行,而不是一个操作执行一次。
run 方法有点大,就看一下 while 循环开始和结束的时候,以及 switch case 里 OP_ADD 和 OP_REPLACE 分支就可以了。

public void run() {if (FragmentManagerImpl.DEBUG) {Log.v(TAG, "Run: " + this);}if (mAddToBackStack) {if (mIndex < 0) {throw new IllegalStateException("addToBackStack() called after commit()");}}bumpBackStackNesting(1);SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();calculateFragments(firstOutFragments, lastInFragments);beginTransition(firstOutFragments, lastInFragments, false);// 获取队列的头Op op = mHead;while (op != null) {switch (op.cmd) {case OP_ADD: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.addFragment(f, false);//添加}break;case OP_REPLACE: {Fragment f = op.fragment;if (mManager.mAdded != null) {for (int i = 0; i < mManager.mAdded.size(); i++) {Fragment old = mManager.mAdded.get(i);if (FragmentManagerImpl.DEBUG) {Log.v(TAG,"OP_REPLACE: adding=" + f + " old=" + old);}if (f == null || old.mContainerId == f.mContainerId) {if (old == f) {op.fragment = f = null;} else {if (op.removed == null) {op.removed = new ArrayList<Fragment>();}op.removed.add(old);old.mNextAnim = op.exitAnim;if (mAddToBackStack) {old.mBackStackNesting += 1;if (FragmentManagerImpl.DEBUG) {Log.v(TAG, "Bump nesting of "+ old + " to " + old.mBackStackNesting);}}mManager.removeFragment(old, mTransition, mTransitionStyle);//删除}}}}if (f != null) {f.mNextAnim = op.enterAnim;mManager.addFragment(f, false);//添加}}break;case OP_REMOVE: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.removeFragment(f, mTransition, mTransitionStyle);}break;case OP_HIDE: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.hideFragment(f, mTransition, mTransitionStyle);}break;case OP_SHOW: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.showFragment(f, mTransition, mTransitionStyle);}break;case OP_DETACH: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.detachFragment(f, mTransition, mTransitionStyle);}break;case OP_ATTACH: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.attachFragment(f, mTransition, mTransitionStyle);}break;default: {throw new IllegalArgumentException("Unknown cmd: " + op.cmd);}}op = op.next;//队列的下一个}mManager.moveToState(mManager.mCurState, mTransition,mTransitionStyle, true);if (mAddToBackStack) {mManager.addBackStackState(this);}
}

BackStackRecord 的构造器里参数列表里有一个 FragmentManager ,所有 BackStackRecord 其实是有一个 FragmentManager 的引用的,BackStackRecord 可以直接调用 FragmentManager 的 addFragment 方法。
下面是 FragmentManager 的 addFragment() 方法,每次 add 一个 Fragment,Fragment 对象都会被放入到 mAdded 的容器里。

public void addFragment(Fragment fragment, boolean moveToStateNow) {if (mAdded == null) {mAdded = new ArrayList<Fragment>();}if (DEBUG) Log.v(TAG, "add: " + fragment);makeActive(fragment);if (!fragment.mDetached) {if (mAdded.contains(fragment)) {throw new IllegalStateException("Fragment already added: " + fragment);}mAdded.add(fragment);fragment.mAdded = true;fragment.mRemoving = false;if (fragment.mHasMenu && fragment.mMenuVisible) {mNeedMenuInvalidate = true;}if (moveToStateNow) {moveToState(fragment);}}
}

有时候,咱们 add Fragment A, 然后 add Fragment B,B 把 A 都覆盖了,点击菜单的时候 A 和 B 的菜单选项都出来了,这是为什么?原因在下面。当在创建 OptionsMenu 的时候,FragmentManager 遍历了 mAdded 容器,所以 A 和 B 的菜单都被添加进来了。也就是说使用 add 的方式,虽然 B 把 A 覆盖住了,但是 A 还是存活的,而且是活动着的。

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {boolean show = false;ArrayList<Fragment> newMenus = null;if (mAdded != null) {for (int i=0; i<mAdded.size(); i++) {Fragment f = mAdded.get(i);if (f != null) {if (f.performCreateOptionsMenu(menu, inflater)) {show = true;if (newMenus == null) {newMenus = new ArrayList<Fragment>();}newMenus.add(f);}}}}if (mCreatedMenus != null) {for (int i=0; i<mCreatedMenus.size(); i++) {Fragment f = mCreatedMenus.get(i);if (newMenus == null || !newMenus.contains(f)) {f.onDestroyOptionsMenu();}}}mCreatedMenus = newMenus;return show;

}

这篇关于FragmentTransaction add 和 replace 区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089142

相关文章

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

售价599元起! 华为路由器X1/Pro发布 配置与区别一览

《售价599元起!华为路由器X1/Pro发布配置与区别一览》华为路由器X1/Pro发布,有朋友留言问华为路由X1和X1Pro怎么选择,关于这个问题,本期图文将对这二款路由器做了期参数对比,大家看... 华为路由 X1 系列已经正式发布并开启预售,将在 4 月 25 日 10:08 正式开售,两款产品分别为华

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

分辨率三兄弟LPI、DPI 和 PPI有什么区别? 搞清分辨率的那些事儿

《分辨率三兄弟LPI、DPI和PPI有什么区别?搞清分辨率的那些事儿》分辨率这个东西,真的是让人又爱又恨,为了搞清楚它,我可是翻阅了不少资料,最后发现“小7的背包”的解释最让我茅塞顿开,于是,我... 在谈到分辨率时,我们经常会遇到三个相似的缩写:PPI、DPI 和 LPI。虽然它们看起来差不多,但实际应用

GORM中Model和Table的区别及使用

《GORM中Model和Table的区别及使用》Model和Table是两种与数据库表交互的核心方法,但它们的用途和行为存在著差异,本文主要介绍了GORM中Model和Table的区别及使用,具有一... 目录1. Model 的作用与特点1.1 核心用途1.2 行为特点1.3 示例China编程代码2. Tab

Nginx指令add_header和proxy_set_header的区别及说明

《Nginx指令add_header和proxy_set_header的区别及说明》:本文主要介绍Nginx指令add_header和proxy_set_header的区别及说明,具有很好的参考价... 目录Nginx指令add_header和proxy_set_header区别如何理解反向代理?proxy

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使