UI片段——Fragment

为什么需要Fragment?Fragment与Activity又是什么关系?Fragment的生命周期是怎样的?Fragment如何使用呢?其实Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity碎片。下面来看看Fragment到底有哪些神奇之处吧 ~

Fragment基本概述

使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化管理。Fragment不能够单独使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影响。

1、一个Activity可以运行多个Fragment 2、Fragment不能脱离Activity而存在 3、Activity是屏幕的主体,而Fragment是Activity的一个组成元素 4、一个Fragment可以被多个Activity重用 5、Fragment有自己的生命周期,并能接收输入事件 6、可以在Activity运行时动态地添加或删除Fragment

Fragment的优势: 1、模块化:我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。 2、可重用:多个Activity可以重用一个Fragment。 3、可适配:根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。

静态加载与动态加载

使用Fragment有两种方式,分别是静态加载和动态加载。

1、静态加载

关于静态加载的流程如下:

  • 定义Fragment的xml布局文件
  • 自定义Fragment类,继承Fragment类或其子类,同时实现onCreate()方法,在方法中,通过inflater.inflate加载布局文件,接着返回其View
  • 在需要加载Fragment的Activity对应布局文件中的name属性设为全限定类名,即包名.fragment
  • 最后在Activity调用setContentView()加载布局文件即可

静态加载一旦添加就不能在运行时删除

比如我现在要先进入到一个专门用于静态加载的Activity

1// 静态加载
2public void toStaticLoadActivity(View view) {
3    startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
4}

StaticLoadActivity的布局文件如下:

 1<LinearLayout
 2    xmlns:android="http://schemas.android.com/apk/res/android"
 3    android:layout_width="match_parent"
 4    android:orientation="vertical"
 5    android:layout_height="match_parent">
 6
 7    <fragment
 8        android:id="@+id/list_fragment"
 9        android:name="com.example.learnfragment.ListFragment"
10        android:layout_width="250dp"
11        android:layout_height="150dp"/>
12
13    <fragment
14        android:layout_marginTop="20dp"
15        android:id="@+id/list_fragment_two"
16        android:name="com.example.learnfragment.ListFragment"
17        android:layout_width="250dp"
18        android:layout_gravity="center"
19        android:layout_height="150dp"/>
20</LinearLayout>

其中fragment的name属性都指向了com.example.learnfragment.ListFragment:

 1// 列表 Fragment
 2public class ListFragment extends Fragment {
 3
 4    // 创建视图
 5    @Nullable
 6    @Override
 7    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 8        View view = inflater.inflate(R.layout.fragment_list, container, false);
 9        TextView textView = view.findViewById(R.id.tv_static_load);
10        textView.setText("Hello, Fragment!");
11        return view;
12    }
13}

fragment_list.xml 布局文件如下所示:

 1<LinearLayout
 2    xmlns:android="http://schemas.android.com/apk/res/android"
 3    android:layout_width="match_parent"
 4    android:orientation="vertical"
 5    android:background="@color/colorPrimary"
 6    android:layout_height="match_parent">
 7
 8    <TextView
 9        android:id="@+id/tv_static_load"
10        android:layout_width="match_parent"
11        android:layout_height="100dp"
12        android:gravity="center"
13        android:textColor="#ffffff"
14        android:textSize="20sp"
15        android:text="This is a fragment" />
16</LinearLayout>

现在只需要点击Main Activity中的TextView即可看到静态加载Fragment的效果:

由此可见,其实Fragment是可复用的,因为StaticLoadActivity的布局文件中写了两个Fragment标签。

2、动态加载

动态加载Fragment的流程如下:

  • 提前准备好Container,即Fragment的容器
  • 获得FragmentManager对象,通过getSupportFragmentManager()
  • 获得FragmentTransaction对象,通过fm.beginTransaction()
  • 调用add()方法或者repalce()方法加载Fragment
  • 最后调用commit()方法提交事务

