本文是对Android Fragment 使用解析的学习笔记
一、作用
1. 作用
模块化
不必把所有的代码放在 Activity 中,而是放在各自的 Fragment 中。可重用
一个 Activity 可以有多个 Fragment,一个 Fragment 可以被多个 Activity 使用可适配
不同尺寸的屏幕适配
2. 特点
- Fragment 依赖于 Activity,不可独立存在
- 一个 Fragment 可被多个 Activity 重用,一个 Activity 中可以有多个 Fragment
- Fragment 有自己的生命周期,并接受事件输入
- Activity 中可动态地添加、删除 Fragment
二、概念
1. Fragment 生命周期
1)如果希望保留用户数据:
hide()/show()
2)如果不希望保留用户数据:可以先remove()
再add()
,或者直接replace()
3)如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach:remove()
会销毁整个 Fragment 实例,detach()
只是销毁视图结构,实例并不会销毁。
2. 核心类
(1)Fragment
(2)FragmentManager
(3)FragmentTransaction
3. Fragment 回退栈
三、使用
1. 创建 Fragment
public class MyFragment extends Fragment {
private static final String ARG_PARAM = "key1";
public static Fragment newInstance(String str) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle);
return fragment;
}
private Activity mActivity;
private String mParam;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (Activity) context;
mParam = getArguments().getString(ARG_PARAM);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_1, container, false);
TextView view = root.findViewById(R.id.text);
view.setText(mParam);
return root;
}
}
(1)一般提供一个newInstance()
方法用于创建 Fragment 实例
- 通过
setArguments()
传递给 Fragment 的参数
该方法中通过
setArguments()
设置 Activity 要传递给 Fragment 的参数。相比于构造函数中传入数据地方法,setArguments()
中的数据,在 Fragment 被异常终止后再次恢复时,这些数据仍会保留。
setArguments()
方法必须在 fragment 创建以后,添加给 Activity 前完成。千万不要,首先调用了 add,然后设置 arguments。
(2)onAttach()
通过
getArguments()
获取 Activity 传递过来的数据将
Context context
强转为 Activity,获取绑定的 Activity 对象
mActivity = (Activity) context;
。不建议通过getActivity()
,而是将context
进行强转
(3)onCreateView()
inflater.inflate(R.layout.fragment_1, container, false);
加载布局时中inflate()
方法的第三个参数设置为false
,如果设置为true
会造成 Fragment 的布局被重复添加到container
中,产生异常。
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
2. 将 Fragment 添加到 Activity 中
(1)静态添加
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
class="com.example.myapplication.mvp.fragment.MyFragment"/>
</LinearLayout>
(2)动态添加
首先,需要在 Activity 的布局文件中有一个容器放置 Fragment,一般是 FrameLayout。
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
然后在 Activity 的代码中将 Fragment 添加到 Activity 中。
public class MyActivity {
private void addFragment() {
MyFragment myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MyFragment.TAG);
if (myFragment == null) {
myFragment = new MyFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, myFragment, MyFragment.TAG);
}
}
}
getSupportManager()
因为用的是support.v4
包中的 Fragment,因此也要用对应的getSupportManager()
获取 Fragment 管理器,该管理器可管理该 Activity 中嵌入的一级 Fragment。
- 在创建 Fragment 实例之前先判空,防止 Fragment 重叠
if (myFragment == null)
当 Activity 因为配置发生变化或内存不足被系统回收,造成重新创建时,我们 Fragment 会被保存下来,但是会创建新的 FragmentManager,新的 FragmentManager 会首先会去获取保存下来的 fragment 队列,重建 fragment 队列,从而恢复之前的状态。这样就造成了 Fragment 重叠(重复添加)。
- 添加 Fragment 时为其设置TAG
getSupportFragmentManager().beginTransaction().add(R.id.container, myFragment, MyFragment.TAG);
设置 TAG 是为了方便在 Activity 中查找 Fragment。findFragmentByTag()
MyFragment myFragment= (MyFragment) getSupportFragmentManager().findFragmentByTag(MyFragment.TAG);
建议使用动态添加,灵活性高。
3. Activity 中获取 Fragment 实例
- 若 Activity 中包含自己管理的 Fragment 的引用
那么在 Activity 中可以通过引用直接访问 Fragment 中所有的 public 方法
- 若 Activity 中不包含...
每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()
或者findFragmentById()
获得任何Fragment实例,然后进行操作。
4. Fragment 中获取 Activity 实例
通过
getActivity()
获取当前绑定的 Activity 的实例,然后进行操作。通过在
onAttach(Context context)
中将Context
强转获得 Activity 实例
public class MyFragment extends Fragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (Activity) context;
}
}
四、注意
1. Fragment 和 Activity 之间进行通信
(1)Activity 向 Fragment 传递数据
- Activity 创建 Fragment 时传递数据
建议通过为 Fragment
setArguments(Bundle bundler)
的方式,不建议通过带参数的Fragment 构造函数传递。
因为:通过前一方式创建的 Fragment,在内存紧张时 Fragment 被杀死又恢复时,这些数据能够保留。
private static final String ARG_PARAM = "key1";
public static Fragment newInstance(String str) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle);
return fragment;
}
- 其他情况传递数据
获取 Fragment 对象,调用 Fragment 对象的方法,将数据传递过去。
Fragment 的实现如下,并提供方法setName(String name)
。
public class MyFragment extends Fragment {
private String name;
public void setName(String name) {
this.name = name;
}
}
在 Activity 中调用fragment.setName("xx");
。
(2)Fragment 向 Activity 传递数据
Android Fragment 真正的完全解析(下),该文中提供了两种最佳实践,选一个即可。在 Fragment 中声明了一个接口,来回调其点击事件。
因为考虑到 Fragment 的重复使用,所以必须降低 Fragment 和 Activity 的耦合。而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
最佳实践达到的效果是:Fragment 不和任何 Activity 耦合,任何 Activity 都可以使用
Fragment 中创建一个接口,Activity 实现该接口。
public class MyFragment extends Fragment {
public interface OnMuyClickListener {
void onMyClick();
}
private OnMuyClickListener mOnMuyClickListener;
public void setOnMuyClickListener(OnMuyClickListener listener) {
mOnMuyClickListener = listener;
}
public void test() {
if (mOnMuyClickListener != null) {
mOnMuyClickListener.onMyClick();
}
}
}
Activity 中添加 Fragment 时为其设置监听事件
private void addFragment() {
MyFragment myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MyFragment.TAG);
if (myFragment == null) {
myFragment = new MyFragment();
myFragment.setOnMuyClickListener(new MyFragment.OnMuyClickListener() {
@Override
public void onMyClick() {
//...
}
});
getSupportFragmentManager().beginTransaction().add(R.id.container, myFragment, MyFragment.TAG);
}
}
Fragment 在合适的地方调用接口中的方法mOnFragmentInteractionListener.onItemClick("hello");
将数据传递过去。
(3)Fragment 向 Fragment 传递数据
Fragment 之间没有任何依赖关系,如果二者需要通信的话建议通过 Activity 作为中介,不要直接通信。