背景
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
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(); } }