下面演示一下动态加载Fragment,先在activity_main.xml布局文件中准备好两个Container和按钮:

 1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2    xmlns:tools="http://schemas.android.com/tools"
 3    android:layout_width="match_parent"
 4    android:layout_height="match_parent"
 5    android:orientation="vertical"
 6    tools:context=".MainActivity">
 7
 8    <TextView
 9        android:layout_width="match_parent"
10        android:layout_height="100dp"
11        android:gravity="center"
12        android:onClick="toStaticLoadActivity"
13        android:text="static load fragment" />
14
15    <LinearLayout
16        android:layout_width="match_parent"
17        android:orientation="horizontal"
18        android:layout_height="wrap_content">
19        <Button
20            android:id="@+id/load_left"
21            android:text="加载左边"
22            android:layout_width="0dp"
23            android:layout_weight="1"
24            android:onClick="dynamicLoad"
25            android:layout_height="wrap_content"/>
26        <Button
27            android:id="@+id/load_right"
28            android:text="加载右边"
29            android:layout_width="0dp"
30            android:layout_weight="1"
31            android:onClick="dynamicLoad"
32            android:layout_height="wrap_content"/>
33        <Button
34            android:id="@+id/remove_left"
35            android:text="删除左边"
36            android:layout_width="0dp"
37            android:layout_weight="1"
38            android:onClick="dynamicLoad"
39            android:layout_height="wrap_content"/>
40        <Button
41            android:id="@+id/remove_right"
42            android:text="删除右边"
43            android:layout_width="0dp"
44            android:layout_weight="1"
45            android:onClick="dynamicLoad"
46            android:layout_height="wrap_content"/>
47    </LinearLayout>
48
49    <LinearLayout
50        android:orientation="horizontal"
51        android:layout_width="match_parent"
52        android:layout_height="wrap_content">
53        <LinearLayout
54            android:layout_margin="1dp"
55            android:id="@+id/list_container"
56            android:layout_width="150dp"
57            android:orientation="horizontal"
58            android:layout_height="400dp">
59        </LinearLayout>
60
61        <LinearLayout
62            android:layout_margin="1dp"
63            android:orientation="horizontal"
64            android:id="@+id/detail_container"
65            android:layout_width="200dp"
66            android:layout_height="400dp">
67        </LinearLayout>
68    </LinearLayout>
69</LinearLayout>

一个Container是list_container、一个是detail_container,四个按钮的点击事件分别为dynamicLoad:

 1public class MainActivity extends AppCompatActivity {
 2    ListFragment leftFragment = null;
 3    ListFragment rightFragment = null;
 4    @Override
 5    protected void onCreate(Bundle savedInstanceState) {
 6        super.onCreate(savedInstanceState);
 7        setContentView(R.layout.activity_main);
 8    }
 9
10    // 静态加载
11    public void toStaticLoadActivity(View view) {
12        startActivity(new Intent(MainActivity.this, StaticLoadActivity.class));
13    }
14
15    // 动态加载 1、container 2、fragment 3、fragment -> container
16    public void dynamicLoad(View view) {
17        int id = view.getId();
18        switch (id){
19            case R.id.load_left:
20                leftFragment = new ListFragment();
21                getSupportFragmentManager()
22                        .beginTransaction()
23                        .add(R.id.list_container, leftFragment)
24                        .commit();
25                break;
26            case R.id.load_right:
27                rightFragment = new ListFragment();
28                getSupportFragmentManager()
29                        .beginTransaction()
30                        .add(R.id.detail_container, rightFragment)
31                        .commit();
32                break;
33            case R.id.remove_left:
34                getSupportFragmentManager()
35                        .beginTransaction()
36                        .remove(leftFragment)
37                        .commit();
38                break;
39            case R.id.remove_right:
40                getSupportFragmentManager()
41                        .beginTransaction()
42                        .remove(rightFragment)
43                        .commit();
44                break;
45        }
46    }
47}

