欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

安卓 Fragment 碎片详解

程序员文章站 2022-06-19 09:18:15
Android Fragment 是可以看成是一个小型的Activity,又称Activity 片段想想,如果一个很大的界面,就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦使用Fragment则可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理,从而可以更加方便的在 运行过程中动态地更新Activity的用户界面下图是文档中给出的一个 Fragment 分别对应手机与平板间不同情况的处理图Fragment不能单独使用,需要嵌套在Activit......

Android Fragment 是可以看成是一个小型的 Activity,又称 Activity 片段

想想,如果一个很大的界面,就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦

使用 Fragment 则可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理,从而可以更加方便的在 运行过程中动态地更新 Activity 的用户界面

下图是文档中给出的一个 Fragment 分别对应手机与平板间不同情况的处理图

安卓 Fragment 碎片详解

Fragment 不能单独使用,需要嵌套在 Activity中使用,会受到宿主 Activity的生命周期的影响,比如 Activity 被 destory() 销毁了,它也会跟着销毁

Activity 和Fragment 的关系

  • Fragment是依赖于Activity的,不能独立存在的。
  • 一个Activity里可以有多个Fragment。
  • 一个Fragment可以被多个Activity重用。
  • Fragment有自己的生命周期,并能接收输入事件。
  • 我们能在Activity运行时动态地添加或删除Fragment。

Fragment的生命周期

Activity 加载 Fragment 的时候,依次调用下面的方法

onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume()

 

  1. 当 Fragment 所在的 Activity 可见,但不获得焦点时,比如悬浮的对话框风格的 Activity,就会调用 onPause

  2. 当对话框关闭, Activity 又获得了焦点,就会调用 onResume

  3. 替换 Fragment,并调用 addToBackStack() 将它添加到 Back 栈中

    onPause -> onStop -> onDestoryView

    注意 ,此时 Fragment 还没有被销毁

  4. 按下键盘的回退键,Fragment 会再次显示出来

    onCreateView -> onActivityCreated -> onStart -> onResume

  5. 如果替换后, 在事务 commit之前 没有调用 addToBackStack() 方法将 Fragment 添加到 back 栈中的话;又或者退出了 Activity 的话,那么 Fragment 将会被完全结束, Fragment会进入销毁状态

    onPause -> onStop -> onDestoryView -> onDestory -> onDetach

安卓 Fragment 碎片详解

安卓 Fragment 碎片详解

  1. 官方文档说创建 Fragment 时至少需要实现三个方法: onCreate(),onCreateView(),onPause(),其实好像只要实现 onCreateView() 就可以了

  2. Fragment 的生命周期和 Activity 有点类似,有三种状态

    1. Resumed:在允许中的 Fragment可见
    2. Paused : 所在 Activity 可见,但是得不到焦点
    3. Stoped : 片段不可见。宿主 Activity 已停止,或片段已从 Activity 中移除,但已添加到返回栈。 停止片段仍然处于活动状态(系统会保留所有状态和成员信息)。 不过,它对用户不再可见,如果 Activity 被终止,它也会被终止

    4. 调用 addToBackStack(),Fragment 被添加到 Bcak 栈

    5. 该 Activity 转向后台,或者该 Fragment 被替换/删除

      停止状态的fragment仍然活着(所有状态和成员信息被系统保持着),然而,它对用户 不再可见,并且如果activity被干掉,他也会被干掉.

 

安卓 Fragment 碎片详解

 

可以看到Fragment比Activity多了几个额外的生命周期回调方法:

  • onAttach(Context context):Fragment和Activity相关联时调用。如果不是一定要使用具体的宿主 Activity 对象的话,可以使用这个方法或者getContext()获取 Context 对象,用于解决Context上下文引用的问题。同时还可以在此方法中可以通过getArguments()获取到需要在Fragment创建时需要的参数。
  • onCreate():Fragment被创建时调用。
  • onCreateView():创建Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

上面的方法中,只有onCreateView()在重写时不用写super方法,其他都需要。

Fragment的onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的。

package com.example.newdemo.myfragment;

import android.content.Context;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.newdemo.R;

public class TestFragment extends Fragment {

    private final String TAG = "TestFragment";

    @Override
    public void onAttach(@NonNull 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) {
        // Inflate the layout for this fragment
        Log.d(TAG, "---- onCreateView ----");

        View view = inflater.inflate(R.layout.fragment_test, container, false);
        TextView textView = view.findViewById(R.id.frag_text);
        textView.setText("哈哈哈,this is fragment");
        return view;
    }

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

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

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

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

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

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

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

Activity调用顺序:

D/FragmentActivity: --- onCreate ---
D/FragmentActivity: --- onStart --

当打开Fragment调用顺序

D/TestFragment: ---- onAttach ----
D/TestFragment: ---- onCreate ----
D/TestFragment: ---- onCreateView ----
D/TestFragment: ---- onActivityCreated ----
D/TestFragment: ---- onStart ----
D/TestFragment: ---- onResume ----

当退出Fragment的时候:

D/TestFragment: ---- onPause ----
D/TestFragment: ---- onStop ----
D/TestFragment: ---- onDestroy ----
D/TestFragment: ---- onDetach ----

 

Fragment的几个核心类

