Android 基础教程5 fragment

随着社会的发展,我们小时候要写日记的场景慢慢的淡出了视野。但是很多时候我们仍然希望能够记录下自己生活的点点滴滴,借助学习Android的机会,我们打造一个自己的日记本。

新建工程LikeNotes:
在这里插入图片描述
博主写的文章是记录学习过程的文章,所以工程看起来比较乱,不会按照app常规的开发流程进行,今天我们主要是学习Fragment的使用,最终实现一个展示日记列表和日记详细信息的页面。

Fragment(碎片)概念

Fragment 表示 FragmentActivity 中的行为或界面的一部分。我们可以在一个 Activity 中组合多个碎片,从而构建多窗格界面,并在多个 Activity 中重复使用某个碎片。我们可以将碎片视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除碎片。

碎片必须始终托管在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。例如,当 Activity 暂停时,Activity 的所有碎片也会暂停;当 Activity 被销毁时,所有碎片也会被销毁。不过,当 Activity 正在运行(处于已恢复生命周期状态)时,可以独立操纵每个碎片,如添加或移除碎片。

Fragment应用场景

Android 在 Android 3.0(API 级别 11)中引入了碎片,主要目的是为大屏幕(如平板电脑)上更加动态和灵活的界面设计提供支持。由于平板电脑的屏幕尺寸远胜于手机屏幕尺寸,因而有更多空间可供我们组合和交换界面组件。利用碎片实现此类设计时,无需管理对视图层次结构做出的复杂更改。通过将 Activity 布局分成各个碎片,在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。

查阅相关文档,演示Fragment的例子大多是新闻应用,如下图:

在这里插入图片描述

Fragment的优势

模块化(Modularity)
可重用(Reusability)
可适配(Adaptability)

那么开始我们今天的学习之旅。

不知道大家创建项目的时候有没有注意到,目前Android已经在创建项目时强制使用androidx包。

在这里插入图片描述
AndroidX 是 Android 团队用于在 Jetpack 中开发、测试、打包和发布库以及对其进行版本控制的开源项目。

AndroidX 对原始 Android 支持库进行了重大改进。与支持库一样,AndroidX 与 Android
操作系统分开提供,并与各个 Android 版本向后兼容。AndroidX完全取代了支持库,不仅提供同等的功能,而且提供了新的库。此外,AndroidX 还包括以下功能:

AndroidX 中的所有软件包都使用一致的命名空间,以字符串 androidx 开头。支持库软件包已映射到对应的 androidx.*软件包。

与支持库不同,AndroidX 软件包会单独维护和更新。androidx 软件包使用严格的语义版本控制,从版本 1.0.0 开始。可以单独更新项目中的 AndroidX 库。

如果要在新项目中使用 AndroidX,则需要将编译 SDK 设置为 Android 9.0(API 级别 28)或更高版本,
在这里插入图片描述

并在 gradle.properties 文件中将以下两个 Android Gradle 插件标记设置为 true。
在这里插入图片描述
android.useAndroidX:如果设置为 true,Android 插件会使用相应的 AndroidX 库,而非支持库。如果未指定,则该标记默认为 false。
android.enableJetifier:如果设置为 true,Android 插件会重写其二进制文件,自动迁移现有的第三方库以使用 AndroidX。如果未指定,则该标记默认为 false。

网上也游荡了好久,前辈们说在开发一个App的时候,创建一个Activity的基类,然后我们后续创建的Activity都继承该基类。在基类中实现通用的方法,同时创建一个活动管理器用来管理我们的App。不管怎么样,我也决定照着把这个结构给搬过来。

这里为了看Log方便,所以直接将Log使用的TAG 使用"LN-"+类名的形式定义,在logcat过滤的时候,直接输入我们的应用程序名称即可。后面有时间我们实现一个简单的Log工具类,让输出更符合我们各自的需求,

注意:TAG的长度最多23个字符,所以我们添加前缀的情况下,Activity的名称不能超过20个字符,我们先这样用吧,这里只是为了查看方便。

