Fragment指碎片。是Activity界面中的一部分,可理解为模块化的Activity。
(1)Fragment不能独立存在,必须嵌入到Activity中
(2)Fragment具有自己的生命周期,接收它自己的事件,并可以在Activity运行时被添加或删除
(3)Fragment的生命周期直接受所在的Activity的影响。如:当Activity暂停时,它拥有的所有Fragment们都暂停
为了解决不同屏幕分辩率的动态和灵活UI设计。
Fragment可理解为一个"View控制器",将自身承载的View展示到容器View中,自身有 生命周期,且能添加逻辑控制视图。
Fragments 在活动Activity中为不同的屏幕尺寸修改布局配置(小屏幕可能每次显示一个片段,而大屏幕则可以显示两个或更多)
(1)Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。
(2)Fragment可以轻松得创建动态灵活的UI设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
(3)Fragment是一个独立的模块,紧紧地与activity绑定在一起。可以运行中动态地移除、加入、交换等。
(4)Fragment提供一个新的方式让你在不同的安卓设备上统一你的UI。
(5)Fragment 解决Activity间的切换不流畅,轻量切换。
(6)Fragment 替代TabActivity做导航,性能更好。
(7)Fragment 在4.2.版本中新增嵌套fragment使用方法,能够生成更好的界面效果。
(8)Fragment做局部内容更新更方便,原来为了到达这一点要把多个布局放到一个activity里面,现在可以用多Fragment来代替,只有在需要的时候才加载Fragment,提高性能。
(9)可以从startActivityForResult中接收到返回结果,但是View不能。
可总结为以下三点:
但Fragment有许多坑,且生命周期繁多难以管理,Fragment的嵌套更是坑中之坑。
1、 onAttach:Fragment和Activity建立关联的时候调用,可以获得对应的Context或Activity,这里拿到的Activity是mHost.getActivity()
2、 onCreate:Fragment对象初始创建,用于执行初始化操作。
由于Fragment的onCreate调用时,关联的Activity可能没有创建好,所以不要有依赖外部Activity布局的操作。依赖Activity的操作可以放在onActivityCreate中。
3、 onCreateView:为Fragment创建视图(加载布局)时调用(给当前的fragment绘制UI布局,可以使用线程更新UI)
4、 onActivityCreated:当与Fragment关联的Activity中的onCreate方法执行完后调用(表示activity执行onCreate方法完成了的时候会调用此方法)
这个方法里做些和布局、状态恢复有关的操作,如
onViewStateRestored(Bundle)用于一个Fragment在从就状态回复,获取saveInstanceState恢复状态。
以上4步同步于Activity的onCreate
5、 onStart:Fragment可见时调用,将Fragment对象显示给用户。同步于Activity的onStart
6、 onResume:Fragment对象可见并可与用户交互时调用。同步于Activity的onResume
7、 onPause:Fragment对象与用户不再交互。同步于Activity的onPause
8、 onStop:Fragment对象不再显示给用户。同步于Activity的onStop
9、 onDestroyView:Fragment中的布局被移除时调用(表示fragment销毁相关联的UI布局)
10、onDestroy:Fragment状态清理完成
11、 onDetach:Fragment和Activity解除关联的时候调用(脱离activity)
从这个图上可以看出activity的状态决定了fragment可能接收到的回调函数。
当activity处于Resumed状态时,可以自由地添加和移除fragment,也即是说,只有activity在Resumed状态时,fragment的状态可以独立改变。
但是,当activity离开Resumed状态,fragment的生命周期被activity控制。
由于Fragment依赖Activity的存在而存在,Activity的状态决定了Fragment可能接收到的回调函数,故在Activity生命周期中的方法一般均先于Fragment生命周期中的方法执行。
事实上,Fragment的生命周期除了它第一次创建或销毁之外,都是由Activity启动。
1、首次打开界面
Activity:onCreate()
Fragment:onAttach()->onCreate()->onCreateView()->onActivityCreated()
Activity:onStart()
Fragment:onStart()
Activity:onResume()
Fragment:onResume()
2、点击Home返回主界面&返回
Activity:onPause()
Fragment:onPause()
Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()
Activity:onStop()
Fragment:onStop()
Activity:onRestart()
Activity:onStart()
Fragment:onStart()
Activity:onResume()
Fragment:onResume()
onSaveInstanceState()保存状态的方法是在onPause()之后,onStop()之前,Activity与Fragment在这里是同步的,这个方法在onPause和onStop间任意时刻调用
再次从最近活动返回时,activity会多执行一个onRestart()方法,Fragment无此方法
3、正常销毁(后退键返回)
Activity:onPause()
Fragment:onPause()
Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()
Activity:onStop()
Fragment:onStop()
Activity:onStop()
Fragment:onStop()
Fragment:onDestroyView()
Activity:onDestroy()
Fragment:onDestroy()
Fragment:onDetach()
4、后台意外销毁&返回(横竖屏切换)
Activity:onPause()
Fragment:onPause()
Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()
Activity:onStop()
Fragment:onStop()
Fragment:onDestroyView()
Activity:onDestroy()
Fragment:onDestroy()
Fragment:onDetach()
Activity:onCreate()
Fragment:onAttach()->onCreate()->onCreateView()->onActivityCreated()
Activity:onStart()
Fragment:onStart()
Activity:onRestoreInstanceState()
Activity:onResume()
Fragment:onResume()
Activity会额外执行一个还原状态的方法onRestoreInstanceState,介于onStart和onResume之间
情况1:通过add、hide、show方式切换Fragment
1、初始化:
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1隐藏:onHiddenChanged()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
3、Fragment2切换到Fragment1:
Fragment1:onHiddenChanged()
Fragment2:onHiddenChanged()
通过add、hide、show方式切换Fragment时所有的view都会保存在内存,不会销毁与重建
情况2:通过replace方式切换Fragment
1、初始化
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1销毁:onPause()->onStop()->onDestroyView()->onDestroy()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
3、Fragment2切换到Fragment1:
Fragment2销毁:onPause()->onStop()->onDestroyView()->onDestroy()
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
通过 replace 方法进行替换的时,Fragment 都是进行了销毁,重建的过程,相当于走了一整套的生命周期
情况3:通过ViewPager方式切换Fragment
1、初始化
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1不可见:setUserVisVleHint(false)
Fragment2可见:setUserVisVleHint(true)
3、Fragment2切换到Fragment1:
Fragment1可见:setUserVisVleHint(true)
Fragment2不可见:setUserVisVleHint(false)
1、使用ViewPager与Fragment切换时,Fragment会进行预加载操作,即所有的Fragment都会提前初始化。
2、 setUserVisVleHint()方法在 Fragment 1 第一次加载的时候不走,只有在切换的时候 走该方法。
3、主动调用 setUserVisibleHint()方法来控制第一次不会调用setUserVisibleHint方法的问题。
setUserVisibleHint()方法优先onCreateView方法,当onCreateView方法调用后还会再次调用setUserVisibleHint方法。
此时要对是否调用了onCreateView()方法进行标记判断。
/**
* 标志位,标志已经初始化完成
*/
private boolean isPrepared;
/**
*第一个 Fragment 需要处理 setUserVisVleHint()方法,设置为 setUserVisibleHint(true);
*否则会产空指针异常,因为 setUserVisVleHint()方法的优先级高于 onCreate()方法。
*
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
setUserVisibleHint(true);
super.onActivityCreated(savedInstanceState);
}
主动调用 setUserVisibleHint()方法来控制第一次不会调用setUserVisibleHint方法的问题。
setUserVisibleHint()方法优先onCreateView方法,当onCreateView方法调用后还会再次调用setUserVisibleHint方法。
此时要对是否调用了onCreateView()方法进行标记判断。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_a, container, false);
//已经初始化
isPrepared = true;
return view;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//可见的并且是初始化之后才加载
if (isPrepared && isVisibleToUser) {
getList();
}
}
public abstract class FragmentManager {...}
FragmentManager 是一个抽象类,定义了一些和 Fragment 相关的操作和内部类/接口。
1、方法
public abstract FragmentTransaction beginTransaction();//开启一系列对 Fragments 的操作
public abstract boolean executePendingTransactions();//FragmentTransaction.commit() 是异步执行的,如果你想立即执行,可以调用这个方法
public abstract Fragment findFragmentById(@IdRes int id);//根据 ID 找到从 XML 解析出来的或者事务中添加的 Fragment
public abstract Fragment findFragmentByTag(String tag);//跟上面的类似,不同的是使用 tag 进行查找
public abstract void popBackStack();//弹出回退栈中栈顶的 Fragment,异步执行的
public abstract boolean popBackStackImmediate();//立即弹出回退栈中栈顶的,直接执行
public abstract void popBackStack(String name, int flags);//返回栈顶符合名称的,如果传入的 name 不为空,在栈中间找到了 Fragment,那将弹出这个 Fragment 上面的所有 Fragment(类似singleTask)异步执行
public abstract boolean popBackStackImmediate(String name, int flags);//同上,同步执行
public abstract void popBackStack(int id, int flags);
public abstract boolean popBackStackImmediate(int id, int flags);
public abstract int getBackStackEntryCount();//获取回退栈中的元素个数
public abstract BackStackEntry getBackStackEntryAt(int index);//根据索引获取回退栈中的某个元素
public abstract void addOnBackStackChangedListener(OnBackStackChangedListener listener);
public abstract void removeOnBackStackChangedListener(OnBackStackChangedListener listener);//添加或者移除一个监听器
public abstract void putFragment(Bundle bundle, String key, Fragment fragment);//还定义了将一个 Fragment 实例作为参数传递
public abstract Fragment getFragment(Bundle bundle, String key);
public abstract List getFragments();//获取 manager 中所有添加进来的 Fragment
2、内部类/接口
3、实现类FragmentManagerImpl
FragmentManager 定义的任务是由 FragmentManagerImpl 实现的。
FragmentManager定义了一个对Activity/Fragment中添加进来的Fragment列表、Fragment回退栈的操作与管理。包括Fragment的查找、获取,及监听等。
通过FragmentManager获取FragmentTransaction
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
beginTransaction() 返回一个新的 BackStackRecord ,继承自FragmentTransaction
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
1、方法
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
@Nullable String tag);//在Activity添加Fragment,第一个参数表示Fragment的布局id,第二个参数表示要添加的Fragment,第三个参数为为Fragment设置的tag,后续可以用这个tag进行查询
public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment,
@Nullable String tag);//替换宿主中一个已经存在的 fragment,这一个方法等价于先调用 remove(), 再调用 add()
public abstract FragmentTransaction remove(Fragment fragment);//移除一个已经存在的 fragment,如果之前添加到宿主上,那它的布局也会被移除
public abstract FragmentTransaction hide(Fragment fragment);//隐藏一个已存的 fragment,其实就是将添加到宿主上的布局隐藏
public abstract FragmentTransaction show(Fragment fragment);//显示前面隐藏的 fragment,这只适用于之前添加到宿主上的 fragment
public abstract FragmentTransaction detach(Fragment fragment);//将指定的 fragment 将布局上解除,当调用这个方法时,fragment 的布局已经销毁了
public abstract FragmentTransaction attach(Fragment fragment);//当前面解除一个 fragment 的布局绑定后,调用这个方法可以重新绑定,这将导致该 fragment 的布局重建,然后添加、展示到界面上
2、事务提交的四种方式
注意:
commit() 需要在宿主 Activity 保存状态之前调用,否则会报错。
这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的 commit() 将会丢失,这和调用的初衷不符,所以会报错。
此外,这个方法提交的事务可能不会被添加到 FragmentManger 的后退栈,因为你这样直接提交,有可能影响其他异步执行任务在栈中的顺序。
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
关键成员
final FragmentManagerImpl mManager;
//Op 可选的状态值
static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;
ArrayList mOps = new ArrayList<>();//mOps就是栈中所有的Fragment
static final class Op {//Op就是添加了状态和动画信息的Fragment
int cmd; //状态
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
}
int mIndex = -1; //栈中最后一个元素的索引
具体事务实现举例:添加一个Fragment到布局add()
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
final Class fragmentClass = fragment.getClass();
final int modifiers = fragmentClass.getModifiers();
if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
|| (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
+ " must be a public static class to be properly recreated from"
+ " instance state.");
}
//1.修改添加的 fragmentManager 为当前栈的 manager
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 (containerViewId == View.NO_ID) {
throw new IllegalArgumentException("Can't add fragment "
+ fragment + " with tag " + tag + " to container view with no id");
}
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
//2.设置宿主 ID 为布局 ID
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
//3.构造 Op
Op op = new Op();
op.cmd = opcmd; //状态
op.fragment = fragment;
//4.添加到数组列表中
addOp(op);
}
void addOp(Op op) {
mOps.add(op);
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
}
修改 fragmentManager 和 ID,构造成 Op,设置状态信息,然后添加到列表里。
FragmentTransaction是对于Fragment回退栈中事务操作,包括添加add、移除remove、replace替代、hide隐藏、show显示、detach解除、attach附属
主要是执行Fragment栈中操作,用于完成Fragment对其宿主Activity/Fragment的绑定/解除。
Fragment添加本质:
在 onCreateView() 中返回一个 布局,然后在 FragmentManager 中拿到这个布局,添加到要绑定容器(Activity/Fragment)的 ViewGroup 中,然后设置相应的状态值。
步骤1:Activity布局文件
// 该fragment类定义在包名为"com.skywang.app"中的FragmentLayoutTest类的内部类ExampleFragment中
步骤2:Fragment布局文件
步骤3:Activity的.java文件
// 在Activity使用Fragment时,需要考虑版本兼容问题
// 1. Android 3.0后,Activity可直接继承自Activity,并且在其中嵌入使用Fragment
// 2. Android 3.0前,Activity需FragmentActivity(其也继承自Activity),同时需要导入android-support-v4.jar兼容包,这样在Activity中才能嵌入Fragment
public class FragmentLayoutTest extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout_test);
// 设置上述布局文件
}
// 继承自Fragment
// 布局文件中的Fragment通过该FragmentLayoutTest的内部类ExampleFragment实现
public static class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.example_fragment, container, false);
// 将example_fragment.xml作为该Fragment的布局文件
// 即相当于FragmentLayoutTest直接调用example_fragment.xml来显示到Fragment中
}
}
}
步骤1:Activity布局文件定义1占位符(FragmentLayout)
步骤2:设置Fragment布局文件
步骤3:在Activity的.java文件中动态添加Fragment
public class FragmentTransactionTest extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_transaction_test);
// 步骤1:获取FragmentManager
FragmentManager fragmentManager = getFragmentManager();
// 步骤2:获取FragmentTransaction
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
// 步骤3:创建需要添加的Fragment :ExampleFragment
ExampleFragment fragment = new ExampleFragment();
// 步骤4:动态添加fragment
// 即将创建的fragment添加到Activity布局文件中定义的占位符中(FrameLayout)
fragmentTransaction.add(R.id.about_fragment_container, fragment);
fragmentTransaction.commit();
}
// 继承与Fragment
public static class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.example_fragment, container, false);
// 将example_fragment.xml作为该Fragment的布局文件
}
}
}
Fragment 内部有一个 childFragmentManager,通过它管理子 Fragment。在添加子 Fragment 时,把子 Fragment 的布局 add 到父 Fragment 即可。
1、若Fragment存在,则可通过FindFragmentById直接获得Fragment并调用Fragment的共有函数,否则在创建Fragment时将数据放入bundle,并通过setArgument传递bundle对象
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// 用户选中HeadlinesFragment中的头标题后
// 做一些必要的业务操作
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// 如果 article frag 不为空,那么我们在同时显示两个fragmnet的布局中...
// 调用ArticleFragment中的方法去更新它的内容
articleFrag.updateArticleView(position);
} else {
// 否则,我们就是在仅包含一个fragment的布局中并需要交换fragment...
// 创建fragment并给他一个跟选中的文章有关的参数
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// 用这个fragment替换任何在fragment_container中的东西
// 并添加事务到back stack中以便用户可以回退到之前的状态
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// 提交事务
transaction.commit();
}
}
}
2、设置Fragment
public class mFragment extends Fragment {
Button button;
TextView text;
Bundle bundle;
String message;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment, container, false);
// 设置布局文件
button = (Button) contentView.findViewById(R.id.button);
text = (TextView) contentView.findViewById(R.id.text);
// 步骤1:通过getArgments()获取从Activity传过来的全部值
bundle = this.getArguments();
// 步骤2:获取某一值
message = bundle.getString("message");
// 步骤3:设置按钮,将设置的值显示出来
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 显示传递过来的值
text.setText(message);
}
});
return contentView;
}
}
接口回调方式:把实现了某一接口的类所创建的对象的引用 赋给 该接口声明的变量,通过该接口变量 调用 该实现类对象的实现的接口方法。
1、定义接口
定义回调接口,用于Activity与Fragment通信
public interface OnFragmentInteractionListener {
//接口实现方法
void onFragmentInteraction(String data);
}
2、接收方实现接口
接收方通过implements实现接口,并在回调函数中实现具体业务
public class FragmentTestActivity extends AppCompatActivity implements OnFragmentInteractionListener{
...
@Override
public void onFragmentInteraction(String data) {
Toast.makeText(FragmentTestActivity.this,data,Toast.LENGTH_LONG).show();
}
}
3、发送方执行回调函数发送数据
发送方获取实现接口的接收方,并在某个事件(如点击事件)中调用回调方法发送数据
public class Test1Fragment extends Fragment{
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_test1, container, false);
btn1 = (Button)contentView.findViewById(R.id.btn_fragment_1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onButtonPressed();
}
});
return contentView;
}
public void onButtonPressed() {
if (mListener != null) {
mListener.onFragmentInteraction("Hello 我是FragmentTest1");
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
//获取Activity,其中Activity必须实现OnFragmentInteractionListener接口
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
4、接收方接受数据
此时接收方的回调方法被执行,收到了发送方的数据
理解回调函数 回调函数分为接口、发送方与接收方。
1、定义接口:定义接口回调函数
2、接收方实现接口:接收方通过implements实现接口,并在回调函数中实现具体业务
3、发送方执行回调函数发送数据:发送方获取实现接口的接收方,并在某个事件(如点击事件)中调用回调方法发送数据
4、接收方接受数据: 此时接收方的回调方法被执行,收到了发送方的数据
在MenuFragment中的ListView条目点击事件中通过标签获取到MainFragment,并调用对应的setData()方法,将数据设置进去,从而达到数据传递的目的。
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
MainFragment mainFragment =
(MainFragment) getActivity()
.getSupportFragmentManager()
.findFragmentByTag("mainFragment");
mainFragment.setData(mDatas.get(position));
}
});
step1: 在Menuragment中创建一个接口以及接口对应的set方法:
//MenuFragment.java文件中
public interface OnDataTransmissionListener {
public void dataTransmission(String data);
}
public void setOnDataTransmissionListener(OnDataTransmissionListener mListener) {
this.mListener = mListener;
}
step2: 在MenuFragment中的ListView条目点击事件中进行接口进行接口回调:
//MenuFragment.java文件中
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (mListener != null) {
mListener.dataTransmission(mDatas.get(position));
}
}
});
step3: 在MainActivity中根据menuFragment获取到接口的set方法,在这个方法中进行进行数据传递,具体如下:
//在MainActivity.java中
menuFragment.setOnDataTransmissionListener(new MenuFragment.OnDataTransmissionListener() {
@Override
public void dataTransmission(String data) {
mainFragment.setData(data); //注:对应的mainFragment此时应该要用final进行修饰
}
});
简单来说,EventBus是一款针对Android优化的发布/订阅(publish/subscribe)事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者和接收者解耦。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。
show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;replace()的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。
如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
在我使用Fragment过程中,大部分情况下都是用show(),hide(),而不是replace()。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。
(1)add:切换fragment时将fragment隐藏了而不是销毁再创建,故不会让fragment重新刷新
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//第一种方式(add),初始化fragment并添加到事务中,如果为null就new一个
if(f1 == null){
f1 = new MyFragment("消息");
transaction.add(R.id.main_frame_layout, f1);
}
//隐藏所有fragment
hideFragment(transaction);
//显示需要显示的fragment
transaction.show(f1);
//提交事务
transaction.commit();
(2)replace:切换fragment时重新创建fragment,会让fragment重新刷新
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//第二种方式(replace),初始化fragment
if(f1 == null){
f1 = new MyFragment("消息");
}
transaction.replace(R.id.main_frame_layout, f1);
//提交事务
transaction.commit();
(4.1)add
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
final Class fragmentClass = fragment.getClass();
final int modifiers = fragmentClass.getModifiers();
if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
|| (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
+ " must be a public static class to be properly recreated from"
+ " instance state.");
}
//1.修改添加的 fragmentManager 为当前栈的 manager
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 (containerViewId == View.NO_ID) {
throw new IllegalArgumentException("Can't add fragment "
+ fragment + " with tag " + tag + " to container view with no id");
}
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
//2.设置宿主 ID 为布局 ID
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
//3.构造 Op
Op op = new Op();
op.cmd = opcmd; //状态
op.fragment = fragment;
//4.添加到数组列表中
addOp(op);
}
void addOp(Op op) {
mOps.add(op);
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
}
(4.2)replace
@Override
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
return replace(containerViewId, fragment, null);
}
@Override
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;
}
也是调用上面刚提到的 doAddOp(),不同之处在于第四个参数为 OP_REPLACE
通过获得当前 Activity/Fragment 的 FragmentManager/ChildFragmentManager,进而拿到事务的实现类 BackStackRecord,它将目标 Fragment 构造成 Ops(包装Fragment 和状态信息),然后提交给 FragmentManager 处理。
如果是异步提交,就通过 Handler 发送 Runnable 任务,FragmentManager 拿到任务后,先处理 Ops 状态,然后调用 moveToState() 方法根据状态调用 Fragment 对应的生命周期方法,从而达到 Fragment 的添加、布局的替换隐藏等。
(1)FragmentPagerAdapter
使用FragmentPagerAdapter 时,Fragment对象会一直存留在内存中,所以当有大量的显示页时,就不适合用FragmentPagerAdapter了,FragmentPagerAdapter 适用于只有少数的page情况,像选项卡。
原因:步长外的页面会调用destroyItem,但只有onDestroyView调用了,没有调用onDestory,也没有调用onDetach,所以fragment只是把上面的view销毁了,fragment并没有销毁,下次再创建的时候,只会调用onCreateView和onActivityCreated
(2)FragmentStatePagerAdapter
这个时候你可以考虑使用FragmentStatePagerAdapter ,当使用FragmentStatePagerAdapter 时,如果Fragment不显示,那么Fragment对象会被销毁,(滑过后会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉)
原因:会真正销毁(同时销毁view和fragment,调用onDestroyView以及其后面的所有销毁方法),重建时会从最初的onAttach开始一直到onActivityCreated。但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter 比较适合页面比较多的情况,像一个页面的ListView 。
通常情况下我们开发应用最常见的使用情况是TabLayout+ViewPager+Fragment的使用方式,下面通过一个实例展示:
步骤1:引入工具包
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:support-v4:27.1.1'
步骤2:书写布局文件
步骤3:实现TabLayout+ViewPager+Fragment
使用流程:
1、创建存储多个Fragment实例的列表
2、创建PagerAdapter实例并关联到Viewpager中
3、将ViewPager关联到Tablayout中
4、根据需求改写Tablayout属性
public class TabLayoutActivity extends AppCompatActivity implements MyFragment.OnFragmentInteractionListener {
TabLayout tabLayout;
ViewPager viewPager;
List fragments = new ArrayList<>();//步骤1:创建存储多个Fragment实例的列表
List titles = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
tabLayout = findViewById(R.id.tl_tabs);
viewPager = findViewById(R.id.vp_content);
fragments.add(MyFragment.newInstance("11111", "11111"));
fragments.add(MyFragment.newInstance("22222", "22222"));
fragments.add(MyFragment.newInstance("33333", "33333"));
fragments.add(MyFragment.newInstance("44444", "44444"));
fragments.add(MyFragment.newInstance("55555", "55555"));
titles.add("fragment1");
titles.add("fragment2");
titles.add("fragment3");
titles.add("fragment4");
titles.add("fragment5");
//步骤2:创建PagerAdapter实例并关联到Viewpager中
viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
});
//步骤3:将ViewPager关联到Tablayout中
tabLayout.setupWithViewPager(viewPager);
}
@Override
public void onFragmentInteraction(Uri uri) {
}
}
PagerAdapter是一个抽象类,它有两个实现子类供我们使用,分别是FragmentStatePagerAdapter和FragmentPagerAdapter。创建这两个类的实例需要传入一个FragmentManager对象,像代码那样处理就行了,从类名就可以看出来它俩的最大差别就在“State-状态”上,什么意思呢?指的是所包含存储的Fragment对象的状态是否保存。看源码可以发现,FragmentStatePagerAdapter中比FragmentPagerAdapter多维护着两个列表:
private ArrayList mSavedState = new ArrayList();
private ArrayList mFragments = new ArrayList();
而这两个列表带来的最大差别则体现在void destroyItem(ViewGroup container, int position, Object object)这个函数之中。(销毁Fragment)
ViewPager还有一个比较重要的函数是:
viewPager.setOffscreenPageLimit(int limit);
Google在开发ViewPager时,考虑到如果滑动的时候才创建Fragment实例时会带来一定程度的卡顿,因此为ViewPager设置了缓存机制,而上述函数则是设置缓存Fragment的数量,示意图如下:
limit的值代表着还要缓存当前Fragment左右各limit个Fragment,一共会创建2*limit+1个Fragment。超出这个limit范围的Fragment就会被销毁,而上述两种PagerAdapter的差别就是销毁的流程不同!
FragmentPagerAdapter在销毁Fragment时不会调用onDestroy()方法,而带了State的Adapter则会调用Fragment的onDestroy()方法,换言之,前者仅仅是销毁了Fragment的View视图而没有销毁Fragment这个对象,但是后者则彻彻底底地消灭了Fragment对象。
因此,对于PagerAdapter的选择:
FragmentPagerAdapter适用于Fragment比较少的情况,它会把每一个Fragment保存在内存中,不用每次切换的时候,去保存现场,切换回来在重新创建,所以用户体验比较好。而对于Fragment比较多的情况,需要切换的时候销毁以前的Fragment以释放内存,就可以使用FragmentStatePagerAdapter。
TabLayout+ViewPager+Fragment 的页面结构中ViewPager中会采用预加载机制。
Android的View绘制流程是最消耗CPU时间片的操作,尤其是在ViewPager缓存Fragment的情况下,如果在View绘建的同时还进行多个Fragment的数据加载,那用户体验简直是爆炸(不仅浪费流量,而且还造成不必要的卡顿)因此,需要对Fragment们进行懒加载策略。
就是被动加载,当Fragment页面可见时,才从网络加载数据并显示出来。
ViewPager为了优化用户体验,默认加载相邻两页,来尽可能保证滑动的流畅性。它自身提供了一个方法:mViewPager.setOffscreenPageLimit()。它的意思就是设置 ViewPager 左右两侧缓存页的数量,默认是1。
(1)setUserVisibleHint 这个方法可能会在 onAttach 之前就调用
(2)在滑动中设置缓存页数之内的页会被创建,在滑动中设置缓存页数之外的页会被销毁
setUserVisibleHint通过ViewPager中PagerAdapter方法主动调用:
位置1:instantiateItem
在我的数据集合中取出对应 positon 的 Fragment,直接给它的 setUserVisibleHint 设置为 false,然后才把它 add 进 FragmentTransaction 中
位置2:setPrimaryItem
得到当前 ViewPager 正在展示的 Fragment,并且将上一个 Fragment 的 setUserVisibleHint 置为 false,将要展示的 setUserVisibleHint 置为 true。
实行懒加载必须满足的条件
(setUserVisibleHint函数是游离在Fragment生命周期之外的,它的执行有可能早于onCreate和onCreateView,然而既然要时间数据的加载,就必须要在onCreateView创建完视图过后才能使用,不然就会返回空指针崩溃)
故在Fragment全局变量中增加对应的三个标志参数并设置初始值
boolean mIsPrepare = false; //视图还没准备好=>onCreateView
boolean mIsVisible= false; //不可见=>setUserVisibleHint
boolean mIsFirstLoad = true; //第一次加载
当然在onCreateView中确保了View已经准备好时,将mPrepare置为true,在setUserVisibleHint中确保了当前可见时,mIsVisible置为true,第一次加载完毕后则将mIsFirstLoad置为false,避免重复加载。
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mIsPrepare = true;
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见
if (isVisibleToUser) {
mIsVisible = true;
lazyLoad();
} else {
mIsVisible = false;
}
}
最后,贴上懒加载的lazyLoad()代码(只要标志位改变,就要进行lazyLoad()函数的操作)
private void lazyLoad() {
//这里进行三个条件的判断,如果有一个不满足,都将不进行加载
if (!mIsPrepare || !mIsVisible||!mIsFirstLoad) {
return;
}
loadData();
//数据加载完毕,恢复标记,防止重复加载
mIsFirstLoad = false;
}
private void loadData() {
//这里进行网络请求和数据装载
}
最后,如果Fragment销毁的话,还应该将三个标志位进行默认值初始化:
@Override
public void onDestroyView() {
super.onDestroyView();
mIsFirstLoad=true;
mIsPrepare=false;
mIsVisible = false;
}
为什么在onDestroyView中进行而不是在onDestroy中进行呢?这又要提到之前Adapter的差异,onDestroy并不一定会调用。
Fragment的加载最为耗时的步骤主要有两个,一个是Fragment创建(尤其是创建View的过程),另一个就是读取数据填充到View上的过程。懒加载能够解决后者所造成的卡顿,但是针对前者来说,并没有效果。
Google为了避免用户因翻页而造成卡顿,采用了缓存的形式,但是其实缓不缓存,只要该Fragment会显示,都会进行Fragment创建,都会耗费相应的时间,换言之,缓存只不过将本应该在翻页时的卡顿集中在启动该Activity的时候一起卡顿。
viewPager.setOffscreenPageLimit(int limit) 能够有效地一次性缓存多个Fragment,这样就能够解决在之后每次切换时不会创建实例对象,看起来也会流畅。但是这样的做法,最大的缺点就是容易造成第一次启动时非常缓慢!如果第一次启动时间满足要求的话,就使用这种简单地办法吧。
不管是FragmentStatePagerAdapter还是FragmentPagerAdapter,其中都有一个方法可以被覆写:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// super.destroyItem(container, position, object);
}
把中间的代码注释掉就行了,这样就可以避免Fragment的销毁过程,一般情况下能够这样使用,但是容易出现一个问题,我们再来看看FragmentStatePagerAdapter的源码:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
看到没?这个过程之中包含了对FragmentInstanceState的保存!这也是FragmentStatePagerAdapter的精髓之处,如果注释掉,一旦Activity被回收进入异常销毁状态,Fragment就无法恢复之前的状态,因此这种方法也是有纰漏和局限性的。FragmentPagerAdapter的源代码就留给大家自己去研究分析,也会发现一些问题的哦。
优化Viewpager和Fragment的方法就是尽可能地避免Fragment频繁创建,当然,最为耗时的都是View的创建。所以更加优秀的优化方案,就是在Fragment中缓存自身有关的View,防止onCreateView函数的频繁执行,我就直接上源码了:
public class MyFragment extends Fragment {
View rootView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(R.layout.fragment_my, container, false);
}
return rootView;
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView: " + mParam1);
mIsFirstLoad=true;
mIsPrepare=false;
mIsVisible = false;
if (rootView != null) {
((ViewGroup) rootView.getParent()).removeView(rootView);
}
}
onCreateView中将会对rootView进行null判断,如果为null,说明还没有缓存当前的View,因此会进行过缓存,反之则直接利用。当然,最为重要的是需要在onDestroyView() 方法中及时地移除rootView,因为每一个View只能拥有一个Parent,如果不移除,将会重复加载而导致程序崩溃。
其实ViewPager+Fragment的方式,ViewPager中显示的就是Fragment中所创建的View,Fragment只是一个控制器,并不会直接显示于ViewPager之中,这一点容易被忽略。
术语"内存重启":
app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
(1)在系统要把app资源回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。
(2)在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复(从onAttach生命周期开始)
内存重启本质就是Android系统(而非开发人员手动)杀死进程并重启进程的过程。会在进程结束前调用onSaveInstanceState保存Activity现场状态,同时会在进程创建后恢复。
(1)异常
内存重启/pop了Fragment后Fragment的异步任务仍在执行,且执行时调用了getActivity方法,此时会报空指针异常。
(2)原因
调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity。
(3)解决
3.1)避免在Fragment已onDetach后再去调用宿主Activity。
3.2)在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些)
protected Activity mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity)context;
}
(1)异常
Activity在调用onSaveInstanceState()保存当前Activity的状态后,直到Activity状态恢复之前,你commit 一个FragmentTransaction,就会抛出该异常——导致Activity状态丢失
(2)原因
当框架调用onSaveInstanceState()的时候,它会向这个方法传递一个Bundle参数,Activity可以用这个参数来保存页面、对话框、Fragments和视图的状态。当onSaveInstanceState返回时,会将一个Bundle对象序列化之后通过Binder接口传递给系统服务进程,并安全的保存起来。当系统晚一点想要重启Activity的时候, 它会把之前的Bundle对象传递回应用,并用来恢复Activity之前的状态。
所以为什么会抛出之前的异常呢?问题的根源在于Bundle这个对象仅仅是Activity在onSaveInstanceState()方法被调用那一刻的快照。这就意味着当你如果在onSaveInstanceState()之后再调用FragmentTransaction.commit()的话,由于这次Transaction没有被作为Activity状态的一部分来保存,自然也就丢失掉了。从用户的角度来说,Transaction的遗失就导致意外的UI状态丢失。那么为了维护良好的用户体验,Android系统会不惜一切代价的避免页面状态的丢失,所以在这种情况发生的时候就直接抛出了IllegalStateException。
(3)解决
(3.1)谨慎的在Activity的生命周期方法中调用transaction的commit方法。
大多数应用只会在第一次调用onCreate()或者响应用户输入的时候去commit transaction, 这样不会有什么问题。但是, 当您把transaction放到其他Activity的生命周期方法中时,比如onActivityResult(), onStart() 和onResume(),事情就会变得有点复杂。举个栗子,您不应该在FragmentActivity#onResume()中去commit transaction,因为在某些情况下,这个方法可能会在Activity状态恢复之前被调用(看这里))。如果您的应用需要在onCreate()之外的其他生命周期方法中去commit transaction,那就请在 FragmentActivity#onResumeFragments()或者Activity#onPostResume()中去commit。这两个方法保证会在Activity恢复状态之后才会被调用,所以就能完全避免状态丢失的可能性。(作为例子,可以查看我在StackOverflow上,关于怎样在Activity#onActivityResult()中commit FragmentTransactions 的回答)。
(3.2)避免在异步回调中去处理transaction。
包括使用AsyncTask#onPostExecute()和 LoaderManager.LoaderCallbacks#onLoadFinished()方法等。这样做的问题在于,您不会知道这些异步方法在调用的时候,当前的Activity究竟处于生命周期的哪一个状态。比如说,下面这一系列事件:
总体来说,在这种情况下,最好避免异常的方法是不要在异步回调中去commit transaction。Google的工程师们看起来也同意这个观点。根据Android开发者团队邮件组的这篇文章,Android团队认为在异步方法中去调用commit transaction而产生的UI切换,会导致不好的用户体验。 如果您的应用确实需要在异步方法中处理transaction,并且没有简单的方法可以保证回调在onSaveInstanceState()之前被调用的话,你可能只有使用commitAllowingStateLoss(),并且自己处理可能发生的状态丢失了(请参考StackOverflow,这里,还有这里)。
(3.3)只在不得已的情况下,才使用commitAllowingStateLoss()。
commit()和commitAllowingStateLoss()唯一的不同是,后者当状态丢失时,不会抛出异常。通常由于存在状态丢失的可能性,您不会希望使用这个方法。更好的解决方法是,在您的应用中使用commit(),并保证在Activity的状态保存之前调用它,来获取更好的用户体验。除非状态丢失的可能性不能被避免,否则您不应该使用commitAllowingStateLoss()。
(Support 24.0.0及以上版本已修复)
(1)异常
满足下面2个条件可能会发生Fragment重叠:1、采用add方式添加Fragment并使用hide|show切换Fragment时 2、发生内存重启 会导致Fragment重叠
(2)原因
Activity在调用onSaveInstanceState方法时,系统保存了Fragment的现场状态。但是在重启后恢复时,视图的可见状态没帮我们保存,而Fragment默认的是show状态,所以产生了Fragment重叠现象。
FragmentManager对Fragment进行管理,当发生"内存重启"时。他会从栈底向栈顶的顺序一次性恢复Fragment;但由于没有保存Fragment的mHidden属性,默认为false,即show状态,所有的Fragment都以show形式恢复。所以界面发生重叠。
(3)解决
即在add()或者replace()时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag找到对应的Fragment,并hide()需要隐藏的fragment。(回退栈已存在该Fragment)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “内存重启”时调用
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解决重叠问题
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常时
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}
如果你想恢复到用户离开时的那个Fragment的界面,你还需要在onSaveInstanceState(Bundle outState)里保存离开时的那个可见的tag或下标,在onCreate“内存重启”代码块中,取出tag/下标,进行恢复。
(1)原因
对嵌套的栈视图产生混乱
(2)解决
理清栈视图关系,做好恢复相关工作以及正确选择是使用getFragmentManager()还是getChildFragmentManager()就可以避免这些问题。
FragmentManager栈视图:
(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
(2)对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。
如果你想让某一个Fragment出栈,使用remove()在加入回退栈时并不靠谱。
(support-25.4.0版本修复)
(1)坑1:pop多个Fragment时转场动画 带来的问题(25.4.0版本修复)
(2)坑2:进入新的Fragment并立刻关闭当前Fragment 时的一些问题:可能会闪屏