  • FragmentFragment的基类,任何创建的Fragment都需要继承该类。
  • FragmentManager管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。
  • FragmentTransaction对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。

两种创建Fragment 的方法

1)静态加载Fragment

步骤:

  1. 定义 Fragment 的布局,就是 fragment显示内容的

  2. 自定义一个继承 Fragment 或者它的子类的类,然后重写 onCreateView() 方法

    在该方法中调用 inflater.inflate() 方法加载 Fragment 的布局文件,接着返回加载的 view 对象

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

     

  3. 在需要加载 Fragment 的 Activity 对应的布局文件中添加 fragment 的标签

    注意,android:name 属性是全限定类名,就是要包含 Fragment 的包名

    <fragment
        android:id="@+id/fragment1"
        android:name="com.jay.example.fragmentdemo.Fragmentone"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

     

  4. Activity 在 onCreate() 方法中调用 setContentView() 加载布局文件即可

2)动态 加载Fragment

  1. 通过调用方法 getFragmentManager() 获得 FragmentManager 对象

    FragmentManager fm = getFragmentManager();
    
  2. 通过调用方法 fm.beginTransaction() 获得 FragmentTransaction 对象

    FragmentTransaction ft = fm.fm.beginTransaction();
    
    1. 通过调用 ft.add() 或者 ft.replace() 方法加载 Fragment

      add(要传入的容器,fragment 对象)

  3. 然后调用 ft.commit() 方法提交事务,或调用 ft.remove() 删除事务

                // Activity主要依赖于 FragmentManager 管理 Fragment
                FragmentManager fm = getSupportFragmentManager();

                // Fragment事务,FragmentTransaction 对象提供了 commit() 方法用于提交事务
                FragmentTransaction fragmentTransaction = fm.beginTransaction();
                // 将 TestFragment 加载到 Activity 容器上
                fragmentTransaction.add(R.id.framelayout, new TestFragment());
                fragmentTransaction.commit();

管理Fragment

Activity 管理 Fragment 主要依赖 FragmentManager

FragmentManager 提供了一些方法用于管理 Fragment

方法 说明
findFragmentById() 获得指定的 Fragment
popBackStack() 方法弹出后台 Fragment
addToBackStack(null) 加入 Back 栈

也提供了一个事件监听器用于监听后台栈的变化 addOnBackStackChangeListener

加入到 Back 栈的区别:

  1. 当点击按钮,调用replace()FragmentTest替换为FragmentTestReplace,加addToBackStack()时,日志如下:

安卓 Fragment 碎片详解

 

可以看到,F1被替换时,最后只调到了onDestroyView(),并没有调用onDestroy()和onDetach()。当用户点返回按钮回退事务时,F1会调onCreateView()->onStart()->onResume(),因此在Fragment事务中加不加addToBackStack()会影响Fragment的生命周期。


链接:https://juejin.im/post/6844903816240857095
 

FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:

  • add(): onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume(),在执行add()时,同一个Fragment不允许被add()两次
  • remove(): onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()。
  • replace():相当于新Fragment调用add(),旧Fragment调用remove(), replace() 方法不会保留 Fragment 的状态,也就是说诸如 EditText 内容输入等用户操作在 remove() 时会消失。分为两种情况
    • 不加addToBackStack(): new onAttach() -> new onCreate() -> old onPause()-> old onStop()-> old onDestroyView()-> old onDestroy()-> old onDetach() -> new onCreateView() -> new onActivityCreated() -> new onStart()。
    • addToBackStack(): new onAttach() -> new onCreate() -> old onPause()-> old onStop()-> old onDestroyView() -> new onCreateView -> new onActivityCreated() -> new onStart()。
  • show(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。
  • hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。

 

Fragment 事务:

增删替换 Fragment 需要借助 FragmentTransaction 对象

FragmentTransaction 对象提供了 commit() 方法用于提交事务,提供了 remove() 方法用于删除事务

Fragment 和Activity 交互

1)获取组件的方法

Fragment 获得 Activity 中的组件

getActivity().findViewById(R.id.list);

Activity 获得 Fragment 中的组件(根据 id 或 tag 都可以)

getFragmentManager.findFragmentByid(R.id.fragment1);

2)数据传递

2-1)Activity 给 Fragment 传递数据

在 Activity 中创建 Bundle 数据包,调用 Fragment 实例的 setArguments(bundle) 从而将 Bundle数据包传给 Fragment,然后 Fragment 中调用 getArguments() 获得 Bundle 对象,最后进行解析就可以了

2-2)Fragment 给Activity 传递数据

