Fragment 复习总结

主要内容:

  • 1、Fragment 的生命周期
  • 2、Fragment 的管理
  • 3、Fragment 的空参构造方法
  • 4、Fragment PagerAdapter 与 FragmentStatePagerAdapter
  • 5、Fragment 间的数据传递
  • 6、Fragment 与 Activity 通信
  • 7、Fragment 与屏幕旋转
  • 8、Fragment 与菜单栏
  • 9、Fragment 与对话框

一、Fragment 的生命周期

Fragment 的生命周期主要有下面几种:

- onAttach - onCreate - onCreateView - onActivityCreate - onStart - onResume 
- onPause - onStop -( onSaveInstanceState )- onDestroyView - onDestroy - onDetach

和 Activity 相比,Fragment 多了许多可在流程中操作的接口,让我们对于用户界面的控制更加灵活。在安卓开发中,有许多要用到关乎 Activity 生命周期的操作,由于 Fragment 是依赖于 Activity 存在的,因此它的生命周期与 Activtiy 的表现一致。下面是常见的用户操作:

//按下 home 键后返回界面
- onPause - onStop
- onStart - onResume
//按下返回键
- onPause - onStop -( onSaveInstanceState )- onDestroyView - onDestroy - onDetach
//屏幕旋转
- onPause - onStop -( onSaveInstanceState )- onDestroyView - onDestroy - onDetach 
- onAttach - onCreate - onCreateView - onActivityCreate - onStart - onResume

如果 Fragment 被加入回退栈,那么上面的按下返回键时,只执行到 onDestroyView。

二、Fragment 的管理

在开发中,我们通常使用 FragmentManager 和 FragmentTransaction 来管理界面中的 Fragment。通常,我们用下面的代码操作 Fragment。

FragmentTest fragmentTest = FragmentTest.getInstance();
FragmentManager fm = getSupportFragmentManager();
// 获得事件
FragmentTransaction ft = fm.beginTransaction();
// 添加
ft.add(R.id.activity_containerId, fragmentTest ,"TEST");
// 删除
ft.remove(fragmentTest);
// 替换 功能相当于 添加+删除
ft.replace(R.id.activity_containerId, fragmentTest ,"TEST");
// 显示
ft.show(fragmentTest);
// 隐藏
ft.hide(fragmentTest)
// 提交
ft.commit();

其中,添加和替换操作都需要重新创建这个 Fragment 对象,而显示和隐藏操作不需要,因此,在碎片不多的情况下,我们为了保存碎片中的数据,很多时候会用这种方法对 Fragment 进行控制。Fragment 是个碎片,但是在很多情况下,它几乎充满了整个 Activity,我们常常会有这样的需求,在按下回退键的时候,可以回到上一个 Fragment,这时我们需要一个类似 Activity 的栈存储系统,在 Fragment 中的处理方式是在 FragmentTransaction 提交之前,调用 ft.addToBackStack()。

三、Fragment 的空参构造方法

谷歌规定 Fragment 必须要有一个空参公共的构造器。然而,我们通常不用这个构造器获得实例。这是为什么呢,官网中对 Fragment 的说明给了我们答案:官网文档链接

原文翻译:
每一个 Fragment 必须有一个空的构造方法,这样当 Activity 恢复状态时
Fragment 能够被实例化。强烈建议当我们继承 Fragment 类时,不要添加带有参数的构造方法,因为当Fragment被重新实例化时,这些构造方法不会被调用。如果需要给 Fragment 传递参数,可以调用 setArguments(Bundle) 方法,然后在 Fragment 中调用 getArguments() 来获取参数。

在 AS 中,创建新的 Fragment 时,会有模板给出,如下图。完成创建的代码中 TODO 提示的相关修改,就能得到一个官方推荐的碎片。


1.png

四、FragmentPagerAdapter 与 FragmentStatePagerAdapter

这两个类一般都用于和 ViewPager 结合使用。
不同点:

  • FragmentPagerAdapter 对于不再需要的 Fragment 只是调用 destroyView 销毁视图,并不销毁实例
  • FragmentStatePagerAdapter 对于不在需要的 Fragment 会调用 onDestroy 方法,销毁实例
    因此对于滑动页面少的界面可以使用 FragmentPagerAdapter ,属于牺牲内存的减少运行时间的优化方式,对于页面较多的,我们使用 FragmentStatePagerAdapter 属于牺牲时间减少内存消耗的优化方式。

下面来看看滑动过程中的 logcat 的打印:

  • FragmentPagerAdapter
    创建视图:


    创建视图.png

    销毁视图:


    销毁视图.png
  • FragmentStatePagerAdapter
    创建 Fragment:


    创建 Fragment.png

    销毁 Fragment:


    销毁 Fragment.png

五、Fragment 之间的通信

