Bootstrap

【Android】碎片的初识

之前我们学习的是一个活动作为一个页面,有了平板之后,页面如果像手机一样设计就会浪费很多的空间,会有很多的空白区域,为了使屏幕充分利用,引入了碎片这样一个概念。

碎片(Fragment):是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分的利用大屏幕的空间,在平板上应用得非常广泛。

碎片的使用方式

碎片的静态创建

我们要在一个活动当中存放两个碎片,并让这两个碎片平分活动的空间,首先新建两个碎片布局

新建一个左碎片布局,当中放一个按钮:

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="buttonl"
        android:id="@+id/Buttonleft2"/>

</LinearLayout>

新建一个右碎片布局,将背景颜色换为粉色,并放置一个TextView用于显示一段文本,用来区分两块碎片:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#f0a1a8"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is right fragment"
        android:layout_gravity="center"
        android:id="@+id/right2"/>

</LinearLayout>

紧接着新建两个类:LeftFragment与RightFragment,并让其继承Fragment,此时我们需要导包,有不同的包供我们选择,此时应导入import androidx.fragment.app.Fragment;如果你导的是别的包,有可能会报错,甚至程序崩溃。接下来编写一下里面的代码吧:

LeftFragment中的代码:

import androidx.fragment.app.Fragment;

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

代码当中我们重写了onCreateView方法,是Fragment生命周期的一部分,用于加载Fragment的布局:

  • LayoutInflater inflater:用于将XML布局文件实例化为View对象。
  • ViewGroup containerFragment 将被添加到这个容器中。
  • Bundle savedInstanceState:如果 Fragment 之前被保存过状态,这个 Bundle 包含了之前保存的状态信息。

同样的方法去重写RightFragment类,基本上代码是相同的。

接下来就去修改承载这两个碎片活动的XML文件代码,由于使用的手机,所以换成了上下平分:

<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".StaticFragment">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/left_fragment"
        tools:layout="@layout/left_fragment"
        android:name="com.example.fragmenttext1.LeftFragment" />

    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/right_fragment"
        tools:layout="@layout/right_fragment"
        android:name="com.example.fragmenttext1.RightFragment4" />

</LinearLayout>

此时两个碎片就被加载到了一个活动当中,看一看运行结果吧:

在这里插入图片描述

注意,在使用fragment标签时:

  1. 必须声明android:id或者android:tag属性,否则会有错

android:tag 是一个可以在 XML 布局文件中使用的属性,它为视图或组件提供了一个标签(tag),方便在代码中通过 findViewWithTag() 方法来查找和引用特定的视图。

  1. 与之前不同在于,之前我们可以直接看到视图的预览,而在fragment时,我们需要使用tools:layout="@layout/left_fragment"来看到预览的视图

动态添加碎片

再新建一个another_right_fragment布局文件,此时修改了文本内容与背景颜色:

<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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#8076a3"
        android:gravity="center"
        android:text="This is other right fragment"
        tools:layout_editor_absoluteX="105dp"
        tools:layout_editor_absoluteY="50dp" />

</LinearLayout>

新建一个AnotherRightFragment作为右边的碎片,与之前的代码类似:

import androidx.fragment.app.Fragment;

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

代码与之前都非常类似,接下来我们就看看如何动态地添加到活动当中,我们修改放碎片的活动的XML文件:

<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".dong_fragment">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/left_fragment"
        tools:layout="@layout/activity_dong_fragment"
        android:name="com.example.fragmenttext1.LeftFragment" />

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

</LinearLayout>

此时,我们将右面的碎片替换成了FrameLayout,下面再通过代码向里面添加内容,从而实现动态的添加碎片,接下来就看放碎片的活动的代码:

public class dong_fragment extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_dong_fragment);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        Button button = (Button) findViewById(R.id.Buttonleft2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                replaceFragment(new AnotherRightFragment());
            }
        });
        replaceFragment(new RightFragment4());
    }

    private void replaceFragment (Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_fragment, fragment);
        transaction.commit();
    }
}

此时我们是对左边碎片的按钮注册了点击事件,一开始会调用replaceFragment方法将右侧添加为一开始的右侧碎片,与上面静态添加碎片的结果相同,但当我们按下Button按钮,就会触发按钮的点击事件,从而使碎片发生变化,变成另一个右边的碎片。

由上面的代码我们知道动态添加布局需要5步:

  • 创建待添加的碎片实例,即上面调用换碎片时所new的实例
  • 获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManager()获取

FragmentManager 是 Android 开发中用于管理 Fragment 生命周期和事务的核心类。它负责将 Fragment 附加到 ActivityFragment,以及执行 Fragment 事务,比如添加、移除、替换和回退等操作。

  • 开启一个事务,通过调用beginTransaction()方法开启

FragmentTransaction 是 Android 开发中用于执行 Fragment 事务操作的类。它允许你在一个事务中执行多个操作,比如添加、移除、替换或隐藏 Fragment

  • 向容器内添加或替换碎片,一般通过replace()方法实现需要传入容器的id和待添加的碎片实例
  • 提交事务,调用commit()方法来完成

重新运行程序,一开始会调用replaceFragment方法将右侧碎片添加到下面,与上面静态添加碎片的结果是一样的,当我们按下Button按钮就会触发点击事件,从而使右侧碎片变化为另一个右侧碎片,效果如图所示:

在这里插入图片描述

在碎片中模拟返回栈

我们上面实现向活动中动态添加碎片,当我们按下按钮转换到另一个碎片的时候,再按下返回键,就会直接退出程序,但我们如果想按下Back键可以返回到上一个碎片,又要如何实现呢?

