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;
    }
}


運行程序:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

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