【Android】学习Fragment

前言:除了安卓四大组件(activityservicecontent providerbroadcast receiver)之外,还有一个最重要的知识点是:fragment

1. Fragment 是什么?

Fragment 也叫碎片,片段(相当于 迷你的 Activity ,或者是 Activity 的模块化的组件)。是 Google 在 Android 3.0 引入的,主要为了给大屏幕的 UI 提供支持的。

实际开发中两个作用:

  • 相当于海报的功能,可以到处粘贴。就是把一部分业务逻辑和 UI 封装在一起,方便灵活的用在各个地方。
  • 适配 Pad 和手机。只用一套代码适配,方便重用,提高程序的复用性和可维护性。

Fragment 可以理解为一个迷你的 Activity 或者是 Activity 的模块化的组件,它有自己的生命周期与显示界面,我们可以利用多个 Fragment 嵌套在 Activity 达到以下的功能,如适配平板,或适配横竖屏幕,或者在程序运行的过程中动态的更改我们的 UI 界面。如下图:


上图是显示应用运行在手机情况下,从一个列表页面跳转到详细页面的例子。如果我们不使用 Fragment 的情况,当应用运行在平板上面的情况,就只能显示放大版的手机显示的界面,为了利用平板的特性,Android 在 3.0 版本中提供了Fragment 技术,我们就可以将列表与内容以组件的方式插入,在手机上分屏显示两个内容,在平板上面就可以左右显示两个内容。

你可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且你可以在 Activity 运行时添加或删除片段(有点像你可以在不同 Activity 中重复使用的“子 Activity ”)。

片段必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。

2. 如何使用 Fragment

2.1 创建 Fragment

如果需要使用 Fragment 的话,需继承 Fragment 类,目前有两个 Fragment 类:

  • import android.support.v4.app.Fragment 提供的向下兼容类(支持3.0以下版本)
  • import android.app.Fragment 提供3.0以上版本的类(只支持3.0以上的版本)

继承 Fragment 至少需实现以下方法:

  • onCreateView() 系统会在片段首次绘制其用户界面时调用此方法,并将创建的UI界面返回。
import android.support.v4.app.Fragment;

public class FirstFragment extends Fragment {
    @Nullable
    @Override
    /* 系统会在片段首次绘制其用户界面时调用此方法,并将创建的UI界面返回 */
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        /* 返回一个view,把布局返回去 */
        View view = inflater.inflate(R.layout.fragment_first, null);
        
        return view;
    }
}

2.2 使用 Fragment

方式1:在 Activity 的布局文件中添加一个 Fragment 控件

<!-- 使用 XML 添加 fragment 子模块 -->
<fragment android:name="com.example.testfragment.FirstFragment"
    android:id="@+id/abc"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

android:name 属性指定要在布局中实例化的 Fragment 类。注意如果是在 xml 中声明的 Fragment 的话,必须给这个 Fragment 设置 id 或者 tag。

方式2:动态添加一个 Fragment 到 Activity 指定的控件内部

FirstFragment firstFragment = new FirstFragment();

// 获取到低版本兼容的 FragmentManager( getFragmentManager(); 支持3.0以上版本 )
FragmentManager fm = getSupportFragmentManager();

// 1.开启一个 Fragment 事务(即可对 Fragment 进行操作)
FragmentTransaction transaction = fm.beginTransaction();

// 2.添加一个 Fragment 到 Activity 指定的控件内部(每调用一次 add 方法,就添加一次,会重复叠加)
// (参数:布局容器 FrameLayout 的 ID,fragment 对象,动态添加时需要设置一个 id/tag)
transaction.add(R.id.content, firstFragment, "abc");

// 3.提交(对 Fragment 进行任何操作都必须提交)
transaction.commit();

2.3 传值给 Fragment

  • 通过Fragment的方法、或者回调进行传值
  • 给 Fragment 传递参数 setArguments
// 页面之间的传值:Bundle是传递数据的集合
Bundle bundle = new Bundle();
bundle.putString("title", "Fragment的标题");
// bundle.putCharSequence("title", title);
// 通过 Bundle 把参数从 Activity 中传给 Fragment
firstFragment.setArguments(bundle);
  • 在 Fragment 中接收参数 getArguments
/* 接收传递的参数 */
Bundle bundle = getArguments();
String title = bundle.getString("title");

3. Fragment 的生命周期


