iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
0
Mobile Development

Android 十全大補系列 第 11

[Android 十全大補] Fragment

Fragment

如果前面十篇都看完的話,對於 UI 的掌握度應該就沒什麼問題了,就算是不同的 ViewActivity,大致上也都能運用一樣的概念。但在 Android UI 學習的旅程上,在 ViewActivity 中間還有另外一個層重要的觀念必須介紹。
很多人可能會猜測是 ViewGroup,有點接近但 ViewGroup 是被歸類在 View 的範疇裡。

而真正的答案就是我們的標題 -- Fragment

Android 3.0 開始支援平板裝置,因為對於螢幕尺寸跟手機比起來差這麼多的裝置,實在很難設計一套 layout 同時在手機跟平板都可以完美的呈現,Fragment 就此橫空出世。

基本思維是希望依據螢幕大小在 Activity 嵌入一到多個 Fragment,把 app 開發從 Activity(依賴螢幕大小)的維度降到 Fragment(跟螢幕大小無關)的維度。大的螢幕可以放二個 Fragment,而小的螢幕就放一個。雖然 Android 平板市佔一直沒什麼起色,但 Fragment 的這種設計概念卻慢慢深入了 Android 開發者的心中。

Android 碎片化有很多面向,我們這裡說的是螢幕尺寸,還有一個比較為人詬病的是 OS version。

Fragment is not Fragment

Fragment 的出現是為了解決螢幕大小碎片化的問題,但有趣的是 Fragment 自己也碎片化了,在一開始推出的時候,Android 是內建在 Android 3.0 的 SDK 裡的,但因為 Android OS 更新實在是很慢,為了更快提的迭代,Android 提供了 support library 把 Fragment 獨立自 Android SDK 外降低對 OS 的依賴,但 support library 的存在也是蠻令人困惑的,而且又有 v4、v7、v13 等不同版本,幸好在一片混亂之後,最後又整合回到了 Android X 裡。所以其實大家只要記得 import Android X 這個版本的 Fragment 就對了!!

相信熟悉 OS 歷史的人都知道我們 Fragment is not Fragment 的梗是來自於 Linux 的 Linux Is Not UniX,如果你沒看過,可以 google 看看。

如果你好奇有哪些 Fragment 的話:

  • android.app.Fragment
  • android.support.v4.app.Fragment
  • android.support.v13.app.FragmentCompat
  • androidx.fragment.app.Fragment

Lifecycle

Fragment 就像 Activity 有很多個狀態,我們一樣引用官網的 lifecycle 如下:

文件如下:
https://developer.android.com/guide/components/fragments#Lifecycle

Usage

首先一樣是要加上 dependency。

dependencies {
    implementation "androidx.fragment:fragment:1.1.0"
}

有二種方式可以使用 Fragment,我們先直接定義在 xml 裡之後再看怎麼動態修改,回到 activity_main.xml 把內容移動到新建立的檔案 fragment_main.xml,然後把 activity_main.xml 改成 fragment 如下:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment"
    class="com.example.myapplication.MyFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</fragment>

Android 會自動連結到 com.example.myapplication.MyFragment 裡把真正產生的 View 放回到 xml 裡。

所以我們也必須建立繼承自 FragmentMyFragment 這個 class ,
回到 MainActivity 裡把原本 onCreate 除了 setContentView 以外的程式碼移動到 MyFragmentonViewCreated 裡,會發現很多地方錯誤,主要是因為 Activity 繼承自 Context ,但 Fragment 必須另外透過 getContext() 才能拿到。我們把所有的 this 改成 context ,然後複寫相對應的 onCreateView 來建立 layout,會得到以下的程式碼。

class MyFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ) = inflater.inflate(R.layout.fragment_list, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
        val adapter = MyAdapter()
        adapter.setListener(object : MyAdapter.OnItemClickListener {
            override fun onItemClick(index: Int) {
                startActivity(Intent(context, SecondActivity::class.java))
            }
        })
        recyclerView.adapter = adapter
        recyclerView.layoutManager =
            LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        val dividerItemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
        recyclerView.addItemDecoration(dividerItemDecoration)
    }
}

如果不清楚為什麼 context 等於 getContext(),可以參考 Kotlin 文件:
https://kotlinlang.org/docs/reference/java-interop.html#getters-and-setters

另一方面,MainActivity 就會空空如也如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

重新 build 一次我們的程式,run 起來如果跟前章的結果一樣就是成功囉。
再來我們看怎麼把他變成動態改變。

FragmentManager

Fragment 一個很棒的優點就是他就像疊積木一樣可以一層一層新增,也可以刪除、置換,使用起來甚至比 Activity 更自由,這些操作都是透過 FragmentManager 來完成。我們來做個簡單的範例把原本寫在 xml 的 fragment 改成動態設定的樣子。
回到 activity_main.xml 把 fragment 換成 FrameLayout 如下:

<?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:id="@+id/fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</FrameLayout>

FrameLayout 大致上可以視為最陽春版的一種 layout,文件如下:
https://developer.android.com/reference/android/widget/FrameLayout

是時候回到 MainActivity 把空空的 onCreate 補滿啦:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val fragmentManager = supportFragmentManager
    val fragmentTransaction = fragmentManager.beginTransaction()
    fragmentTransaction.add(R.id.fragment, MyFragment())
    fragmentTransaction.commit()
}

FragmentTransaction 是我們用來操作新增(add)、刪除(delete)或置換(replace) Fragment 的物件。可以做一系列操作在呼叫 commit 來確認執行。
FragmentTransaction 還提供一個很棒的功能是 addToBackStack,當我們呼叫這個函式後,可以使用 Android 的 back 鍵來返回到更新 fragment 前的狀態。

重新 build 一次我們的程式,run 起來如果跟前章的結果一樣就是成功囉。

挑戰:大家可以想想 Activity、Fragment、Custom View 這幾個 components 的差異是什麼?什麼情境適合哪個 component,這是常見的面試題喔。

Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786


上一篇
[Android 十全大補] RecyclerView as a Pro
下一篇
[Android 十全大補] Retrofit
系列文
Android 十全大補30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言