背景
Fragment
已经成为Android
开发界面设计中不可或缺的一部分,同时也发挥着越来越重要的角色,虽然Fragment
已经能出色的项目开发,但是在使用过程中也暴露了越来越多的问题,虽然google
也一直在及时的修复,但是还是有很多坑,所以决定记录Fragment
使用过程中的使用问题,避免小伙伴们重复踩坑。
在了解踩坑之前,我们需要先了解Fragment
的使用要点和使用方法
PS : 有兴趣的加入Android工程师交流QQ群:752016839 主要针对Android开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。
Fragment
介绍
作为 view
界面的一部分,Fragment
的存在必须依附于 FragmentActivit
使用,并且与 FragmentActivit
一样,拥有自己的独立的生命周期,同时处理用户的交互动作。同一个 FragmentActivit
可以有一个或多个 Fragment
作为界面内容,同样Fragment
也可以拥有多个子Fragment
,并且可以动态添加、删除 Fragment
,让UI
的重复利用率和易修改性得以提升,同样可以用来解决部分屏幕适配问题。
另一方面,support v4 包中也提供了 Fragment,兼容 Android 3.0 之前的系统,使用兼容包需要注意两点:
-
宿主
Activity
必须继承自FragmentActivity
; -
使用
getSupportFragmentManager()
方法获取FragmentManager
对象;
生命周期
Fragment
同样是具备了独立的生命周期,但是和Activity
的生命周期还有不一样的地方,如图:原图地址
Fragment
初始化
Fragment
默认有两种初始化的方法,一种new
另一种是嵌入xml
-
new
FirstFragment firstFragment=new FirstFragment();复制代码
-
xml
<fragment android:layout_width="match_parent" android:layout_height="match_parent" class="com.wzgiceman.fragmentpit.Fragment.FirstFragment"/>复制代码
上面两种方法都可以初始得到一个Fragment
对象,但是前者比后者的有点在于,前者更加的灵活,所以推荐使用第一种方式。
Activity
和Fragment
传参
默认创建Fragment
系统已经给我们初始了传参的代码
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment FirstFragment.
*/
// TODO: Rename and change types and number of parameters
public static FirstFragment newInstance(String param1, String param2) {
FirstFragment fragment = new FirstFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}复制代码
这无疑是最好的选择
回调
Fragment
类提供有startActivityForResult()
方法用于 Activity
间的页面跳转和数据回传,其实内部也是调用 Activity
的对应方法。但是在页面返回时需要注意 Fragment 没有提供 setResult()
方法,可以通过宿主 Activity
实现。
FragmentManager
和FragmentTransaction
使用
FragmentManager
在Activity
中使用Fragment
可以使用getSupportFragmentManager
获取一个FragmentManager
对象,但是在Fragment
中显示子Fragment
需要调用Fragment
的getChildFragmentManager()
源码如下:
public final FragmentManager getChildFragmentManager() {
throw new RuntimeException("Stub!");
}复制代码
FragmentTransaction
Fragment
的动态添加、删除等操作都需要借助于 FragmentTransaction
类来完成,比如上面提到的 commit()
操作,下面是几种常用的方法:
-
add() 系列:添加 Fragment 到 Activity 界面中;
-
remove():移除 Activity 中的指定 Fragment;
-
replace() 系列:通过内部调用 remove() 和 add() 完成 Fragment 的修改;
-
hide() 和 show():隐藏和显示 Activity 中的 Fragment;
-
addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;
-
commit():提交事务,所有通过上述方法对 Fragment 的改动都必须通过调用 commit() 方法完成提交
replace()
和hide()
区别
replace()
和hide()
都可以动态的在Activity
中显示多个Fragment
,并且可以来回灵活的切换,但是它们有很大的区别,replace()
方法不会保留 Fragment
的状态,也就是说诸如 EditText
内容输入等用户操作在 remove()
时会消失;但是hide()
却不会,能完整的保留用户的处理信息。
addToBackStack()
退栈
当用户按下返回键时,如果回退栈中保存有之前的事务,会先执行事务回退,然后再执行Activity
的finish()
方法 。
简单使用
通过FragmentManager
和FragmentTransaction
结合使用,我们可以将第一种初始化的Fragment
动态的显示到界面中,这里使用replace()
演示:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fl_fragment, FirstFragment.newInstance("A","B"));
ft.commit();复制代码
踩坑
在了解了Fragment
的基础使用后,可以开始使用过程中的踩坑了
getActivity()
引用问题
在Fragment
中常常需要使用到content
对象,比如网络加载现在一个progress
等等,这时候可能你遇到过getActivity()
返回null
,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null
,报了空指针异常。
大多数情况下的原因:你在调用了getActivity()
时,当前的Fragment已经onDetach()
了宿主Activity
。
比如:你在pop
了Fragment
之后,该Fragment
的异步任务仍然在执行,并且在执行完成后调用了getActivity()
方法,这样就会空指针;
解决办法
-
用
getContext()
替代getActivity()
-
定义全局变量,在
Fragment
的onAttach(Activity activity)准备废弃
或者onAttach(Context context)
方法中初始化Context context; @Override public void onAttach(Context context) { super.onAttach(context); this.context=context; }复制代码
显然第一种方法更加灵活方便了。
高耦合
当子Fragment
需要调用宿主Acitivity
的方法时,比如子Fragment
需要发送一个广播,但是Fragment
没有改方法,所以需要借助宿主Activity
去发送,这时候常常需要强制转换content
对象,然后调用宿主Acitivity
发方发送广播,这种直接使用的方式违背了高聚低耦的设计原则;
解决办法
通过接口抽象的方法,通过接口去调用宿主Activity的方法。
- 定义接口
/**
* 发送广播
* Created by WZG on 2016/12/31.
*/
public interface SendBListener {
void send();
}复制代码
- 实现接口
public class FirstFragment extends Fragment {
SendBListener listener;
public void setListener(SendBListener listener) {
this.listener = listener;
}
@OnClick(value = R.id.tv)
void onTvClick(View view) {
listener.send();
}
}复制代码
- 调用
public class MainActivity extends AppCompatActivity implements SendBListener{
@BindView(R.id.fl_fragment)
FrameLayout mFlFragment;
@Override
public void send() {
sendBroadcast(new Intent("xxxxxx"));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirstFragment firstFragment=new FirstFragment();
firstFragment.setListener(this);
}
}复制代码
重叠
由于采用创建对象的方式去初始化Fragment
对象,当宿主Activity
在界面销毁或者界面重新执行onCreate()
方法时,就有可能再一次的执行Fragment
的创建初始,而之前已经存在的 Fragment 实例也会销毁再次创建,这不就与 Activity 中 onCreate() 方法里面第二次创建的 Fragment 同时显示从而发生 UI 重叠的问题。
如果宿主界面Acitivity
可以横竖屏切换,导致的生命周期重新刷新也同理可导致界面的重叠问题。
解决办法
- 推荐:利用
savedInstanceState
判断
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
FirstFragment firstFragment;
if (savedInstanceState==null) {
firstFragment=new FirstFragment();
ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
}else {
firstFragment = (FirstFragment) fm.findFragmentByTag("FirstFragment");
}
}复制代码
- 在
Activity
提供的onAttachFragment()
方法中处理
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof FirstFragment){
firstFragment = (FirstFragment) fragment;
}
}复制代码
- 创建
Fragment
时判断
Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
if (fragment==null) {
firstFragment =new FirstFragment();
ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
}else {
firstFragment = (FirstFragment) fragment;
}复制代码
Fragment
转场动画
如果你想给下一个Fragment设置进栈动画和出栈动画,setCustomAnimations(enter, exit)
只能设置进栈动画,第二个参数并不是设置出栈动画;
请使用setCustomAnimations(enter, exit, popEnter, popExit)
,这个方法的第1个参数对应进栈动画,第4个参数对应出栈动画,所以是setCustomAnimations(进, exit, popEnter, 出))
Fragment
状态监听
很多时候,我们需要在多Fragment
中刷新界面,当然由于Fragment
有自己独立的生命周期但是也依赖宿主Activity
存在,所以在刷新界面的时候需要注意如:
当宿主Activity
A
进入B
中,又冲B
返回到A
,这时候宿主A
执行onResume()
方法,当然这时候的Fragment
也会执行onResume()
当宿主Activity
A
中的Fragment
全部初始完成显示过,在切换Fragment
的时候不会再一次触发onResume()
方法,但是却可以触发Fragment的onHiddenChanged(boolean hidden)
方法
所以当我们需要实时刷新Fragment
界面的时候,需要同时结合onResume()
和onHiddenChanged(boolean hidden)
方法去刷新当前显示Fragment
而避免刷新hide()
的Fragment
使用
Override
public void onResume() {
super.onResume();
//当前是否是现实状态
if (isVisible()){
//刷新界面
updateUI();
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
//方法重复发起刷新界面
if (isVisible() && isResumed()){
updateUI();
}
}复制代码