动态加载Fragment中,FragmentTransaction类提供了方法完成增删等操作,完成后调用FragmentTransaction.commit()方法提交修改。

  • transaction.add():往Activity里面添加一个片段

  • transaction.remove():从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁

  • transaction.replace():使用另一个Fragment替换当前的,实际上是remove()然后add()的合体

  • transaction.hide():隐藏当前Fragment,仅不可见,不会销毁

  • transaction.show():显示之前隐藏的Fragment

  • detach():会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护

  • attach():重建view视图,附加到UI上并显示。

FragmentTransaction的commit方法一定要在Activity.onSaveInstance()之前调用,commit()操作是异步的,内部通过mManager.enqueueAction()加入处理队列。对应的同步方法为commitNow(),commit()内部会有checkStateLoss()操作,如果开发人员使用不当(比如commit()操作在onSaveInstanceState()之后),可能会抛出异常,而commitAllowingStateLoss()方法则是不会抛出异常版本的commit()方法,但是尽量使用commit(),而不要使用commitAllowingStateLoss()。

FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。

3、使用注意点

1、Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛IllegalStateException异常。

2、如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据。

3、可以在Fragment的onAttach()中通过getArguments()获得传进来的参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象。

Activity向Fragment传值

在ListFragment写一个获取ListFragment的方法:

 1public class ListFragment extends Fragment {
 2    private static final String BUNDLE_TITTLE = "bundle_tittle";
 3    private String mTittle;
 4
 5    // 传递一个String tittle进来
 6    public static ListFragment getInstance(String tittle){
 7        ListFragment fragment = new ListFragment();
 8        Bundle bundle = new Bundle();
 9        bundle.putString(BUNDLE_TITTLE, tittle);
10        fragment.setArguments(bundle);
11        return fragment;
12    }
13    
14    // ....
15    
16    @Override
17    public void onCreate(@Nullable Bundle savedInstanceState) {
18        super.onCreate(savedInstanceState);
19        Bundle arguments = getArguments();
20        if(arguments != null){
21            mTittle = arguments.getString(BUNDLE_TITTLE);
22        }
23    }
24    
25    // 创建视图
26    @Nullable
27    @Override
28    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
29        View view = inflater.inflate(R.layout.fragment_list, container, false);
30        TextView textView = view.findViewById(R.id.tv_static_load);
31        textView.setText(mTittle);
32        return view;
33    }
34}

这样在创建ListFragment对象的时候,直接ListFragment.getInstance()传参即可:

 1switch (id){
 2    case R.id.load_left:
 3        leftFragment = ListFragment.getInstance("Left Fragment!");
 4        getSupportFragmentManager()
 5            .beginTransaction()
 6            .add(R.id.list_container, leftFragment)
 7            .commit();
 8        break;
 9    case R.id.load_right:
10        rightFragment = ListFragment.getInstance("Right Fragment!");
11        getSupportFragmentManager()
12            .beginTransaction()
13            .add(R.id.detail_container, rightFragment)
14            .commit();
15        break;
16    // ...
17}

Fragment向Activity传值

还是根据上面的例子,首先在ListFragment中定义

 1public class ListFragment extends Fragment {
 2    ......
 3    
 4    // 创建视图
 5    @Nullable
 6    @Override
 7    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 8        View view = inflater.inflate(R.layout.fragment_list, container, false);
 9        TextView textView = view.findViewById(R.id.tv_static_load);
10        textView.setText(tittle);
11        textView.setOnClickListener((v) -> {
12            if(mOnTittleListener != null) {
13                mOnTittleListener.onClick(tittle);
14            }
15        });
16        return view;
17    }
18        
19    // 1、定义接口
20    // 当TextView被点击的时候可以把Tittle传出去
21    public interface OnTittleListener {
22        void onClick(String tittle);
23    }
24
25    // 2、定义全局变量
26    private OnTittleListener mOnTittleListener;
27
28    // 3、设置接口的方法
29    public void setOnTittleListener(OnTittleListener onTittleListener) {
30        this.mOnTittleListener = onTittleListener;
31    }
32}

