第十四章 Android常见的Fragment的使用

1. 引言

  现如今移动设备的发展非常的迅速,手机和平板都非常的普及了。这两者的差距除了屏幕的大小以外,其他的差距都非常的小。我们知道一般的手机的屏幕是4-6英寸,平板一般是7-10英寸。屏幕大小的差距会让同样的界面的视觉效果有较大的差异。有些控件会被过分的拉伸,元素之间的排列空隙变大,图片的效果失真。

2.碎片的概念

  Android自从3.0开始引入Fragment(碎片),它可以让界面在平板上更好的显示。Fragment是一种可以镶嵌在Activity当中的UI片段。它可以让程序更加合理充分的利用大屏幕的空间。因此在平板上应用非常的普遍。在你对Actiivty有过了解之后,你再来了解Fragment的话,简单了很多,他们之间很多地方都非常的相像。你甚至可以说Fragment是Activity的翻版。因为我们知道,平板的屏幕比手机的屏幕要大。程序员在设计的时候就得兼顾手机和平板。假设我们在开发今日头条的新闻App,一个界面展示一组的新闻标题,另外一个界面展示新闻的内容。现在手机开发,可以是两个Activity,一个Activity展示标题,一个Activity展示内容。那么如果是平板呢?新闻的标题在手机界面上能展示出来,在平板是当然也可以,但是你能容忍还有一大片空白以及标题被拉伸么?这时候就得用上碎片了,使用两个碎片分别放置标题和内容,再将这两个碎片引入到同一个活动里。这样屏幕控件就被充分利用了么。如下图所示:

碎片的使用

3. 碎片的使用

3.1 碎片的简单用法

在一个活动中添加两个碎片,并让这两个碎片平分活动空间。left_fragment.xml

<?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">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="left_Fragment"/>

</LinearLayout>

接着新建一个LeftFragment类继承Fragment。注意,这里可能会有两个不同的包下的Fragment可以选择。一个是系统内置的android.app.Fragment,一个是support-v4库下的android.support.v4.app.Fragment。这里强烈建议你用V4包下的Fragment,因为它可以在所有的Android系统版本中保持功能的一致性。而系统包下的Fragment在4.2之前的设备不能使用。
在使用的时候,不需要在build.gradle文件中添加support-v4库的依赖,builder.gradle文件中添加了appcompat_v7库的依赖,包括了support_v4库。


public class LeftFragment  extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view=inflater.inflate(R.layout.left_fragment,container, false);
        return view;
    }
}

right_fragment.xml

<?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"
    android:background="#ffff00">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="right_fragment"
        android:textSize="20sp"
        />

</LinearLayout>

right的写法与left都一样。然后修改activity_main.xml。之前讲过设置权重,然后就是通过使用<fragment>标签在布局中添加碎片。最后还有设置android:name 属性,来指明需要添加的碎片全包名。项目结果如下图所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.fragmentdemo.MainActivity">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>

    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>
</LinearLayout>

fragment

3.2 碎片的高级用法

1.上面只是讲了碎片在布局文件中的用法,碎片的真正强大之处在于他可以在程序中动态的添加到Activity当中。新建代码new_right_fragment:

<?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"
    android:background="#00ff00">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="新的Fragment"
        android:textSize="20sp"
        />

</LinearLayout>

2.新建Fragment,NewRightFragment继承 Fragment


public class NewRightFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.new_right_fragment,container,false);
        return  view;
    }
}

3.修改activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.fragmentdemo.MainActivity">

    <fragment
        android:id="@+id/left_fragment" android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>

    <FrameLayout
        android:id="@+id/new_right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>
</LinearLayout>

之前的文章介绍了FrameLayout,所有的控件默认放在左上角。这里只有一个碎片,不需要任何定位,非常适合FrameLayout。
4.修改MainActivity,这里我们设计通过点击左边的碎片的按钮,实现右边的碎片进行替换。最后设置点击Back键,返回之前的碎片。