在 Fragment 中定义一个内部回调接口,再让包含该 Fragment 的 Activity 实现该回调接口,Fragment 就可以通过回调接口传数据了

  1. 在 Fragment 中定义一个回调接口

    public interface CallBack{  
        /*定义一个获取信息的方法*/  
        public void getResult(String result);  
    }

     

  2. 在 Fragment 中实现接口回调

    /*接口回调*/  
    public void getData(CallBack callBack){  
        /*获取文本框的信息,当然你也可以传其他类型的参数,看需求咯*/  
        String msg = editText.getText().toString();  
        callBack.getResult(msg);  
    }

     

  3. 在 Activity 中使用接口回调方法读数据

    /* 使用接口回调的方法获取数据 */  
    leftFragment.getData(new CallBack() {  
     @Override  
           public void getResult(String result) {              /*打印信息*/  
                Toast.makeText(MainActivity.this, "-->>" + result, 1).show();  
                }
            });

     

2-3)Fragment 给Fragment 传递数据

找到要接受数据的 Fragment 对象,直接调用 setArguments() 传数据进去就可以了

一般情况下时 replace 时,即 fragment 跳转的时候传数据的,那么只需要在初始化要跳转的 Fragment 后调用 `setArguments() 方法传入数据即可

如果是两个 Fragment 需要即时传数据,而非跳转的话,就需要以Activity为媒介,先在 Activity 获得 f1 传过来的数据,再传到f2了,就是~

FragmentManager fManager = getSupportFragmentManager( );
FragmentTransaction fTransaction = fManager.beginTransaction();

Fragmentthree t1 = new Fragmentthree();
Fragmenttwo t2 = new Fragmenttwo();

Bundle bundle = new Bundle();
bundle.putString("key",id);
t2.setArguments(bundle); 

fTransaction.add(R.id.fragmentRoot, t2, "~~~");  
fTransaction.addToBackStack(t1);  
fTransaction.commit();

 

 

Fragment的一个基本的使用

1. 创建一个TestFragment 继承于Fragment

package com.example.newdemo.myfragment;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.newdemo.R;

public class TestFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_test, container, false);
        TextView textView = view.findViewById(R.id.frag_text);
        textView.setText("哈哈哈,this is fragment");
        return view;
    }
}

2. 创建Fragment 对应的 xml 文件,一个TextView,主要用于显示

<?xml version="1.0" encoding="utf-8"?>
<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:layout_gravity="center"
    tools:context=".myfragment.TestFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:id="@+id/frag_text"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

3.创建 Activity 及其对应的xml【xml中要有对应的  FrameLayout ,用于显示 fragment】

package com.example.newdemo.myfragment;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.newdemo.R;

public class FragmentActivity extends AppCompatActivity {
    private Button frame_btn;

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

        bindViews();

        frame_btn.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {

                // Activity主要依赖于 FragmentManager 管理 Fragment
                FragmentManager fm = getSupportFragmentManager();
                
                // Fragment事务,FragmentTransaction 对象提供了 commit() 方法用于提交事务
                FragmentTransaction fragmentTransaction = fm.beginTransaction();
                // 将 TestFragment 加载到 Activity 容器上
                fragmentTransaction.add(R.id.framelayout, new TestFragment());
                fragmentTransaction.commit();
            }
        });

    }

    public void bindViews(){
        frame_btn = findViewById(R.id.frame_btn);
    }
}

对应的xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".myfragment.FragmentActivity"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/frame_btn"
        android:text="显示fragement"/>

<!--    <fragment-->
<!--        android:layout_width="wrap_content"-->
<!--        android:layout_height="wrap_content"-->
<!--        android:id="@+id/frame"-->
<!--        android:layout_below="@+id/frame_btn"-->
<!--        android:name="com.example.newdemo.myfragment.TestFragment"/>-->

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/frame_btn"
        android:id="@+id/framelayout"/>

</RelativeLayout>
  • Fragment有一个常见的问题,即Fragment重叠问题,这是由于Fragment被系统杀掉,并重新初始化时再次将fragment加入activity,因此通过在外围加if语句能判断此时是否是被系统杀掉并重新初始化的情况。解决方法有三种

    • 第一种方式,在 Activity 提供的 onAttachFragment() 方法中处理:
    @Override
    public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);
    if (fragment instanceof  OneFragment){
        oneFragment = (OneFragment) fragment;
        }
    }
    
    • 第二种方式,在创建 Fragment 前添加判断,判断是否已经存在:
    Fragment tempFragment = getSupportFragmentManager()
    .findFragmentByTag("OneFragment");
    if (tempFragment==null) {
        oneFragment = OneFragment.newInstance();
    
        ft.add(R.id.fl_content, oneFragment, "OneFragment");
        } else {
    
        oneFragment = (OneFragment) tempFragment;
        }
    
    • 第三种方式,更为简单,直接利用 savedInstanceState 判断即可:
    if (savedInstanceState==null) {
    oneFragment = OneFragment.newInstance();
    ft.add(R.id.fl_content, oneFragment, "OneFragment");
    }else {
    oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment");
    }
    
 

很好的参考文章:

Android Fragment 使用解析

 

本文地址:https://blog.csdn.net/mike_jungle/article/details/110630940