您的当前位置:首页正文

UI片段——Fragment

2024-11-13 来源:个人技术集锦

为什么需要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

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

StaticLoadActivity的布局文件如下:

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

    <fragment
        android:id="@+id/list_fragment"
        android:name="com.example.learnfragment.ListFragment"
        android:layout_width="250dp"
        android:layout_height="150dp"/>

    <fragment
        android:layout_marginTop="20dp"
        android:id="@+id/list_fragment_two"
        android:name="com.example.learnfragment.ListFragment"
        android:layout_width="250dp"
        android:layout_gravity="center"
        android:layout_height="150dp"/>
</LinearLayout>

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

// 列表 Fragment
public class ListFragment extends Fragment {

    // 创建视图
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        TextView textView = view.findViewById(R.id.tv_static_load);
        textView.setText("Hello, Fragment!");
        return view;
    }
}

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

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_static_load"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="20sp"
        android:text="This is a fragment" />
</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和按钮:

<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="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:gravity="center"
        android:onClick="toStaticLoadActivity"
        android:text="static load fragment" />

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/load_left"
            android:text="加载左边"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:onClick="dynamicLoad"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/load_right"
            android:text="加载右边"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:onClick="dynamicLoad"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/remove_left"
            android:text="删除左边"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:onClick="dynamicLoad"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/remove_right"
            android:text="删除右边"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:onClick="dynamicLoad"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_margin="1dp"
            android:id="@+id/list_container"
            android:layout_width="150dp"
            android:orientation="horizontal"
            android:layout_height="400dp">
        </LinearLayout>

        <LinearLayout
            android:layout_margin="1dp"
            android:orientation="horizontal"
            android:id="@+id/detail_container"
            android:layout_width="200dp"
            android:layout_height="400dp">
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

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

public class MainActivity extends AppCompatActivity {
    ListFragment leftFragment = null;
    ListFragment rightFragment = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

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

    // 动态加载 1、container 2、fragment 3、fragment -> container
    public void dynamicLoad(View view) {
        int id = view.getId();
        switch (id){
            case R.id.load_left:
                leftFragment = new ListFragment();
                getSupportFragmentManager()
                        .beginTransaction()
                        .add(R.id.list_container, leftFragment)
                        .commit();
                break;
            case R.id.load_right:
                rightFragment = new ListFragment();
                getSupportFragmentManager()
                        .beginTransaction()
                        .add(R.id.detail_container, rightFragment)
                        .commit();
                break;
            case R.id.remove_left:
                getSupportFragmentManager()
                        .beginTransaction()
                        .remove(leftFragment)
                        .commit();
                break;
            case R.id.remove_right:
                getSupportFragmentManager()
                        .beginTransaction()
                        .remove(rightFragment)
                        .commit();
                break;
        }
    }
}

 

动态加载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的方法:

public class ListFragment extends Fragment {
    private static final String BUNDLE_TITTLE = "bundle_tittle";
    private String mTittle;

    // 传递一个String tittle进来
    public static ListFragment getInstance(String tittle){
        ListFragment fragment = new ListFragment();
        Bundle bundle = new Bundle();
        bundle.putString(BUNDLE_TITTLE, tittle);
        fragment.setArguments(bundle);
        return fragment;
    }
    
    // ....
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle arguments = getArguments();
        if(arguments != null){
            mTittle = arguments.getString(BUNDLE_TITTLE);
        }
    }
    
    // 创建视图
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        TextView textView = view.findViewById(R.id.tv_static_load);
        textView.setText(mTittle);
        return view;
    }
}

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

switch (id){
    case R.id.load_left:
        leftFragment = ListFragment.getInstance("Left Fragment!");
        getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.list_container, leftFragment)
            .commit();
        break;
    case R.id.load_right:
        rightFragment = ListFragment.getInstance("Right Fragment!");
        getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.detail_container, rightFragment)
            .commit();
        break;
    // ...
}

 

Fragment向Activity传值

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

public class ListFragment extends Fragment {
    ......
    
    // 创建视图
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        TextView textView = view.findViewById(R.id.tv_static_load);
        textView.setText(tittle);
        textView.setOnClickListener((v) -> {
            if(mOnTittleListener != null) {
                mOnTittleListener.onClick(tittle);
            }
        });
        return view;
    }
        
    // 1、定义接口
    // 当TextView被点击的时候可以把Tittle传出去
    public interface OnTittleListener {
        void onClick(String tittle);
    }

    // 2、定义全局变量
    private OnTittleListener mOnTittleListener;

    // 3、设置接口的方法
    public void setOnTittleListener(OnTittleListener onTittleListener) {
        this.mOnTittleListener = onTittleListener;
    }
}

MainActivity中:

public class MainActivity extends AppCompatActivity implements ListFragment.OnTittleListener {
    ListFragment leftFragment = null;
    boolean leftDisplay = false;
    
    .......
        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1、container 2、fragment 3、fragment -> container
        leftFragment = ListFragment.getInstance("Left Fragment!");
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.list_container, leftFragment)
                .commit();
        leftDisplay = true;
        leftFragment.setOnTittleListener(this);
    }

    @Override
    public void onClick(String tittle) {
        // 设置Lable为ListFragment传回来的值
        setTitle(tittle);
    }
}

 

 

如果在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的界面代码如下所示:

<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="vertical"
    tools:context=".MainActivity">

    <Button
        android:onClick="loadFragment"
        android:text="加载Fragment"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="切换到另一个Activity"
        android:textAllCaps="false"
        android:onClick="toAnotherActivity" />
    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_weight="2" />
</LinearLayout>

MyFragment的界面:

<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=".MyFragment">
    <TextView
        android:gravity="center"
        android:layout_marginTop="50dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:text="Fragment的布局显示" />
</RelativeLayout>

AnotherActivity的界面:

<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=".AnotherActivity">

    <TextView
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:gravity="center"
        android:text="这是另一个Activity"
        android:textAllCaps="false"
        android:layout_height="match_parent"/>
</RelativeLayout>

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

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 加载Fragment
    public void loadFragment(View view) {
        MyFragment fragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.container, fragment, "MyFragment")
                .commit();
    }

    // 跳转另一个Activity
    public void toAnotherActivity(View view) {
        startActivity(new Intent(this, AnotherActivity.class));
    }
}

MyFragment代码如下:

public class MyFragment extends Fragment {
    private static final String TAG = "MyFragment";
    
    public static MyFragment newInstance() {
        return new MyFragment();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: Fragment创建");
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        Log.i(TAG, "onCreateView: Fragment绑定布局");
        return inflater.inflate(R.layout.my_fragment, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.i(TAG, "onActivityCreated: 依附的Activity创建");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i(TAG, "onStart: Fragment启动");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i(TAG, "onResume: Fragment正在运行");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.i(TAG, "onPause: Fragment不再交互");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.i(TAG, "onStop: Fragment停止运行");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.i(TAG, "onDestroyView: Fragment视图销毁");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: Fragment销毁");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.i(TAG, "onDetach: Fragment脱离Activity");
    }
}

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

 

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

  • 本文作者: Tim
  • 本文链接:
  • 版权声明: 本博客所有文章除特别声明外,均采用 许可协议。转载请注明出处!
显示全文