如果前面十篇都看完的話,對於 UI 的掌握度應該就沒什麼問題了,就算是不同的 View
、Activity
,大致上也都能運用一樣的概念。但在 Android UI 學習的旅程上,在 View
跟 Activity
中間還有另外一個層重要的觀念必須介紹。
很多人可能會猜測是 ViewGroup
,有點接近但 ViewGroup
是被歸類在 View
的範疇裡。
而真正的答案就是我們的標題 -- Fragment
。
Android 3.0 開始支援平板裝置,因為對於螢幕尺寸跟手機比起來差這麼多的裝置,實在很難設計一套 layout 同時在手機跟平板都可以完美的呈現,Fragment
就此橫空出世。
基本思維是希望依據螢幕大小在 Activity
嵌入一到多個 Fragment
,把 app 開發從 Activity
(依賴螢幕大小)的維度降到 Fragment
(跟螢幕大小無關)的維度。大的螢幕可以放二個 Fragment
,而小的螢幕就放一個。雖然 Android 平板市佔一直沒什麼起色,但 Fragment
的這種設計概念卻慢慢深入了 Android 開發者的心中。
Android 碎片化有很多面向,我們這裡說的是螢幕尺寸,還有一個比較為人詬病的是 OS version。
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
Fragment 就像 Activity 有很多個狀態,我們一樣引用官網的 lifecycle 如下:
文件如下:
https://developer.android.com/guide/components/fragments#Lifecycle
首先一樣是要加上 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 裡。
所以我們也必須建立繼承自 Fragment
的 MyFragment
這個 class ,
回到 MainActivity
裡把原本 onCreate
除了 setContentView
以外的程式碼移動到 MyFragment
的 onViewCreated
裡,會發現很多地方錯誤,主要是因為 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 起來如果跟前章的結果一樣就是成功囉。
再來我們看怎麼把他變成動態改變。
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