现在复杂的 Activity 大多离不开多个 Fragment 组合实现,那么同个界面中 Fragment 应该如何通信呢?就目前而言,最好的办法莫过于使用 EventBus 这样的优秀开源库,对事件总线进行管理。但是如果不希望采用第三方库,那我们只能回到 Android SDK 中寻求一般的解决方法。从网上的资料来看,解决方法绕不开观察者模式,一般通过广播或是接口回调实现。

测试代码,我只做了最简单的布局逻辑,一个 Activity 中有左右两个 Fragment,两个 Fragment 中分别有一个 Button 和一个 TextView,Button 点击后会使另一个 Fragment 的 TextView 改变。

  • 1、广播的实现:
  1. 在 Button 的点击事件中分别发送广播:
// LeftFragment Button 点击事件
public void onLeftFragmentButtonClick(View v){
        Intent intent = new Intent(ACTION_LEFT);
        intent.putString(INFO, "msg from left");// 传入数据
        sendBroadcast(intent);
    }
// RightFragment Button 点击事件
public void onRightFragmentButtonClick(View v){
        Intent intent = new Intent(ACTION_RIGHT);
        intent.putString(INFO, "msg from right");// 传入数据
        sendBroadcast(intent);
    }
  1. 在 Fragment 的 onCreate 方法中注册广播监听:
// LeftFragment
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
BroadcastReceiver br = new BroadcastReceiver() {
     @Override
     public void onReceive(Context context, Intent intent) {// 处理数据
          String info = intent.getStringExtra(INFO);
          textView.setText(info);
     }
 };
lbm.registerReceiver(br,new IntentFilter(ACTION_RIGHT));
// RightFragment
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
BroadcastReceiver br = new BroadcastReceiver() {
     @Override
     public void onReceive(Context context, Intent intent) {// 处理数据
          String info = intent.getStringExtra(INFO);
          textView.setText(info);
     }
 };
lbm.registerReceiver(br,new IntentFilter(ACTION_LEFT));

通过这种方式,我们便可以简单地利用广播实现 Fragment 之间的通信。用这种方法实现通信的时候需要注意这样几点:

  • 广播注册后需要在 onDestroy 中注销。

  • 特殊场景可能会涉及广播的其他用法。
    我们需要注意事件发送和广播注册的时机,例如在当前 Fragment 中点击后,弹出一个 FragmentDialog,如果需要该 Fragment
    与这个 FragmentDialog 通信,那么上面的方法是无法接收到广播的,因为发送时, FragmentDialog 中的广播接收器并没有注册。这个时候我们需要发送粘滞广播。

  • 广播接收器中不要处理耗时操作,因为这样做十分容易造成 ANR。当然如果一定要这样操作,也是有办法的,这点我会在下一篇 Broadcast 总结里提到。

  • 2、接口回调
    如果我们有了一段不短的学习经历,或者有了一些开发经验,那么我们就会很自然地发现,我们在开发中几乎离不开接口回调的应用。事实上,Google 在 AS 创建的模板里也有不少接口回调的影子,比如 Fragment。
    在创建 BlankFragment 的时候如果勾选了 include the interface callbacks,我们就可以看到在 onAttach 方法中,就自动把 context 对象绑定到监听器上,如果这个 Activity 没有实现这个接口会出运行时异常。我们也参考这个方式进行操作,并且将 View 的处理都放在宿主 Activity 里。
    参考代码:
    LeftFragment:

public class LeftFragment extends Fragment {

    private OnLeftFragmentListener mListener;
    private TextView mTextView;

    public LeftFragment() {
        // Required empty public constructor
    }

    public static LeftFragment newInstance() {
        LeftFragment fragment = new LeftFragment();
        // 传入数据
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            // 初始化数据
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        mTextView = ((TextView) view.findViewById(R.id.fragment_left_text));
        view.findViewById(R.id.fragment_left_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) {
                    mListener.onLeftClick("msg from left");
                }
            }
        });
        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnLeftFragmentListener) {
            mListener = (OnLeftFragmentListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnLeftFragmentListener {
        void onLeftClick(String info);
    }

    public void receiveInfo(String info){
        mTextView.setText(info);
    }
}

由于左右碎片的代码是一样的,因此不再贴了,直接是宿主 Activity:

public class Fun3Activity extends BaseActivity 
                            implements LeftFragment.OnLeftFragmentListener,
                            RightFragment.OnRightFragmentListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void initViews() {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.activity_fun3_left_container,LeftFragment.newInstance(),"LEFT");
        ft.add(R.id.activity_fun3_right_container,RightFragment.newInstance(),"RIGHT");
        ft.commit();
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_fun3;
    }

    @Override
    public void onRightClick(String info) {
        FragmentManager fm = getSupportFragmentManager();
        LeftFragment leftFragment = (LeftFragment) fm.findFragmentByTag("LEFT");
        leftFragment.receiveInfo(info);
    }

    @Override
    public void onLeftClick(String info) {
        FragmentManager fm = getSupportFragmentManager();
        RightFragment rightFragment = (RightFragment) fm.findFragmentByTag("RIGHT");
        rightFragment.receiveInfo(info);
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容