**涵盖知识点
**
- show() hide() 和 attach()detach()和 attach()detach()方法的比较
- fragment回退栈
如果上述都会了,可以立马关掉这个网页,向你说一声:对不起,打扰了!如果不会,可以接着往下看。
一.前言
诚惶诚恐,怀着羞愧的心态写下这篇文章,羞愧的原因在于本应在三年前会的知识,现在才注意到,我可能是个假程序员!于是有了这篇比较偏向基础知识的文章,以此文来践踏我的自尊心,进而促使我更大的进步。
二.Fragment
Fragment中文意思是碎片,我理解的碎片:替代activity的轻量级组件。
三.Fragment生命周期方法
Fragment生命周期有11个方法之多,这里就对几个具有标志性的方法进行解释:
在这里我把fragment比做一个装控件的箱子
-
onAttoch方法被调用时候:表示Activity对Fragment进行了挂载,挂载就是:activity中fragmentManage持有这fragment对象,并对fragment进行管理。类似创建一个箱子并管理他
-
onDetach方法被调用的时候:表示Activity对Fragment取消了挂载,取消挂载就是:fragmentManage放弃持有这fragment对象,并不对fragment进行管理。类似抛弃这个箱子
-
onCreatView方法被调用的时候,表示为Fragment添加布局视图,添加布局视图:布局设置给Fragment,同时fragment的视图添加到显示的Activity的主视图树中。类似向箱子里边放控件,箱子里的控件挂载到activity的主视图中,至于箱子是否是新创建的,取决于是否走了onAttoch方法
-
onDetoryView方法被调用的时候,表示把Fragment中的视图(布局)移除并取消视图(布局)和activity主视图树(布局)的关联。类似把箱子里边的控件都扔了,但是箱子还留着,至于箱子最后仍不扔,取决于最后是否走了onDetach方法
这样类比可能还是比较抽象,我们通过代码log去测试上边的方法
在activity中去操作fragment有两种方式:静态加载和动态加载,我们这里为了比较这些方法对fragment的生命周期的影响,我们这里只使用动态加载。
activity中:
public class MainActivity extends AppCompatActivity {
private TestFragment testFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testFragment = new TestFragment();
//按钮一点击事件
findViewById(R.id.btn1).setOnClickListener(v -> {});
//按钮二点击事件
findViewById(R.id.btn2).setOnClickListener(v -> {});
}
}
fragment中写下几个重要的生命周期的方法,并打印对应方法名字:
public class TestFragment extends Fragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.e("rrrrrrrr","onAttach");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("rrrrrrrr","onCreate");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e("rrrrrrrr","onCreateView");
return inflater.inflate(R.layout.fragment_test,container,false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e("rrrrrrrr","onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("rrrrrrrr","onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.e("rrrrrrrr","onDetach");
}
}
测试一:当在按钮一和按钮二中写下如下代码:
testFragment = new TestFragment();
//按钮一点击事件
findViewById(R.id.btn1).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().add(R.id.rongqi, testFragment).commit();
});
//按钮二点击事件
findViewById(R.id.btn2).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().remove(testFragment).commit();
});
分别点击按钮打印日志如下:
点击添加按钮:
rrrrrrrr: onAttach
rrrrrrrr: onCreate
rrrrrrrr: onCreateView
点击移除按钮:
rrrrrrrr: onDestroyView
rrrrrrrr: onDestroy
rrrrrrrr: onDetach
这里就好比每次都去创建一个新的箱子,并且去放入新的控件。
测试二:当在按钮一和按钮二中写下如下代码:
testFragment = new TestFragment();
getSupportFragmentManager().beginTransaction().add(R.id.rongqi, testFragment).commit();
//按钮一点击事件
findViewById(R.id.btn1).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().detach(testFragment).commit();
});
//按钮二点击事件
findViewById(R.id.btn2).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().attach(testFragment).commit();
});
点击按钮分别打印日志:
rrrrrrrr: onDestroyView
rrrrrrrr: onCreateView
这就好比每次箱子里边的控件都是新创建的,但是箱子还是老箱子。
当在按钮一和按钮二中写下如下代码:
testFragment = new TestFragment();
getSupportFragmentManager().beginTransaction().add(R.id.rongqi, testFragment).commit();
//按钮一点击事件
findViewById(R.id.btn1).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().hide(testFragment).commit();
});
//按钮二点击事件
findViewById(R.id.btn2).setOnClickListener(v -> {
getSupportFragmentManager().beginTransaction().show(testFragment).commit();
});
点击按钮没有日志输出
这就好比,箱子和箱子里边的控件都是老的,只是把它隐藏起来了而已
但对于界面对用户展示而言都是看不见了,又看见了
比较三对不同的操作,对fragment生命周期调用完全不一样:
-
当show和hide的时候,没有调用任何生命周期方法
-
当add()和remove() 的时候,走了从onAttach到onDetach的整个生命周期方法
-
当attach()和detach()的时候,走了从onCreateView到 onDestroyView的一段生命周期方法
而对于程序而言,持有相同的对象和不同的对象是完全不同的概念,所以我们要根据不同的需求去选择调用不同的方法去显示和隐藏fragment。
四. 对于Fragment嵌套在Viewpager中的需求
默认情况下,超过预加载的fragment就会走到onDestroyView,每次回来,里边的控件都会创建新的,但是fragment不会创建新的。
1.假如需求是要求不去创建新的控件,我们可以通过设置viewpagr的预加载数量,完美解决这个不去创建新的控件需求
2.假如需求是不要求创建的新控件且每次呈现再用户面前都去刷新,第一,我们去设置预加载数量,要求使用老的控件,第二系统给我们提供结合Viewpager使用的新方法,当在用户面前显示的时候都会调用,setUserVisibleHint方法,显示时候返回true,通过这个方法,可以让页面每次呈现在用户面前都去刷新,再加载一个是否已经请求的标记,可以实现懒加载效果。值得注意的是,该方法不是生命周期方法,且只有再结合viewpager时候才会被调用,查看源码可以看出该方法是viewpger手动调用的。
五.fragment回退栈
在android系统中,A页面跳转到B页面,然后我们按返回键,就又回到页面A,我们知道这个效果是因为系统给我们添加了栈数据结构去管理所有开启的页面。
那么在一个Activity中如果我们依次添加了Fragment1,Fragment2,Fragment3,然后什么也不处理的情况下,我们点击返回键,直接就关闭了Activity,那么我们能不能像activity一样,点击返回键,依次推出fragment,最后再关闭页面呢?当然是可以的!这就是fragment的回退栈。
-
addToBackStack(tag); 将Fragment添加到回退栈中
-
popBackStack(); 清除回退栈中栈顶的Fragment
-
popBackStack(String tag, int i );
- 如果i=0,回退到该tag所对应的Fragment层
- 如果i=FragmentManager.POP_BACK_STACK_INCLUSIVE,回退到该tag所对应的Fragment的上一层
-
popBackStackImmediate 立即清除回退栈中栈顶Fragment
-
getBackStackEntryCount(); 获取回退栈中Fragment的个数
-
getBackStackEntryAt(int index) 获取回退栈中该索引值下的Fragment
通过以上方法,可以实现Fragment的回退栈效果。下边用代码去讲解回退栈效果:
private OneFragment one;
private TwoFragment two;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
one = new OneFragment();
two = new TwoFragment();
findViewById(R.id.btn2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().add(R.id.fragment, one, "oneFragment").addToBackStack("oneFragment").commit();
}
});
findViewById(R.id.btn3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment, two, "TwoFragment").addToBackStack("TwoFragment").commit();
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 判断当前按键是返回键
if (keyCode == KeyEvent.KEYCODE_BACK) {
// 获取当前回退栈中的Fragment个数
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
// 回退栈中至少有多个fragment,栈底部是首页
if (backStackEntryCount > 1) {
getSupportFragmentManager().popBackStackImmediate();
} else {
finish();
}
}
return true;
}
如上代码我们点击返回键,实现了点击返回,退出到第一个Fragment中,实现了需求。细心的朋友可以发现我这里用了replace方法去处理了第二个fragment,我们都知道replace方法是remove方法和add方法的合体,按照之前测试一的log,我们猜测,oneFragment应该走完所有的生命周期方法,并且解除被Activity的管理,事实真的是这样吗?我们去测试一下:
观察oneFragment的log如下:
点击按钮一:
rrrrrrrrOne: onAttach
rrrrrrrrOne: onCreate
rrrrrrrrOne: onCreateView
点击按钮二:
rrrrrrrrOne: onDestroyView
惊讶射的满脸都是,在仔细一想,合理,因为我添加了回退栈,如果真的移除了,还怎么管理!
合理,合理,非常合理!
可惜的是,上边代码没有切换的动画效果!没有动画效果?不可能!接下来介绍一个方法setCustomAnimations
这个方法有两个参数和4个参数的重载,这里因为有回退栈,就说一下4个参数的:第一个参数和第二个参数是设置Fragment进入和退出的动画效果,第三个和第四个是设置Fragment回退栈的动画效果,代码如下:
在res下创建anim文件夹,然后创建四个动画:
enter:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="100.0%p"
android:toXDelta="0.0" />
</set>
out:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="0.0"
android:toXDelta="-100.0%p" />
</set>
pop_Enter:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="-100.0%p"
android:toXDelta="0.0" />
</set>
pop_out:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="0.0"
android:toXDelta="100.0%p" />
</set>
通过manager,就可以设置动画,代码如下:
findViewById(R.id.btn2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.enter, R.anim.out, R.anim.proenter, R.anim.proout).add(R.id.fragment, one, "oneFragment").addToBackStack("oneFragment").commit();
}
});
findViewById(R.id.btn3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.enter, R.anim.out, R.anim.proenter, R.anim.proout).replace(R.id.fragment, two, "TwoFragment").addToBackStack("TwoFragment").commit();
}
});
基本算是完美了,你有没有发现一个问题:
当回退栈的时候每次都走Fragment的onCreatView方法,那假如需求要求回退栈,不去走任何方法,而且要回退栈,我们可以修改代码如下,把
replace方法换成add方法,为了防止重叠,我们隐藏第一个fragment:
findViewById(R.id.btn2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.enter, R.anim.out, R.anim.proenter, R.anim.proout).add(R.id.fragment, one, "oneFragment").addToBackStack("oneFragment").commit();
}
});
findViewById(R.id.btn3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.enter, R.anim.out, R.anim.proenter, R.anim.proout).hide(one).add(R.id.fragment, two, "TwoFragment").addToBackStack("TwoFragment").commit();
}
});
这样就满足了需求,回退栈,并且不去从新创建布局。
如果结合Viewpager,也可以实现回退栈,通过设置缓存数量,而不让布局去重新创建,并且也会再回退栈的时候调用setUserVisibleHint方法。
那么我不结合Viewpager,如果fragment回退栈了,那么fragment会不会有什么方法被回调呢?有还是没有?
没有!
原因:Fragment只是Activity中的一个控件而已,系统怎么会给一个控件分发回退事件呢?这当然是不可能的。
那我们怎么去监听呢?
当然还要从Activity中入手:
//每当回退栈数量变化,不管是增加还是减少,都会回掉这个方法,我们可以根据数量的变化,来监听到底是哪个Fragment从回退栈中出来
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
}
});
然后再调用对应的fragment中的方法,来通知fragment被退出栈
亦或是我们用:
//该方法在fragment第一次创建的时候并不去调用
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
}
到此,关于fragment基本上算是回顾完毕,github上还有关于fragment的库,如
Fragmentation,FragmentManager,同时Google在2018 IO大会上提供的组建**Navigation**
,有时间再探讨两个第三方和一个原生组件对fragment栈的支持!