Android基础回顾(三)| 关于Fragment

参考书籍:《第一行代码》 第二版 郭霖
如有错漏,请批评指出!

Fragment的简单使用

  • 静态添加Fragment

    首先创建一个项目,自动生成MainActivity和它的布局文件activity_main.xml,然后创建三个Fragment,取消掉下面两项的勾选,仅仅为它自动创建布局文件。

    这里我们需要注意一下,Android中有两个包下的Fragment,一个是系统内置的android.app.Fragment,另一个是support-v4库中的android.support.v4.app.Fragment,系统自动为我们创建的Fragment是support-v4包下的,因为它对Android的版本适配更好,因此后面我们使用的所有Fragment都是support-v4包下的。

    将三个Fragment分别命名为TopFragment1、TopFragment2、ButtomFragment,布局文件fragment_top_1、fragment_top_2、fragment_buttom。
    修改布局文件 fragment_top_1.xml:

    <FrameLayout 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"
        android:background="@color/colorWhite"
        tools:context="com.laughter.AboutFragment.fragment.TopFragment1">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/fragment_1"
            android:textAllCaps="false"
            android:textSize="24sp"/>
    </FrameLayout>
    

    修改 fragment_top_2.xml

    <FrameLayout 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"
        android:background="@color/colorGray"
        tools:context="com.laughter.AboutFragment.fragment.TopFragment2">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/fragment_2"
            android:textSize="24sp"
            android:textAllCaps="false"/>
    </FrameLayout>
    

    这些修改只是为了将两个Fragment区分开来,并没有什么特别的地方。接下来修改MainActivity的布局文件activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:baselineAligned="false">
    
        <fragment
            android:id="@+id/top_fragment"
            android:name="com.laughter.AboutFragment.fragment.TopFragment1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="4"/>
    
        <fragment
            android:id="@+id/buttom_fragment"
            android:name="com.laughter.AboutFragment.fragment.TopFragment2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    
    </LinearLayout>
    

    我们可以看到,这里我们在LinearLayout中添加了两个 <fragment/> 标签,我们通过android:name属性为fragment标签静态绑定Fragment,这里需要注意一定要写完整的包名。下面看运行结果:
  • 动态添加Fragment
    相比于静态添加Fragment,动态添加就要灵活的多了。在前面的基础上,我们先修改一下MainActivity的布局activity_main.xml(仅仅将第一个<fragment/>标签改为FrameLayout。):

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:baselineAligned="false">
    
        <FrameLayout
            android:id="@+id/frame_layout"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="4"/>
    
        <fragment
            android:id="@+id/buttom_fragment"
            android:name="com.laughter.AboutFragment.fragment.ButtomFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    </LinearLayout>
    

    接下来修改ButtomFragment的布局文件fragment_buttom.xml:

    <LinearLayout 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"
        android:orientation="horizontal"
        android:gravity="center"
        tools:ignore="ButtonStyle"
        tools:context="com.laughter.AboutFragment.fragment.ButtomFragment">
    
        <Button
            android:id="@+id/but_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:layout_margin="8dp"
            android:foreground="?android:attr/selectableItemBackground"
            android:text="@string/frag_1"
            android:textSize="24sp"
            android:textAllCaps="false" />
    
        <Button
            android:id="@+id/but_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:layout_margin="8dp"
            android:foreground="?android:attr/selectableItemBackground"
            android:text="@string/frag2"
            android:textSize="24sp"
            android:textAllCaps="false"/>
    </LinearLayout>
    

    我们添加了两个Buttom,用于切换Fragment。最后在MainActivity中实现Fragment的动态添加和切换:

    public class MainActivity extends AppCompatActivity{
    
        private TopFragment1 fragment1;
        private TopFragment2 fragment2;
    
        private final String FRAG1 = "frag1";
        private final String FRAG2 = "frag2";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
        }
    
        @OnClick({R.id.but_left, R.id.but_right})
        public void show(View view) {
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            hideFragments(manager, transaction);
            switch (view.getId()) {
                case R.id.but_left:
                    if (fragment1 == null){
                        fragment1 = new TopFragment1();
                        transaction.add(R.id.frame_layout, fragment1, FRAG1);
                    }
                    transaction.show(fragment1);
                    break;
                case R.id.but_right:
                    if (fragment2 == null){
                        fragment2 = new TopFragment2();
                        transaction.add(R.id.frame_layout, fragment2, FRAG2);
                    }
                    transaction.show(fragment2);
                    break;
                default:
                    break;
            }
            transaction.commit();
        }
    
        private void hideFragments(FragmentManager manager, FragmentTransaction transaction) {
            fragment1 = (TopFragment1)manager.findFragmentByTag(FRAG1);
            fragment2 = (TopFragment2)manager.findFragmentByTag(FRAG2);
            if (fragment1 != null) {
                transaction.hide(fragment1);
            }
            if (fragment2 != null) {
                transaction.hide(fragment2);
            }
        }
    }
    

    上面代码中有关于 ButterKnife 的使用,可以参考我的另一篇博客 Android实践(二) | 注解框架ButterKnife基本使用

    1. 最初,MainActivity的FrameLayout中是什么都没有的,当我们触发点击事件时,我们通过 getSupportFragmentManager() 方法获取到FragmentManager实例,然后调用它的 beginTransaction() 方法开启一个事务;
    2. 然后先隐藏所有的Fragment(若事务中已经add了Fragment实例,可以通过 FragmentManager 的 findFragmentByTag() 方法获取到已经存在的实例,若不存在则返回null);
    3. 接下来判断Fragment实例是否已经被创建,若未创建则创建实例并通过 FragmentTranscation 的add()方法添加到事务中,这里需要注意 add() 方法有多个重载方法,这里因为我们要使用到TAG这个参数,因此我们使用add(int resId, Fragment fragment, String tag)这个。第一个参数传入一个layout,也就是将Fragment添加到哪个布局中;第二个参数就是Fragment实例,第三个参数也就是tag,用于标识我们传入的Fragment实例。
    4. 最后别忘了还要调用 FragmentTranscation 的 commit() 方法提交事务,否则我们前面所做的都无效。

      这样,我们动态添加Fragment的功能就完成了,看效果:

Fragment的生命周期

和Activity一样,Fragment也有自己的生命周期,并且它的生命周期和Activity很相似。

为了直观感受Fragment的生命周期,我们通过一个例子来观察创建及销毁一个Fragment完整的生命周期:
还是通过修改前面的例子,我们给重写Fragmeng的每个生命周期方法,加上打印信息(这里只给出TopFragment1的代码,TopFragment2一样):

public class TopFragment1 extends Fragment {

    private final String TAG = "Fragment1";

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG, "onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView");
        return inflater.inflate(R.layout.fragment_top1, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG, "onActivityCreated");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG, "onDetach");
    }
}

为了生命周期的过程更加清晰,我们用replace()的方式来切换Fragment:

public class MainActivity extends AppCompatActivity {

    private TopFragment1 fragment1;
    private TopFragment2 fragment2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick({R.id.but_left, R.id.but_right})
    public void show(View view) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        switch (view.getId()) {
            case R.id.but_left:
                if (fragment1 == null) {
                    fragment1 = new TopFragment1();
                }
                transaction.replace(R.id.frame_layout, fragment1);
                break;
            case R.id.but_right:
                if (fragment2 == null){
                    fragment2 = new TopFragment2();
                }
                transaction.replace(R.id.frame_layout, fragment2);
                break;
            default:
                break;
        }
        transaction.commit();
    }
}

补充说明:
Fragment的切换有两种方式,第一种是add()+show()+hide()的方式,第二种是replace()的方式,关于它们的区别,后面我会写一篇文章来总结。

下面我们来进行测试:

  1. 点击Fragment1按钮,将TopFragment1添加到Activity中,生命周期如下:

    可以看到,Fragment的创建过程比Activity多了几个方法:
    onAttach():Fragment与Activity建立关联时调用
    onCreateView():为Fragment加载布局时调用
    onActivityCreated():当Activity的 onCreate() 方法执行完后调用

  2. 然后点击Fragment2按钮,将TopFragment2添加到Activity中,生命周期如下:

    当我们切换Fragment时,首先Activity会与Fragment2建立关联,然后创建Fragment2,接下来暂停、销毁Fragment1(因为我们使用的是replace()方式切换Fragment,会默认销毁前一个Fragment的实例),然后为Fragment2加载布局,启动Fragment2。
    onDestroyView():与onCreateView相对应,Fragment的布局被移除时调用
    onDetach():Activity与Fragment解除关联时调用

  3. 再切换回Fragment1,又会创建Fragment1的实例,并移除Fragment2:

4 接下来按back键退出,会销毁Fragment1:

根据上面的测试,我们对Fragment的生命周期已经有了清晰的认识了,不过Fragment不独立存在,一般都是和Activity关联使用,因此我们还需要了解Fragment与Activity生命周期是如何穿插在一起的。
如果你想熟悉一下Activity的生命周期,可以参考:Android笔记(一) | Activity的生命周期

Activity与Fragment生命周期的关系

在前面的基础上,我们只需要重写Activity生命周期的各个方法,并打印出来:

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";

    private TopFragment1 fragment1;
    private TopFragment2 fragment2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Log.d(TAG, "onCreate");
    }

    @OnClick({R.id.but_left, R.id.but_right})
    public void show(View view) { ··· }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }
}

接下来再来测试:
为了观察Activity与Fragment的生命周期是如何交叉在一起的,我们查看静态加载的ButtomFragment的生命周期(需要重写ButtomFragment生命周期的的各个方法,并打印,和前面TopFragment一样),因为静态加载是和Activity一起创建的。

  1. 创建
  2. 息屏
  3. 亮屏
  4. 进入别的Activity
  5. 再回到MainActivity
  6. 按back键退出

有关Fragment的基本知识就是这些了,在实际项目中Fragment的使用十分灵活,还得自己慢慢摸索。


上一篇:Android基础回顾(二)| 常用控件 — ListView和RecyclerView
下一篇:Android基础回顾(四)| 关于广播机制


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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • 动态创建fragment的流程 1.0 新建一个类继承fragment. 2.0 在自定义的fragment里面复...
    仇诺伊阅读 2,209评论 0 3
  • 感激者:余俊娟 地点:湖北省武汉市 时间:2017.7.18,周二 1,我感激,早上快7点出门,还有位置! 2,我...
    余俊娟阅读 226评论 0 0
  • 今天突然想起昨天在超市发生的一些小事,觉得很有趣就记下来。 昨天带女儿回娘家,固定节目之一就是逛超市。作为一个小村...
    童亨书斋阅读 173评论 0 0