背景
Fragment已经成为Android开发界面设计中不可或缺的一部分,同时也发挥着越来越重要的角色,虽然Fragment已经能出色的项目开发,但是在使用过程中也暴露了越来越多的问题,虽然google也一直在及时的修复,但是还是有很多坑,所以决定记录Fragment使用过程中的使用问题,避免小伙伴们重复踩坑。
在了解踩坑之前,我们需要先了解Fragment的使用要点和使用方法
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
newFirstFragment 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();
}
}
随时随地看视频