public class MainActivity extends AppCompatActivity  implements OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RightFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                replaceFragment(new NewRightFragment());
                break;
            default:
                break;
        }
    }


    private   void replaceFragment(Fragment fragment){
        FragmentManager  fragmentManager=getSupportFragmentManager();
        FragmentTransaction  fragmentTransaction=fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.right_layout,fragment);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }
}

可以看到,首先我们给左侧的碎片中的按钮button注册了点击事件,然后调用 replaceFragment()这个方法来动态替换掉右边的fragment。换成NewRightFragment。结合代码可以看到:
1.创建带添加的代码实例
2.获取FragmentManager,可以在Activity中直接调用getSupportFragmentManager()方法得到
3.开启一个事务。通过调用beginTransaction()的方法开启
4.向容器内添加或替换碎片。一般使用replace()方法实现,需要传入容器的id,和待添加的碎片实例。
5.在提交事务之前,调用FragmentTransaction 的addToBackStack()的方法,它可以接受一个名字用于描述返回栈的状态,一般传入null就可以了。点击Back键,你会发现程序没有退出,会回退到上一层RightFragment,再点击一次,RightFreagment界面会消失,再点一次,才会退出程序。

6.提交事务,调用commit()的方法
效果如下图所示:

fragment的动态添加

4.碎片和活动之间的通信

我们知道碎片是镶嵌在活动Activity中显示的,但是他们之间的关系并不是特别的亲密。从上面的代码中,你可以看出Fragment和Actiivty都各自存在一个类中。哪么他们之间有没有明显的方式来直接通信呢?肯定是有的。

4.1 活动中获取碎片的方法

为了方便碎片和活动之间进行通信,FragmentManager提供了一个类似于findviewbyId的方法来从布局文件中获取碎片的实例,代码如下:

RightFragment  right=(RightFragment)  getSupportFragmentManager().findFragmentById(R.id.right_fragment);

通过FragmentManager的findFragmentById可以在活动中得到相应的碎片的实例。然后调用碎片里面的方法。

4.2 碎片中获取活动的方法

因为我们知道碎片是嵌入到活动中的,那么每个碎片想要获取对象和的Activity的方法就非常简单了,只需要调用getActivity()的方法来获取和当前碎片相关的实例就可以了。

MainActivity activity =(MainActivity) getActivity();

这样就获取到了活动的实例,有了活动的实例就好办了,你可以随便调用这个活动的方法。另外当碎片需要使用Context对象的时候,也可以使用getActivity()的方法。因为获取的活动本身就是一个Context对象。

4.3 碎片与碎片之间的进行通信

看到这里,我和你说碎片和碎片之间不能通信,你是不是一板砖直接拍过来的啊。好好说话不动手。咳咳,这个 碎片都是在活动中的,每个碎片都是独立的fragment,那么如果fragment之间想要通信怎么办?当然是找爸爸啊,啊不对,是找Activity的。一个活动首先得得到它相关联的活动,然后通过它的活动再去过去另一个碎片的实例。这样就可以实现碎片之间的通信功能了。

5.Fragment的生命周期

看到生命周期是不是很熟悉啊,都说了 Fragment是嵌入到Activity中的,Activity有生命周期,Fragment也肯定会有的啊。来,看图说话:

生命周期的对比

联想一下Activity的生命周期,它在生命周期内一会共有 运行状态,暂停状态,停止状态和销毁四种状态。所以fragment也有这四种状态,在一些小的地方会有所差别。

