Fragment全面使用及懒加载初试--指北

随着Fragment的使用越来越简便好用,项目中使用Fragment越来越频繁。这不仅可以减少频繁创建销毁Activity时的开销,也可以通过统一管理Fragment让我们的代码更加清晰,更方便别人接手二次开发。

1.Fragment简介

这里简单根据官方文档说下什么是Fragment?

A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity. 
Interaction with fragments is done through FragmentManager, 
which can be obtained via Activity.getFragmentManager() and Fragment.getFragmentManager().

api文档已经说的很清楚了,Fragment是依附在Activity中的一个表现用户界面或者行为的片段,你可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且可以在 Activity 运行时添加或移除片段(有点像可以在不同 Activity 中重复使用的“子 Activity”)。当然,我们可以通过getFragmentManager()获取FragmentManager来管理Fragment。

2.Fragment使用

在学习使用前,我们先罗列下Fragment的生命周期:


fragment

这里举个最简单的使用Fragment和验证生命周期的栗子:
activity_main.xml布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.blink.dagger.MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/frag_container"
        />

</RelativeLayout>

MainActiviy:

getSupportFragmentManager().beginTransaction().add(R.id.frag_container,BlankFragment.getInstance()).commit();

fragment_blank.xml布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.blink.dagger.BlankFragment">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="I am a fragment~"
        android:textSize="18sp"
        android:layout_centerInParent="true"
        />

</RelativeLayout>

当我们启动MainActiviy时,在onCreate时就把Fragment中add进来了(实际使用中当然是根据场景add、hide、show、remove),这个Fragment的生命周期如下,可以结合上图一起理解:

01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onAttach
01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onCreate
01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onCreateView
01-11 00:32:42.927 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onViewCreated
01-11 00:32:47.823 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDestroyView
01-11 00:32:47.832 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDestroy
01-11 00:32:47.832 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDetach

所以一些关于init的操作我们最好放进onCreateView、onViewCreated中操作。

当然,还有一种不常用的方法。我们可以直接把Fragment通过<fragment/>标签添加至Activity的布局文件中:

    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

接着通过getFragmentManager().findFragmentById或者getFragmentManager().findFragmentByTag来实例化该Fragment。不过个人觉得通过这种方式添加Fragment会提高代码的维护成本,说不定还会给自己挖坑!

3. Fragment 通信

在使用Fragment的时候,肯定需要交互传递不同的数据信息,我们可以把这部分的交互分为两种类型: 1.Fragment && Activity , 2. Fragment && Fragment 。

  • Fragment && Activity

这里首先说个小技巧,当我们需要通过Activity向目标Fragment传递一些简单数据时,完全可以通过Bundle和Arguments将我们的Activity与Fragment解耦(如果通过Intent传递那就将宿主Activity、Fragment绑定到一起了,看实际情况选择吧)。不知道的同学可以通过代码理解,如下:

    public static BlankFragment getInstance(String argument) {
        Bundle bundle = new Bundle();
        bundle.putString("extra", argument);
        if (blankFragment == null) {
            blankFragment = new BlankFragment();
            blankFragment.setArguments(bundle);
        }
        return blankFragment;
    }

在Activity中通过上述getInstance()实例化Fragment时,就可以传入string。在Fragment的onCreateView时,可以将该string取出:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        Bundle bundle = getArguments();
        if (bundle != null) {
            String extra = bundle.getString("extra");
        }
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }

另外一种常见的Fragment && Activity交互场景就是当我们在Fragment中处理事件后,要通知Activity也要做出相应表现。我们先做一个简单的假设场景,当我们点击Fragment中的view时,要在宿主Activity中将该操作展示给用户哪个view被点击了,我们可以这样实现(通过接口回调的方法):

首先在Fragment中定义一个接口,用于在Activity中回调:

    // Container Activity must implement this interface
    public interface OnFragmentSelectedListener {
        public void showText(String content);
    }

在我们点击不同view的时候触发showText()方法 :

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.mText:
                mListener.showText("TextView is clicked !");
                break;
            case R.id.mButton:
                mListener.showText("Button is clicked !");
                break;
        }
    }

不要忘了将实现该接口的Activity转化为我们的"listener":

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            mListener = (OnFragmentSelectedListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException("activity must implement OnFragmentSelectedListener");
        }
        Log.d(TAG, "onAttach");
    }

最后在Activity中实现我们的接口就大功告成了~:

    @Override
    public void showText(String content) {
        mTextView.setText(content);
    }

运行效果如下(屏幕最上方的TextView属于Activity,而不是写在Fragment的布局中):