MainActivity中:

 1public class MainActivity extends AppCompatActivity implements ListFragment.OnTittleListener {
 2    ListFragment leftFragment = null;
 3    boolean leftDisplay = false;
 4    
 5    .......
 6        
 7    @Override
 8    protected void onCreate(Bundle savedInstanceState) {
 9        super.onCreate(savedInstanceState);
10        setContentView(R.layout.activity_main);
11        // 1、container 2、fragment 3、fragment -> container
12        leftFragment = ListFragment.getInstance("Left Fragment!");
13        getSupportFragmentManager()
14                .beginTransaction()
15                .add(R.id.list_container, leftFragment)
16                .commit();
17        leftDisplay = true;
18        leftFragment.setOnTittleListener(this);
19    }
20
21    @Override
22    public void onClick(String tittle) {
23        // 设置Lable为ListFragment传回来的值
24        setTitle(tittle);
25    }
26}

如果在Fragment中需要Context,可以通过getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity.getApplicationContext(); 考虑Fragment的重复使用问题,降低与Activity的耦合,Fragment操作应该由它的管理者Activity决定。

Fragment生命周期

Fragment中常用的生命周期方法

当Fragment从创建到运行时回调的生命周期方法有

1、onAttach():当Fragment依附到Activity时调用的方法 2、onCreate():当Fragment创建时调用的方法 3、onCreateView():给Fragment加载布局时调用的方法 4、onActivityCreated():当该Fragment依附的Activity创建时调用的方法 5、onStart():当Fragment启动时调用的方法 6、onResume():当Fragment正在运行时调用的方法

当Fragment不再使用时调用的生命周期方法

7、onPause():当Fragment不在交互时调用该方法 8、onStop():当Fragment不再可见时调用该方法 9、onDestroyView():销毁Fragment布局时调用的方法 10、onDestroy():当Frament销毁时调用的方法 11、onDetach():当Fragment完全脱离Activity时调用的方法

下面通过代码演示一下Fragment的生命周期:

MainActivity的布局中有两个按钮,一个是用于加载Fragment的,另外一个是切换到另外一个Activity的,MainActivity的界面代码如下所示:

 1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2    xmlns:tools="http://schemas.android.com/tools"
 3    android:layout_width="match_parent"
 4    android:layout_height="match_parent"
 5    android:orientation="vertical"
 6    tools:context=".MainActivity">
 7
 8    <Button
 9        android:onClick="loadFragment"
10        android:text="加载Fragment"
11        android:textAllCaps="false"
12        android:layout_width="match_parent"
13        android:layout_height="wrap_content"/>
14    <Button
15        android:layout_width="match_parent"
16        android:layout_height="wrap_content"
17        android:text="切换到另一个Activity"
18        android:textAllCaps="false"
19        android:onClick="toAnotherActivity" />
20    <LinearLayout
21        android:id="@+id/container"
22        android:layout_width="match_parent"
23        android:layout_height="0dp"
24        android:orientation="vertical"
25        android:layout_weight="2" />
26</LinearLayout>

MyFragment的界面:

 1<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2    xmlns:tools="http://schemas.android.com/tools"
 3    android:layout_width="match_parent"
 4    android:layout_height="match_parent"
 5    tools:context=".MyFragment">
 6    <TextView
 7        android:gravity="center"
 8        android:layout_marginTop="50dp"
 9        android:layout_width="match_parent"
10        android:layout_height="match_parent"
11        android:layout_centerInParent="true"
12        android:text="Fragment的布局显示" />
13</RelativeLayout>