1.运行状态
当一个碎片可见的时候,并且所关联的Activity正处在运行状态中,该碎片也处于运行状态。
2.暂停状态
当Activity进入暂停的状态时,与它相关的可见的碎片就会进入暂停状态
3.停止状态
当一个活动进入停止状态,与它相关联的碎片就会进入到停止状态,或者通过调用Fragment中的FragmentTransaction()的remove(),replace的方法将碎片从活动中移除,但是如果在事务提交之前调用addToBackStack()方法的话,碎片也会进入到停止状态。总的来说,进入停止的碎片对用户来说是不可见的,有可能会被回收掉。
4.销毁状态
碎片依附于活动,活动被销毁了,碎片也要被销毁。Fragment中的FragmentTransaction在提交事务之前调用remove(),replace()的方法将碎片从活动中移除。但是事务提交之前没有调用addToBackStack()的方法的话,碎片也会进入销毁状态。
碎片的几个附加的回调方法:
1.onAttach() 当碎片和活动简历关联的时候,调用
2.onCreateView() 碎片加载布局的时候调用
3.onActivityCreated() Activity已经创建的时候调用
4.onDestoryView() 与碎片相关联的activity被移除的时候调用
5.onDetch() 当碎片和活动解除关联的时候调用

将RightFragment的生命周期方法加上


public class RightFragment extends Fragment {
    private static final String TAG = "RightFragment";

    @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: ");
    }

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

    @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, "onPause: ");
    }


    @Override
    public void onStop() {
        super.onStop();
        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: ");
    }
}

然后继续代码,点击fragment左边的button,替换右边的fragment,然后点击Back键回退。

打印日志

1.通过日志发现,在运行项目代码的时候,第一次启动项目,会调用onAttach() , onCreate() , onCreateView(),onActivityCreated(),onStart(),onResume()方法,然后点击左边的fragment的按钮,日志改变,调用了onPause() , onStop(),onDestoryView(),这时候RightFragment已经被停止了。因为调用了addToBackStack()方法,如果没有调用会被销毁。onDestory()和onDetach()方法会得到执行。
2.接着,点击Back键,RightFragment重新回到了屏幕,这时候重新调用了onCreateView(),onActivityCreated(),onStart(),onResume()方法。因为借助addToBackStack(),RightFragment方法没有被销毁,所以不会执行onCreate()方法。
3.最后,再次点击Back键依次执行 onPause() , onStop() , onDestoryView() , onDestory() , onDetach() 方法。这样完整体验了一遍Fragment的生命周期了。

6.Fragment动态加载布局的小技巧

  因为我们知道,同样的布局,手机放一个,平板可以放两个。假如程序能够根据设备的分辨率或者屏幕大小来判定加载哪个布局,那样我们发挥的控件就更多了,这里说点小技巧

1.通过限定符判断 Android: layout_width="Match_parent"

很多平板都采用双页模式(左侧显示列表,右侧显示内容),因为屏幕大,能放得下,但是手机屏幕小,放不下,只能显示一页的内容,这时候咋办?举个栗子:
1.新建activity_main3.xml的文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>


</LinearLayout>

2.然后在res目录下创建一个layout_larger包,再新建一个activity_main2.xml的文件。如果创建了包,但是文件无法创建的,是因为没有对包做引用。可以参考我的这篇文章:Android Studio 创建一个layout_large文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>
    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>

</LinearLayout>

这时候我们可以看到layout/activity_main2 布局只包含了一个碎片,而layout_larger/activity_main2布局包括了两个布局。其中large就是一个限定符,哪些屏幕被认为是large的设备会自动加载layout_large文件夹下的布局。小屏幕则会加载layout下的布局。效果如下图所示:

手机显示效果图
平板显示效果图

2. 使用最小宽度限定符

我们使用了Large解决单双页的问题,但是我们不知道到底屏幕多大才符合large呢,这时候可以使用最小宽度限定符。同样的方法创建layout_sw600dp文件夹,创建activity_main2.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>
    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="3"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>
</LinearLayout>

这就意味着,当程序运行在屏幕宽度大于600dp的时候,回家在layout_sw600dp/activity_main2这个文件,屏幕小于600的时候仍然会加载layout/activity_main2.xml文件。
好了,关于fragment的一些用法就差不多了。

最后奉上github地址:https://github.com/wangxin3119/fragment1

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

推荐阅读更多精彩内容