创建的生命周期:

  • onAttach:Fragment 开始与 Activity 关联。
  • onCreate:系统会在创建片段时调用此方法。你应该在实现内初始化您想在片段暂停或停止后恢复时保留的必需片段组件。
  • onCreateView:系统会在片段首次绘制其用户界面时调用此方法。 要想为你的片段绘制 UI,你从此方法中返回的 View 必须是片段布局的根视图。如果片段未提供 UI,你可以返回 null。
  • onActivityCreated:Activity onCreate 完成的回调。
  • onSaveInstanceState:移除的生命周期,处理 Fragment 需要保存数据的方法。
  • onDestroyView:将 Fragment 的 View 试图从UI中移除。
  • onDetach:将 Fragment 与 Activity 取消关联。

4. Fragment 的操作 FragmentTransaction

我们可以使用 FragmentTransaction 来对 Fragment 进行操作,如 add、replace、attach、detach、remove、show、hide。

4.1 add() 方法

作用:将一个 Fragment 添加到一个页面,可以重复叠加
add(int containerViewId, Fragment fragment, String tag)

  • containerViewId:将 Fragment 加入的 ViewGroup 的 id
  • fragment:被操作的 Fragment
  • tag:被操作的 Fragment 的标示,我们操作成功后可以使用这个 tag 找到相应的 Fragment
FragmentTransaction transaction = fm.beginTransaction();
transaction.add(R.id.content, firstFragment, "abc");
transaction.commit();

4.2 replace() 方法

作用:将容器内的 Fragemnt 移除后,再进行添加
replace(int containerViewId, Fragment fragment, String tag)

  • containerViewId:将 Fragment 替换的 ViewGroup 的 id
  • fragment:被操作的 Fragment
  • tag:被操作的 Fragment 的标示,我们操作成功后可以使用这个 tag 找到相应的 Fragment
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.content, firstFragment, "abc");
transaction.commit();

4.3 detach() 方法

作用:将一个 Fragment 从 UI 上面解绑

 Fragment fragment =  fm.findFragmentByTag("abc");
 FragmentTransaction transaction = fm.beginTransaction();
 transaction.detach(fragment);
  • 注意解绑并不是删除,我们还可以通过其他方法把解绑的Fragment重新绑定到UI上面。

4.4 attach() 方法

作用:将一个 Fragment 重新绑定到 UI

FragmentManager fm =  getSupportFragmentManager();
Fragment fragment =  fm.findFragmentByTag("abc");
FragmentTransaction transaction = fm.beginTransaction();
transaction.attach(fragment);
transaction.commit();

4.5 remove() 方法

作用:将一个 Fragment 从 Activity 删除

FragmentManager fm =  getSupportFragmentManager();
Fragment fragment =  fm.findFragmentByTag("abc");
FragmentTransaction transaction = fm.beginTransaction();
transaction.remove(fragment);
transaction.commit();

4.6 show() 方法

作用:显示一个 Fragment(不会调用任何生命周期方法,常用这个)

FragmentManager fm =  getSupportFragmentManager();
Fragment fragment =  fm.findFragmentByTag("abc");
FragmentTransaction transaction = fm.beginTransaction();
transaction.show(fragment);
transaction.commit(); 

4.7 hide() 方法

作用:隐藏一个 Fragment(不会调用任何生命周期方法,常用这个)

FragmentManager fm =  getSupportFragmentManager();
Fragment fragment =  fm.findFragmentByTag("abc");
FragmentTransaction transaction = fm.beginTransaction();
transaction.hide(fragment);
transaction.commit(); 

4.8 commit() 和 commitAllowingStateLoss() 方法的区别

Activity 在以下的操作下容易引起回收并触发 onSaveInstanceState()

  • 按下 HOME 键
  • 按下电源按键(关闭屏幕显示)时
  • 屏幕方向切换
  • Activity 跳转的时候

以上几种情况下不能进行状态的提交,如果提交的话会出现以下错误
Can not perform this action after onSaveInstanceState

出现这个错误的时候,我们有两个解决方法:

  • 不在 onSaveInstanceState() 后调用 commit() 方法
  • 使用 commitAllowingStateLoss() 方法

为了防止在 onSaveInstanceState 方法中调用 commit 会报错,我们使用 commitAllowingStateLoss,可以防止状态丢失报错!

5. Fragment 与返回键

默认情况下,Fragment 是不会响应返回键的。如果需要做到类似Activity回退到上一个界面这样的效果,必须将 FragmentTransaction 加入返回栈。