BaseActivity
package com.qiushangge.likenotes.base;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public abstract class BaseActivity extends AppCompatActivity {

    // AppCompatActivity名称
    protected final String TAG = "LN-".concat(getClass().getSimpleName());

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityCollector.add(this);

        Log.d(TAG, TAG);

        //加载布局文件
        setContentView(getActivityLayout());

        // 初始化组件
        initActivityView();

        //实现事件监听
        initActivityListener();

        //初始话数据
        initActivityData();
    }

    /**
     * 从活动管理器中移除Activity
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.remove(this);
    }

    /**
     * 完成必要的数据初始化
     */
    protected abstract void initActivityData();

    /**
     * 实现事件监听
     */
    protected abstract void initActivityListener();

    /**
     * 初始化组件
     */
    protected abstract void initActivityView();

    /**
     * @return 返回布局文件资源id
     */
    protected abstract int getActivityLayout();


}


ActivityCollector
package com.qiushangge.likenotes.base;

import android.app.Activity;
import android.util.Log;


import java.util.ArrayList;
import java.util.List;


public class ActivityCollector {

    private static final String TAG = "LN-ActivityCollector";
    /**
     * 缓存程序Activity
     */
    private static List<Activity> allActivities = new ArrayList<>();


    /**
     * 向活动管理器中添加Activity
     *
     * @param activity 创建的Activity实例
     */
    public static void add(Activity activity) {
        allActivities.add(activity);
        Log.d(TAG, "当前创建活动:    ".concat(activity.getClass().getSimpleName()));
    }


    /**
     * 从活动管理器中移除Activity
     *
     * @param activity 销毁的Activity实例
     */
    public static void remove(Activity activity) {
        allActivities.remove(activity);
        Log.d(TAG, "当前销毁活动:  ".concat(activity.getClass().getSimpleName()));
    }

    /**
     * 销毁所有Activity实例,退出应用程序
     */
    public static void finishAll() {
        for (Activity activity : allActivities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
        allActivities.clear();
    }
}

BaseFragment
package com.qiushangge.likenotes.base;


import androidx.fragment.app.Fragment;

public class BaseFragment extends Fragment {

    // 获取当前Fragment名称
    protected final String TAG = "LN-".concat(getClass().getSimpleName());
}

目前我们就先做这么多,更多功能后面有需求我们在完善。

修改创建好的工程,让启动活动继承我们的BaseActivity,并实现其抽象方法。

public class NotePageActivity extends BaseActivity {

    @Override
    protected void initActivityData() {

    }

    @Override
    protected void initActivityListener() {
        Log.d(TAG, "initActivityListener: ");
    }

    @Override
    protected void initActivityView() {
        Log.d(TAG, "initActivityView: ");
    }

    @Override
    protected int getActivityLayout() {
        return R.layout.activity_notes_page;
    }
}

这里我们打印相关log,观察程序执行情况:

在这里插入图片描述

好了,关于base类的简单封装就到这里,看个人编程习惯了。

首先,我们创建一个NoteItem的java类,用来创建一篇日记。日记内容中最常见的属性有标题,内容以及创建时间,另外为了后续方便,我们需要给日记再加一个唯一标识。

最终代码如下:

package com.qiushangge.likenotes.note;

import android.util.Log;

import java.util.Date;
import java.util.UUID;

public class NoteItem {

    private static final String TAG = "NoteItem";

    //日记唯一标识
    private UUID uid;
    //日记标题
    private String noteTitle;
    //日记内容
    private String noteContent;
    //日记创建时间
    private Date dateCreate;
    //日记修改时间
    private Date dateUpdate;


    public NoteItem() {
        uid = UUID.randomUUID();
        dateCreate = new Date();

        Log.d(TAG, uid.toString());
        Log.d(TAG, dateCreate.toString());
    }

    public UUID getUid() {
        return uid;
    }

    public void setUid(UUID uid) {
        this.uid = uid;
    }

    public String getNoteTitle() {
        return noteTitle;
    }