AnotherActivity的界面:

 1<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2    xmlns:tools="http://schemas.android.com/tools"
 3    android:layout_width="match_parent"
 4    android:layout_height="match_parent"
 5    tools:context=".AnotherActivity">
 6
 7    <TextView
 8        android:layout_centerInParent="true"
 9        android:layout_width="match_parent"
10        android:gravity="center"
11        android:text="这是另一个Activity"
12        android:textAllCaps="false"
13        android:layout_height="match_parent"/>
14</RelativeLayout>

MainActivity的逻辑,其实主要就是两个点击事件:

 1public class MainActivity extends AppCompatActivity {
 2    @Override
 3    protected void onCreate(Bundle savedInstanceState) {
 4        super.onCreate(savedInstanceState);
 5        setContentView(R.layout.activity_main);
 6    }
 7
 8    // 加载Fragment
 9    public void loadFragment(View view) {
10        MyFragment fragment = MyFragment.newInstance();
11        getSupportFragmentManager()
12                .beginTransaction()
13                .add(R.id.container, fragment, "MyFragment")
14                .commit();
15    }
16
17    // 跳转另一个Activity
18    public void toAnotherActivity(View view) {
19        startActivity(new Intent(this, AnotherActivity.class));
20    }
21}

MyFragment代码如下:

 1public class MyFragment extends Fragment {
 2    private static final String TAG = "MyFragment";
 3    
 4    public static MyFragment newInstance() {
 5        return new MyFragment();
 6    }
 7
 8    @Override
 9    public void onCreate(@Nullable Bundle savedInstanceState) {
10        super.onCreate(savedInstanceState);
11        Log.i(TAG, "onCreate: Fragment创建");
12    }
13
14    @Override
15    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
16                             @Nullable Bundle savedInstanceState) {
17        Log.i(TAG, "onCreateView: Fragment绑定布局");
18        return inflater.inflate(R.layout.my_fragment, container, false);
19    }
20
21    @Override
22    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
23        super.onActivityCreated(savedInstanceState);
24        Log.i(TAG, "onActivityCreated: 依附的Activity创建");
25    }
26
27    @Override
28    public void onStart() {
29        super.onStart();
30        Log.i(TAG, "onStart: Fragment启动");
31    }
32
33    @Override
34    public void onResume() {
35        super.onResume();
36        Log.i(TAG, "onResume: Fragment正在运行");
37    }
38
39    @Override
40    public void onPause() {
41        super.onPause();
42        Log.i(TAG, "onPause: Fragment不再交互");
43    }
44
45    @Override
46    public void onStop() {
47        super.onStop();
48        Log.i(TAG, "onStop: Fragment停止运行");
49    }
50
51    @Override
52    public void onDestroyView() {
53        super.onDestroyView();
54        Log.i(TAG, "onDestroyView: Fragment视图销毁");
55    }
56
57    @Override
58    public void onDestroy() {
59        super.onDestroy();
60        Log.i(TAG, "onDestroy: Fragment销毁");
61    }
62
63    @Override
64    public void onDetach() {
65        super.onDetach();
66        Log.i(TAG, "onDetach: Fragment脱离Activity");
67    }
68}

通过演示可以看到如下效果:

 110-18 17:18:08.735 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreate: Fragment创建
 210-18 17:18:08.736 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onCreateView: Fragment绑定布局
 310-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onActivityCreated: 依附的Activity创建
 410-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
 510-18 17:18:08.742 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
 610-18 17:18:11.353 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
 710-18 17:18:11.820 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
 810-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStart: Fragment启动
 910-18 17:18:14.072 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onResume: Fragment正在运行
1010-18 17:18:16.221 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onPause: Fragment不再交互
1110-18 17:18:16.606 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onStop: Fragment停止运行
1210-18 17:18:16.607 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroyView: Fragment视图销毁
1310-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDestroy: Fragment销毁
1410-18 17:18:16.619 5107-5107/cn.changlin.fragmentlifecycle I/MyFragment: onDetach: Fragment脱离Activity