FragmentTransaction transaction = fm.beginTransaction();
transaction.add(R.id.content, firstFragment, "abc" + index);
// 加入返回任务栈,可通过系统返回按钮控制返回
transaction.addToBackStack(null); // 任务名可为空,也可通过任务名控制多层返回

addToBackStack(String name);
name 参数代表这次 FragmentTransion 的名称,我们可以根据这 个名称找到相应的操作,并进行回退动作。也可以传null,代表不记录该次操作的名称。

如果我们将某次操作加入回退栈的话,我们有以下几种方式进行回退:

  • 使用返回键系统自动回退到上一次操作前的状态。
  • 使用 popBackStack 回退到指定的操作的状态。

popBackStack() 回退到上一次操作前的状态 。
popBackStack(String name, int flags) 回退到某次 name 的操作状态。
flag 为 0 表示回退到任务 name 的操作状态的这一步。
flag 为 POP_BACK_STACK_INCLUSIVE 表示回退到任务 name 的操作状态的上一步。
popBackStack(int id, int flags) 回退某个 id 的操作状态,id 为 commit() 返回的。

@Override
public void onBackPressed() {
    Log.i("11", "点击了系统返回按钮");
    super.onBackPressed();

    FragmentManager fm = getSupportFragmentManager();
    /* 返回到上一页 */
    // fm.popBackStack();
    /* 返回到指定页(参数:加入返回任务栈的任务名, 标记) */
    fm.popBackStack("abc", 0);
}

6. Fragment 动画

默认情况下 Fragment 显示和隐藏是不显示动画的,不过 FragmentTransaction 提供了三种显示的动画的方式:

  • setTransition:使用系统提供的默认显示动画
  • setCustomAnimations:使用自定义动画。
  • setTransition (int transit):可以使用系统提供的默认动画,可供选择的有 TRANSIT_NONE,TRANSIT_FRAGMENT_OPEN,or TRANSIT_FRAGMENT_CLOSE
  • setCustomAnimations (int enter, int exit, int popenter, int popexit):能够使用自定义动画。
    enter 设置Fragment 进入动画
    exit 设置Fragment 退出动画
    popenter popback 后回滚状态后上一个 Fragment 的动画
    popexit popback 后回滚状态后当前的Fragment的动画

7. Fragment 间的交互(通信)

使用回调的方式,让 FragmentActivity 充当中间交互的桥梁。
先定义一个接口(一种规范,这里相当于 FragmentActivity 的基类,方便多个不同的 Activity 使用),再在 FragmentActivity 中实现接口中定义的方法。
1》定义一个接口(OneOnClickListener.java)

package net.cbi360.testfragment;

public interface OneOnClickListener {
    public void OneOnClick(int index);
}

2》实现接口的方法

public class CommonActivity extends FragmentActivity implements OneOnClickListener {
    TwoFragment two;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common);

        FragmentManager fm = getSupportFragmentManager();
        OneFragment one = (OneFragment)fm.findFragmentByTag("one");
        one.setOneOnClickListener(this);
        two = (TwoFragment)fm.findFragmentByTag("two");
    }

    public void OneOnClick(int index) {
        String msg = "点击了" + index + "按钮";
        Log.i("CommonActivity", msg);
        two.setMessage(msg);
    }
}
public class OneFragment extends Fragment {
    OneOnClickListener activity;
    int index = 0;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (null != savedInstanceState) {
            // 恢复参数的值
            index = savedInstanceState.getInt("index");
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, null);
        Button button = (Button)view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Log.i("OneFragment", "点击了按钮");
                index++;
                activity.OneOnClick(index);
            }
        });
        return view;
    }

//    public void setActivity(CommonActivity activity) {
//        this.activity = activity;
//
//    }

    // 解耦(减少关联性,方便不同activity重用)
    public void setOneOnClickListener(OneOnClickListener activity) {
        this.activity = activity;

    }

//    @Override
//    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
//        super.onActivityCreated(savedInstanceState);
//        // 也可以从生命周期方法中拿到 Activity
//        activity = (OneOnClickListener)getActivity();
//    }


    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        // 保存参数的值
        outState.putInt("index", index);
    }
}
  • 解决页面翻转后,数据丢失问题
/* 这个方法的作用是:当页面的状态/参数值丢失时我们来进行保存(缓存页面参数的值) */
@Override
// 当页面翻转时/按下Home键时/按下电源键时/Activity跳转时,会触发下面这个方法
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);

    // 保存值
    outState.putInt("index", index);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

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