    public void setNoteTitle(String noteTitle) {
        this.noteTitle = noteTitle;
    }

    public String getNoteContent() {
        return noteContent;
    }

    public void setNoteContent(String noteContent) {
        this.noteContent = noteContent;
    }

    public Date getDateCreate() {
        return dateCreate;
    }

    public void setDateCreate(Date dateCreate) {
        this.dateCreate = dateCreate;
    }

    public Date getDateUpdate() {
        return dateUpdate;
    }

    public void setDateUpdate(Date dateUpdate) {
        this.dateUpdate = dateUpdate;
    }
}

在创建fragment之前我们先来看其一些基本知识,然后完善下我们的BaseFragment类。

创建碎片,我们必须创建 Fragment 的子类(或已有其子类)。Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onStart()、onPause() 和 onStop()。实际上,如果要将现有 Android 应用转换为碎片,可能只需将代码从 Activity 的回调方法移入碎片相应的回调方法中。

通常,至少应实现以下生命周期方法:

onCreate()

系统会在创建碎片时调用此方法。当碎片经历暂停或停止状态继而恢复后,如果希望保留此碎片的基本组件,则应在实现中将其初始化。

onCreateView()

系统会在碎片首次绘制其界面时调用此方法。如要为碎片绘制界面,此方法中返回的 View 必须是碎片布局的根视图。如果碎片未提供界面,可以返回 null。

onPause()

系统会将此方法作为用户离开片段的第一个信号(但并不总是意味着此片段会被销毁)进行调用。通常,应在此方法内确认在当前用户会话结束后仍然有效的。

生命周期示意图:

这里是引用

同BaseActivity,我们同样提供三个抽象方法,用来指定布局文件,初始化控件以及添加事件监听。

修改后的BaseFragment:

package com.qiushangge.likenotes.base;


import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

public abstract class BaseFragment extends Fragment {

    // 获取当前Fragment名称
    protected final String TAG = "LN-".concat(getClass().getSimpleName());

    protected View fragmentRoot;

    /**
     * @param savedInstanceState
     */
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    /**
     * @param savedInstanceState
     */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 初始化组件
        initFragmentView();

        //实现事件监听
        initFragmentListener();

        //初始化数据
        initFragmentData();

    }

    /**
     * @param inflater
     * @param container
     * @param savedInstanceState
     * @return
     */
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (fragmentRoot == null) {
            // 初始化当前的根布局, 但是不在创建时就添加到 container 里面
            fragmentRoot = inflater.inflate(getFragmentLayout(), container, false);

        } else {
            if (fragmentRoot.getParent() != null) {
                // 把当前 Root 从其父控件中移除
                ((ViewGroup) fragmentRoot.getParent()).removeView(fragmentRoot);
            }
        }
        return fragmentRoot;
    }


    /**
     *
     */
    @Override
    public void onPause() {
        super.onPause();
    }

    /**
     * 获取布局文件
     *
     * @return
     */
    protected abstract int getFragmentLayout();

    /**
     * 完成必要的数据初始化
     */
    protected abstract void initFragmentData();

    /**
     * 实现事件监听
     */
    protected abstract void initFragmentListener();

    /**
     * 初始化组件
     */
    protected abstract void initFragmentView();
}

这里我们简单看下inflate()方法参数:

View inflate (int resource, 
                ViewGroup root, 
                boolean attachToRoot)

resource : 布局的资源 ID。
root: 将作为扩展布局父项的 ViewGroup。
attachToRoot: 指示是否应在扩展期间将扩展布局附加至 ViewGroup。

接下来我们创建NoteCreateFragment:
在这里插入图片描述
在这里插入图片描述
修改NoteCreateFragment文件,使其继承自我们自定义的BaseFragment类,然后使用Alt+Enter快捷键快速实现父类抽象方法。
在这里插入图片描述