其实,在FragmentTransaction当中就提供了一个addToBackStack()方法,可以将事务添加到返回栈当中,只需要对上面replaceFragment()进行小小的修改:

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

在提交事务之前,我们调用了FragmentTransactionaddToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可。现在运行程序,点击按钮使其另一个右边碎片替换进活动当中,然后按下Back键,你会发现,程序并没有退出,而是回到了第一个界面,继续按下Back键,右边的碎片会消失,再次按下back键,程序才会退出。

碎片的生命周期

碎片的状态和回调

之前学习活动的时候了解到活动有四种状态:运行状态、暂停状态、停止状态、销毁状态。其实每个碎片的生命周期也会经历这几种状态,但在细微的方面有差别。

  1. 运行状态

当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态

  1. 暂停状态

当一个活动进入暂停状态时(由于另一个未占满屏幕的活动被添加到了栈顶),与它相关联的可见碎片就会进入到暂停状态

  1. 停止状态

当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态,或者通过调用FragmentTransactionremove()replace()方法将碎片从活动中移除,但在事务提交之前调用addToBackStack()方法,此时的碎片会进入停止状态。进入停止状态的碎片对用户来说是完全不可见的,有可能会被系统回收

  1. 销毁状态

碎片是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态,或者通过调用FragmentTransactionremove()replace()方法将碎片从活动中移除,但在事务提交之前没有调用addToBackStack()方法,此时的碎片也会进入销毁状态。

活动的回调方法大家都已经接触过了,接下来就看看碎片的回调方法。活动的回调方法,碎片中几乎都有,但碎片还提供了一些附加的回调方法:

  • onAttach():当碎片和活动建立关联时调用
  • onCreateView():为碎片创建视图(加载布局)时调用
  • onActivityCreated():确保与碎片相关联的活动一定已经创建完毕的时候调用
  • onDestroyView():当与碎片关联的视图被移除的时候调用
  • onDetach():当碎片与活动解除关联的时候调用

完整的碎片过程如图所示:

在这里插入图片描述

体验碎片的生命周期

仍然按照上面的代码进行修改,在右侧碎片的代码当中进行修改

public class RightFragment4 extends Fragment {
    public static final String ATG = "RightFragment";

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

    @Override
    public void onAttach (Context context) {
        super.onAttach(context);
        Log.d(ATG, "onAttach");
    }

    @Override
    public void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(ATG, "onCreate");
    }

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

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

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

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

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

    @Override
    public void onDestroyView () {
        super.onDestroyView();
        Log.d(ATG, "onDestroyView");
    }

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

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

我们在每一个回调方法里面都加入了打印日志的方法,然后重新运行程序,此时的打印信息为:

在这里插入图片描述

依次执行这5种方法,然后点击左边碎片的按钮跳转到另一个碎片,此时的打印信息:

在这里插入图片描述

由于在上面的代码当中我们加入了addToBackStack()方法,则会将现在的碎片进入停止状态,执行onStart()onResume()方法,但如果在替换的时候没有调用这个方法,此时这个碎片处于销毁状态,则会执行onDestroy()onDetach()方法,再次按下Back按钮回到最初的屏幕:

在这里插入图片描述

与第一次屏幕的创建少了两个回调方法:onAttach()onCreate(),再次按下Back键:

在这里插入图片描述

这下你对碎片的生命周期就有了更深刻的理解。

动态加载布局的技巧

使用限定符

在使用平板电脑的时候我们会看到双页模式,左侧含有一个列表,右侧显示内容,而在手机当中大部分都只能显示一页的内容,怎样使程序自己判断使用双页模式还是单页模式呢?此时就需要借助限定符(Qualifiers)来实现了

新建一个活动,在对应的XML文件(activity_qualifiers.xml)里面进行修改

<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".xianDing">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/left_fragment"
        tools:layout="@layout/left_fragment"
        android:name="com.example.fragmenttext1.LeftFragment" />

</LinearLayout>

只放置一个左侧布局充满整个父布局,在res目录下新建layout-large文件夹

在这里插入图片描述

在这里插入图片描述

先将type转换为Layout,再选取Size选项,按到下一页,选取large即可,此时你需要对File进行命名,这时的名字就是第一个布局文件的名字。

在这个文件夹下新建一个名字一样的布局,代码如下:

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

    <fragment
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:id="@+id/left_fragment"
        tools:layout="@layout/left_fragment"
        android:name="com.example.fragmenttext1.LeftFragment" />

    <fragment
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:id="@+id/right_fragment"
        tools:layout="@layout/right_fragment"
        android:name="com.example.fragmenttext1.RightFragment4" />
</LinearLayout>

前一个布局里面只包含了一个碎片,即单页模式,另一个布局包含了两个碎片,即双页模式,其中large就是一个限定符,那些屏幕被认为是large的设备就会自动加载下面的这个布局。此时在手机上运行就会看到加载的是第一个布局即只有一个碎片,当用平板运行加载的就是下面两个碎片的布局。

Android中常见的限定符:

在这里插入图片描述

在这里插入图片描述

使用最小的限定符

在上面我们学习了使用large限定符解决了双页单页问题,但large到底是多大?我们希望更加灵活地为不同的设备加载布局,不论是否被系统认为是large,这时就可以使用最小的限定符(Smallest-widthQualifier)

此时的操作步骤与上面类似,当我们设置最小宽度为600dp时,当屏幕的宽度大于600dp,则会加载现在的这个布局,当屏幕宽度小于600dp时,则会加载默认的布局

在这里插入图片描述

到这里就结束了!

;