运行效果
  • Fragment && Fragment :
    Fragment和Fragment间数据交互,应该也是会经常用到的。第一时间,我们可能本能地会想到使用宿主Activity做传递媒介。原理其实也是通过使用onActivityResult回调,完成Fragment && Fragment 的数据交互,这其中有两个比较重要的方法:Fragment.setTargetFragment、getTargetFragment()。代码也是比较简单的~这里只给出一小部分了:

通过setTargetFragment来连接需要交互的Fragment

requstFragment.setTargetFragment(blankFragment.this, REQUEST_CODE);

接着实现onActivityResult处理传递过来的数据

    //实现回调处理数据(一般数据都封装进intent中了)
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE)
        {
            String evaluate = data
                    .getStringExtra("");
            //dosomething();
        }
    }

在另一个需要发送数据的requsetFragment中回调即可

    // 判断是否设置了targetFragment  
    if (getTargetFragment() == null)
            return;

    Intent intent = new Intent();
    intent.putExtra("key", "value"]);
    getTargetFragment().onActivityResult(BlankFragment.REQUEST_CODE,
                                         Activity.RESULT_OK, intent);
4.Fragment懒加载

Fragment懒加载的使用场景便是在一个ViewPager管理多个Fragment时候,由于ViewPager"出色"的缓存机制会在显示一个Fragment的同时,预先加载好左右相邻两个Fragment的部分资源(会出发相邻Fragment的onCreate生命周期)。所谓的懒加载就是让我们加载数据的操作仅在Fragment可见的时候执行,可以节省不必要的开销。

看"懒加载"这个名称挺唬人的,其实就是通过系统提供的setUserVisibleHint(boolean isVisibleToUser)方法,在这其中进行数据的加载。

setUserVisibleHint:

Set a hint to the system about whether this fragment's UI is currently visible to the user. 
An app may set this to false to indicate that the fragment's UI is scrolled out of visibility 
or is otherwise not directly visible to the user. 
This may be used by the system to prioritize operations such as fragment lifecycle updates 
or loader ordering behavior.

唯一需要值得注意的是,setUserVisibleHint是在onCreateView周期前调用,此时布局中各View还未初始化,所以只能在setUserVisibleHint中进行纯数据的加载。那么牵涉到ui的操作(比如为某个view设置数据)该放在那里呢?栗:

/**
 * created by blink
 */
public abstract class LazyFragment extends Fragment {

    private boolean isVisible;
    private boolean isViewCreated;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutID(), container, false);
        Log.e("blink","onCreateView");
        isViewCreated = true;
        return view;
    }


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(getUserVisibleHint()) {
            isVisible = true;
            loadForData();
        } else {
            isVisible = false;
            onInvisible();
        }
        if (isViewCreated && isVisible)
            loadForUI();
    }

    protected abstract void onInvisible();

    protected abstract void loadForData();

    protected abstract void loadForUI();

    // provide layout id
    public abstract int getLayoutID();

}

可以看到纯数据加载的操作,我们重写在loadForData()中就可以了。而涉及到ui的数据加载操作,我们必须放在loadForUI()中,此方法仅当用户可见,并且onCreata后才会调用,就可以避免view还未创建就去使用的NullPoint(不过这样封装挖了一个小小坑不知道大家看出来没)。具体封装就看具体需求了~为了验证以上推论,我把打印log的时间点附一下:

1-12 23:20:33.765 5845-5845/com.example.viewpager E/blink: onCreateView
01-12 23:20:47.924 5845-5845/com.example.viewpager D/blink: onInvisible_Third
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: onInvisible_First
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: loadForData_Second
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: loadForUI_Second
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: onAttach_Third
01-12 23:20:47.933 5845-5845/com.example.viewpager E/blink: onCreateView
01-12 23:20:55.475 5845-5845/com.example.viewpager D/blink: onInvisible_Second
01-12 23:20:55.476 5845-5845/com.example.viewpager D/blink: loadForData_Third
01-12 23:20:55.476 5845-5845/com.example.viewpager D/blink: loadForUI_Third

具体的封装Fragment和ViewPager等以后有时间补充~(996忙得真心伤不起!!!)

Over,欢迎指出错误,互相学习~~~

最后求赞求关注~

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,813评论 25 707
  • Fragment的应用真的是越来越广泛了,之前Android在3.0版本加入Fragment的时候,主要是为了解决...
    闲庭阅读 2,852评论 0 10
  • 在各种Android项目中,我们不可避免要使用到Fragment,但很多地方其实我们只是习惯性或copy代码来使用...
    HolenZhou阅读 2,074评论 1 15
  • 如何在手机上调试 1,手机和电脑处于同一个 wifi2,在电脑上启动一个 http server npm i -g...
    PYFang阅读 288评论 0 0
  • 今天清早起床,听见隔壁房间儿子的闹钟一直在响,可是听不到任何起床的声响。于是我喊了几声。 儿子无动于衷。 昨天11...
    鱼海棠阅读 242评论 4 2