接下来我们去完成今天的NoteCreateFragment布局页面。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_marginTop="16dp"
    android:layout_marginBottom="16dp"
    tools:context=".fragment.NoteCreateFragment">

    <EditText
        android:id="@+id/et_note_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/note_title_hint"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_note_create_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        app:layout_constraintTop_toBottomOf="@id/et_note_title" />

    <EditText
        android:id="@+id/et_note_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@android:color/transparent"
        android:gravity="top"
        android:hint="@string/note_title_content_hint"
        android:singleLine="false"
        app:layout_constraintBottom_toTopOf="@id/btn_save"
        app:layout_constraintTop_toBottomOf="@id/tv_note_create_date" />

    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/note_save"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/et_note_content" />


</androidx.constraintlayout.widget.ConstraintLayout>

完成后显示的页面如下:
在这里插入图片描述
相关字符串资源:

 	<string name="note_title_hint">日记标题</string>
    <string name="note_title_content_hint">日记内容</string>
    <string name="tv_note_title">标题</string>
    <string name="note_save">保存日记</string>

当然了,现在页面比较丑陋,等后面在同一优化。

NoteCreateFragment实现数据的保存:

public class NoteCreateFragment extends BaseFragment {

    //日记记录实例
    private NoteItem noteItem;

    private EditText etNodeTitle;

    private EditText etNodeContent;

    private TextView tvNodeCreateDate;

    private Button btnSave;

    @Override
    protected int getFragmentLayout() {
        return R.layout.fragment_note_create;
    }


    @Override
    protected void initFragmentData() {
        noteItem = new NoteItem();
    }

    @Override
    protected void initFragmentListener() {

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                noteItem.setDateCreate(new Date());
                tvNodeCreateDate.setText(noteItem.getDateCreate().toString());
                Toast.makeText(getContext(), R.string.note_save_success, Toast.LENGTH_SHORT).show();
                Log.d(TAG, "创建日期:"+noteItem.getDateCreate().toString());
                Log.d(TAG, "标题:"+noteItem.getNoteTitle());
                Log.d(TAG, "日记内容:"+noteItem.getNoteContent());
            }
        });

        etNodeTitle.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                noteItem.setNoteTitle(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        etNodeContent.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                noteItem.setNoteContent(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

    }


    @Override
    protected void initFragmentView() {
        etNodeTitle = fragmentRoot.findViewById(R.id.et_note_title);
        etNodeContent = fragmentRoot.findViewById(R.id.et_note_content);
        tvNodeCreateDate = fragmentRoot.findViewById(R.id.tv_note_create_date);
        btnSave = fragmentRoot.findViewById(R.id.btn_save);

    }
}

在android中我们有下面两种方式使用Fragment:

 Activity 的布局文件内声明片段
<?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:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

android:name 属性指定要在布局中进行实例化的 Fragment 类。

通过编程方式将片段添加到某个现有 ViewGroup

在 Activity 中执行碎片事务(如添加、移除或替换片段),则必须使用 FragmentTransaction 中的 API。如下所示, NotePageActivity获取一个 FragmentTransaction 实例:

    	FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然后,使用 add() 方法添加一个碎片段,指定要添加的片段以及将其插入哪个视图


        NoteCreateFragment fragment = new NoteCreateFragment();
        fragmentTransaction.add(R.id.fragment_container, fragment);
        fragmentTransaction.commit();

传递到 add() 的第一个参数是 ViewGroup,即应放置碎片的位置,由资源 ID 指定,第二个参数是要添加的碎片。

activity_notes_page.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".activity.NotePageActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

一般我们会使用FrameLayout用作承载碎片的容器。

一旦通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。

NotePageActivity最终代码如下:



public class NotePageActivity extends BaseActivity {

    @Override
    protected void initActivityData() {

    }

    @Override
    protected void initActivityListener() {

    }

    @Override
    protected void initActivityView() {

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        NoteCreateFragment fragment = new NoteCreateFragment();
        fragmentTransaction.add(R.id.fragment_container, fragment);
        fragmentTransaction.commit();
    }

    @Override
    protected int getActivityLayout() {
        return R.layout.activity_notes_page;
    }
}


运行程序:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章