Tina Tang's Blog

在哪裡跌倒了,就在哪裡躺下來

0%

Android筆記(31)-自動調整版面配置

版面配置(layout)配合不同的螢幕大小和方向而彈性調整,而不是以特定顯示比例和螢幕大小的硬性尺寸來指定版面配置(layout)。因此,建議將 app 設計成在可順利在各種類型的裝置 (小螢幕或大螢幕裝置) 上執行。

學習目標

  • 如何在 app 中加入 SlidingPaneLayout

建構項目

  • 更新 Sports app,配合大螢幕進行調整。

下載範例程式碼


範例應用程式總覽

Sports app 由兩個畫面組成。第一個畫面會顯示 sports lists 。使用者可以選取特定 sport item,系統便會顯示第二個畫面第二個畫面會顯示所選 sports news 的詳細資料。詳細資料畫面會顯示預留位置文字,以簡化實作。

範例程式碼逐步操作說明

您下載的範例程式碼包含預先設計的 list 畫面和詳細資料畫面 layout。在本課程中,您只需瞭解如何讓 app 配合大螢幕自動調整即可。您將用 SlidingPaneLayout 來利用大螢幕。以下是一些檔案的簡短逐步操作說明,以協助您快速上手。

fragment_sports_list.xml

  • 在「Design」檢視畫面中開啟 res/layout/fragment_sports_list.xml
  • 這包含 app 中第一個畫面的版面配置(layout),也就是 sports lists 。
  • 這個版面配置(layout)包含顯示 sports news list 的 Recyclerview。

sports_list_item.xml

  • 在「Design」檢視畫面中開啟 res/layout/sports_list_item.xml
  • 這包含每個項目(item)在 Recyclerview 中版面配置(layout)。
  • 這個版面配置(layout)包含 sport item 的縮圖、新聞標題和 sports news 摘要的預留位置文字。

fragment_sports_news.xml

  • 在「Design」檢視畫面中開啟 res/layout/fragment_sports_news.xml
  • 這包含 app 中第二個畫面的版面配置(layout)。當使用者從 Recyclerview 選擇運動時,就會顯示這個畫面。
  • 這個版面配置(layout)包含 sport item 的圖片橫幅和 sports news 的預留位置文字。

main_activity.xmlcontent_main.xml

  • 這兩個函式數用 single fragment 定義了 main activity 的版面配置(layout)。

navigation/nav_graph.xml

  • 導覽圖包含兩個目的地,一個用於 sports lists,另一個用於 sports news

res/values 資料夾

  • colors.xml 包含 app 中使用的主題顏色。
  • strings.xml 包含 app 所需的所有字串。
  • themes.xml 包含您為 app 產生的 UI 自訂項目。

MainActivity.kt
這個檔案包含預設範本產生的程式碼,可將 activity 的 content view 設為 main_activity.xml。為了處理 app bar 的預設 Up navigation,我們覆寫了 onSupportNavigateUp() 方法

model/Sport.kt
這是一種資料類別(data class),用來存放 sports list Recyclerview 顯示的各列資料

data/SportsData.kt
這個檔案內含名為 getSportsData() 的函式,此函式會傳回預先填入硬式編碼 sports data 的 ArrayList

SportsViewModel.kt
這是 app 的 shared ViewModel。您可以透過 SportsListFragment 來分享 ViewModel,第一個畫面包含 sports lists 和 NewsDetailsFragment,第二個畫面含有detailed sports news

  • _currentSport 屬性為 MutableLiveData, 類型,此類型會儲存使用者選取的 current sport
  • currentSport 屬性是 _currentSport 的 backing property,並以 public 唯讀(read-only)版本的形式公開,方便其他類別使用。
  • _sportsData 屬性包含 sports data list。與上一個屬性類似,sportsData 是這項屬性的 public 唯讀版本。
  • 初始化器 init{} 區塊會初始化 _currentSport_sportsData
    • _sportsData 會透過來自 data/SportsData.kt 的完整 sports lists 進行初始化。
    • _currentSport 會使用 list 中的第一個 item 進行初始化。
  • 函式 updateCurrentSport() 包含 Sports 執行個體(instance),並以傳送的值(value)更新 _currentSport

SportsAdapter.kt
這是 RecyclerView 的 adapter。在建構函式(constructor)中,會傳入 click listener。這個檔案中的大部分程式碼都是先前的程式碼研究室介紹過的樣板程式碼。

SportsListFragment.kt
這是第一個畫面 fragment ,是顯示 sports lists 之處。

  • onCreateView() 函式會使用 binding object 加載(inflates) fragment_sports_list 版面配置(layout) XML
  • onViewCreated() 函式會設定 RecyclerView adapter。這個函式會將使用者選取的 sport 更新為 shared ViewModel (SportsViewModel) 中的 current sport,並導覽至內含 sports news 的詳細資料畫面,使用 submitList(List) 將要顯示的 sports lists 提交至 adapter

NewsDetailsFragment.kt
這是您 app 的第二個畫面,其中會顯示 sports news 的預留位置文字。

  • onCreateView() 函式會使用 binding object 加載(inflates) fragment_sports_news 版面配置(layout) XML
  • onViewCreated() 函式會SportsViewModel 的屬性上附加觀察器(observer) currentSport,在資料有變時自動更新 UI。運動標題(sports title)、圖片(image)和新聞(news)會在觀察器(observer)中更新。

建立應用程式並加以執行

在模擬器或裝置上建構並執行應用程式。選取體育清單中的任何項目,應用程式應該就會進入第二個畫面,畫面上包含新聞的預留位置文字。


List-Detail 模式

目前的範例應用程式無法充分運用大螢幕裝置 (如平板電腦) 的螢幕空間。為解決這個問題,您稍後會透過本程式碼研究室介紹的「List-Detail」模式來顯示應用程式 UI。

在平板電腦上執行應用程式

在這項工作中,您會建立一個具平板電腦設定檔的模擬器。模擬器建立完畢後,您將執行 Sports 應用程式範例程式碼並觀察 UI。

  1. 在 Android Studio 中,依序前往「Tools」>「AVD Manager」。
  2. 系統會隨即顯示「Android Virtual Device Manager」視窗。按一下底部顯示的「+ Create New Virtual Device…」。
  3. 系統隨即會顯示「Virtual Device Configuration」視窗。請在此處設定模擬器硬體和 OS。按一下左側窗格中的「Tablet」。選取中間窗格內的「Pixel C」或任何其他類似的硬體設定檔。
  1. 按一下「Next」。
  2. 在撰寫這個程式碼研究室時,選取最新的系統映像檔 R (API 級別 30)。
  3. 按一下「Next」。
  4. 您現在可以選擇是否要重新命名虛擬裝置,這不是必須選項。
  5. 按一下「Finish」。
  6. 系統會將您帶回「Android Virtual Device Manager」視窗。在新建的虛擬裝置旁邊按一下啟動圖示。
  7. 這應會啟動具平板電腦設定檔的模擬器。請耐心等候,這可能需要一點時間。
  8. 關閉「Android Virtual Device Manager」視窗。
  9. 在新建的模擬器上執行運動應用程式。

請注意,在大型裝置上,應用程式不會使用整個螢幕。在大螢幕上,清單詳細資料模式會較清單模式更為有效。清單詳細資料模式又稱為「主控制項詳細資料」模式,這種模式會在版面配置的其中一側顯示項目清單,而當您輕觸項目時,便會在項目一旁顯示詳細資料。一般情況下,這類檢視畫面只會在大螢幕 (例如平板電腦) 上顯示,因為大螢幕的空間足以顯示更多的內容。

以下是 list-detail 模式的範例圖片:

如上所示,list-detail 模式會在左側顯示 list of items,在右側則會顯示所選 item 的 details。

同理,如果您在 sports app 中使用上述模式,則 news fragment 將成為 details 畫面。

在本程式碼研究室中,您將瞭解如何使用 SlidingPaneLayout 實作 list-detail UI。


SlidingPaneLayout 模式

list-detail UI 可能需要根據螢幕大小而採取不同的行為。大螢幕有足夠的空間,可並排顯示 list 和 detail panes。在 list item 上點擊,即可在 detail pane 中顯示詳細資料。不過在小螢幕上,這樣的安排就會相當擁擠。與其同時顯示這兩個窗格,不如逐一顯示。一開始,list 窗格(pane)會填滿整個畫面。輕觸某個 item,會以該 item 的 detail pane 取代 list pane,同時填滿整個畫面。

您將瞭解如何使用 SlidingPaneLayout 來管理邏輯,進一步根據目前的螢幕大小選取適當的使用者體驗。

請注意 details pane 在小螢幕上滑過 list pane 的方式。

下方圖片說明 SlidingPaneLayout 在小螢幕上的顯示方式。請在 list item 處於選取狀態時,觀察 details pane 與 list pane 如何重疊,才能讓這兩個窗格(pane)始終存在!

因此,SlidingPaneLayout 支援在大型裝置上並排顯示兩個窗格(pane),而在手機之類的小型裝置上,則會自動調整為只顯示一個窗格(pane)。


新增 library dependencies

  1. 開啟 build.gradle (Module: Sports.app)
  2. dependencies 區段中,加入下列 dependency,即可在應用程式中使用 SlidingPaneLayout
1
2
3
4
dependencies {
...
implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01"
}

注意:您可以前往 AndroidX 版本頁面,取得最新的版本號碼。


設定 sports list fragment 的 XML

在這項工作中,您將 fragment_sports_list 的根版面配置轉換為 SlidingPaneLayout。前面介紹過,SlidingPaneLayout 提供水平的雙窗格版面配置,可在 UI 頂層使用。這個版面配置將第一個窗格用作內容清單或瀏覽器,並對應到主要詳細資料檢視畫面,以在另一個窗格中顯示內容。

在 Sports app 中,第一個窗格會是顯示 sports list 的 RecyclerView,第二個窗格則顯示 sports news。

新增 SlidingPaneLayout

  1. 開啟 fragment_sports_list.xml。請注意根版面配置為 FrameLayout
  2. FrameLayout 變更為 androidx.slidingpanelayout.widget.SlidingPaneLayout
1
2
3
4
5
6
7
8
9
10
<androidx.slidingpanelayout.widget.SlidingPaneLayout
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"
tools:context=".SportsListFragment">

<androidx.recyclerview.widget.RecyclerView...>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
  1. SlidingPaneLayout 中加入 android:id 屬性,並指定一個 @+id/sliding_pane_layout 的值。
1
2
3
4
<androidx.slidingpanelayout.widget.SlidingPaneLayout
...
android:id="@+id/sliding_pane_layout"
...>

在 SlidingPaneLayout 中新增第二個窗格

在這個工作中,您會在 SlidingPaneLayout 中新增第二個子項。這會顯示為右側的內容窗格。

  1. fragment_sports_list.xml 中的 RecyclerView 下新增第二個子項 androidx.fragment.app.FragmentContainerView
  2. FragmentContainerView 中加入必要屬性 layout_heightlayout_width,並指定 match_parent 這個屬性值。請注意,您稍後會更新這些值。
1
2
3
<androidx.fragment.app.FragmentContainerView
android:layout_height="match_parent"
android:layout_width="match_parent"/>
  1. FragmentContainerView 中加入 android:id 屬性,並指定一個 @+id/detail_container 的值。
1
android:id="@+id/detail_container"
  1. 使用 android:name 屬性在 FragmentContainerView 中新增 NewsDetailsFragment
1
android:name="com.example.android.sports.NewsDetailsFragment"

更新 layout_width 屬性

SlidingPaneLayout 會依據兩個窗格的寬度來判斷是否要並排顯示窗格(pane)。舉例來說,如果經過測量後,list pane 的最小尺寸為 300dp,而 detail pane 需要 400dp,那麼只要可用寬度至少為 700dpSlidingPaneLayout 就會自動並排顯示這兩個窗格(pane)。

如果子項檢視畫面(view)的組合寬度超過 SlidingPaneLayout 中的可用寬度,子項 view 會發生重疊。在本範例中,子項 view 會展開來填滿 SlidingPaneLayout 中的可用寬度。

為了判斷子項 view 的寬度,建議您瞭解裝置螢幕寬度相關的基本資訊。下表列出了幾個主觀的中斷點,可讓您針對各種可調整大小的應用程式版面配置來進行設計、開發及測試。這些中斷點經過特別挑選,目的是讓您兼顧版面配置的簡單與靈活,進一步根據獨特的情境需求來最佳化應用程式。

寬度 中斷點 裝置佔比
Compact width(精簡寬度) < 600dp 99.96% 直向模式的手機
Medium width(中等寬度) 600dp+ 93.73% 以 portraitLarge 顯示的平板電腦,會以直向展開內部的顯示項目
Expanded width(展開寬度) 840dp+ 97.22% 以 landscapeLarge 顯示的平板電腦,會橫向展開內部的顯示項目

在運動應用程式中,建議您在手機 (寬度小於 600dp 的裝置) 上顯示單一窗格 (也就是體育項目清單)。如要在平板電腦上顯示這兩個窗格,加總的寬度必須大於 840dp。您可以將第一個子項 (RecyclerView) 的寬度設為 550dp,第二個子項 (FragmentContainerView) 則設為 300dp

  1. fragment_sports_list.xml 中,將 RecyclerView 的版面配置寬度變更為 550dp,再將 FragmentContainerView 的版面配置寬度變更為 300dp
1
2
3
4
5
6
7
8
9
<androidx.recyclerview.widget.RecyclerView
...
android:layout_width="550dp"
.../>

<androidx.fragment.app.FragmentContainerView
...
android:layout_width="300dp"
.../>
  1. 分別在採用平板電腦設定檔和手機設定檔的模擬器上執行應用程式。
  • 請注意,平板電腦會顯示兩個窗格。您將在後續步驟中將平板電腦上的第二個窗格設為固定寬度。
  1. 具手機設定檔的模擬器上執行應用程式。

新增 layout_weight

在這項工作中,您將針對平板電腦調整 UI,讓第二個窗格(pane)占滿剩餘的全部空間。

如果檢視畫面(view)未重疊,則在透過版面配置參數 layout_weight 測量子項檢視畫面(view)之後,SlidingPaneLayout 可定義剩餘空間的分割方式。這個參數僅適用於寬度。

  1. fragment_sports_list.xml 中,將 layout_weight 加入 FragmentContainerView 並指定其值為 1。這樣一來,在測量 list 窗格(pane)之後,第二個窗格(pane)就會展開並填滿剩餘空間。
1
android:layout_weight="1"
  1. 執行應用程式。

恭喜!您已成功新增 SlidingPaneLayout。但這項設定尚未完成。從清單選擇項目之後,您必須導入返回導覽功能並更新第二個窗格。您將於後續工作中實作這些項目。


切換 details pane

在具平板電腦設定檔的模擬器上執行應用程式。從 sports list 中選取 list item。請注意,應用程式會導航(navigates)至 details pane。

在這項工作中,您會修正這個問題。目前,系統會先以選取的 sports item 更新雙窗格的內容,接著應用程式會轉至 NewsDetailsFragment

  1. SportsListFragment 檔案的 onViewCreated() 函式中,找到以下程式行以前往 details 畫面。
1
2
3
// Navigate to the details screen
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
  1. 使用下列程式碼取代以上這幾行:
1
binding.slidingPaneLayout.openPane()
  • SlidingPaneLayout 呼叫 openPane(),將第一個窗格(pane)切換至第二個窗格(pane)。如果兩個窗格(pane)皆處於顯示狀態 (例如在平板電腦上),這項操作不會造成任何可見的影響。
  1. 在平板電腦和手機模擬器上執行應用程式。請注意,雙窗格(pane)內容會不斷正確更新。

在下一個工作中,您將為應用程式新增自訂返回導覽功能(custom back navigation)。


新增 custom back navigation

小型裝置上的 list 和 detail pane 會重疊,因此您必須確保系統 back 按鈕可讓使用者從 detail pane 返回 list pane。方法是 提供 custom back navigation,並將 OnBackPressedCallback 連結到 SlidingPaneLayout 目前的狀態。

Back navigation

Back navigation 是指使用者依據瀏覽歷史記錄返回先前瀏覽過的畫面。所有 Android 裝置都會針對這類導覽提供 Back button。視使用者的 Android 裝置而定,該按鈕可能是實體按鈕或軟體按鈕。

Custom back navigation

當使用者導覽應用程式時,Android 系統會保留目的地的返回堆疊(back stack)。一般情況下,使用者按下 Back button 時,Android 可以導覽至之前的目的地。不過在少數情況下,您可能必須為應用程式實作專屬的返回行為(Back behavior),盡可能提供最佳使用者體驗。

舉例來說,在使用 WebView (例如 Chrome 瀏覽器) 時,您可能要覆寫預設的 Back button 行為,讓使用者返回先前的網頁瀏覽記錄,而不是回到先前的應用程式畫面。

同樣地,您必須在 SlidingPaneLayout 中提供 custom back navigation 功能,將應用程式從 detail pane 導航回 list pane

實作 custom back navigation

如要在 Sports 應用程式中實作 custom back navigation,需要執行以下作業:

  • 定義一個 custom callback處理返回鍵(back key)的動作,這會覆寫 OnBackPressedCallback
  • 註冊並新增 callback instance

首先,請定義 custom callback。

  1. SportsListFragment 檔案中的 SportsListFragment 類別定義下方新增類別。將其命名為 SportsListOnBackPressedCallback
  2. 傳入 SlidingPaneLayoutprivate instance 做為建構函式(constructor)參數。
1
2
3
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
)
  1. 透過 OnBackPressedCallback 繼承類別。OnBackPressedCallback 類別會處理 onBackPressed callbacks。您會在不久之後修正這項建構函式(constructor)參數錯誤。
1
2
3
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback()
  • OnBackPressedCallback 的建構函式(constructor)會以布林值(boolean)表示初始啟用狀態。只有在已啟用 callback 的情況下 (即 isEnabled() 傳回 true),分派器(dispatcher)才會呼叫 callback 的 handleOnBackPressed() 來處理 Back button event
  1. slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen 做為建構函式參數傳入 OnBackPressedCallback。只有在第二個窗格可滑動的情況下,布林值 isSlideable 才會為 true;這種情況可能會發生在顯示單一窗格的小螢幕裝置上。如果第二個窗格 (內容窗格) 完全開啟,則 isOpen 的值將為 true
1
2
3
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen)
  • 這段程式碼可確保只有在小螢幕裝置上且內容窗格開啟的情況下,才會啟用這個 callback。
  1. 如要修正未實作方法造成的錯誤,請按一下紅色燈泡圖示,然後選取「Implement members」。

  2. 在「Implement members」彈出式視窗中按一下「OK」,覆寫 handleOnBackPressed 方法。

您的類別應如下所示:

1
2
3
4
5
6
7
8
9
10
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
/**
* Callback for handling the [OnBackPressedDispatcher.onBackPressed] event.
*/
override fun handleOnBackPressed() {
TODO("Not yet implemented")
}
}
  1. handleOnBackPressed() 函式中刪除 TODO 陳述式,然後新增下列程式碼來關閉 content(內容) pane 並返回 list pane。
1
slidingPaneLayout.closePane()

注意:SlidingPaneLayout 會一律允許您手動呼叫 open()close(),讓您可在手機的 list pane 和 detail pane 之間切換。如果兩個窗格(pane)都顯示且未重疊,這些方法就不會生效

監控 SlidingPaneLayout 的事件

除了處理 back press events 外,您也必須監聽及監控與 sliding pane 相關的事件內容窗格(content pane)滑動時,應視滑動情況啟用或停用 callback。您將使用 PanelSlideListener 進行這項操作。

SlidingPaneLayout.PanelSlideListener 介面包含三個抽象方法

  • onPanelSlide()
  • onPanelOpened()
  • onPanelClosed()

details pane 滑動(slides)開啟(opens)關閉(closes)時,系統會呼叫這些方法。

  1. SlidingPaneLayout.PanelSlideListener 展開 SportsListOnBackPressedCallback 類別。

  2. 如要解決錯誤,請導入這三種方法。按一下紅色燈泡,然後在 Android Studio 中選取「Implement members」。

您的 SportsListOnBackPressedCallback 類別應如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
SlidingPaneLayout.PanelSlideListener{

override fun handleOnBackPressed() {
slidingPaneLayout.closePane()
}

override fun onPanelSlide(panel: View, slideOffset: Float) {
TODO("Not yet implemented")
}

override fun onPanelOpened(panel: View) {
TODO("Not yet implemented")
}

override fun onPanelClosed(panel: View) {
TODO("Not yet implemented")
}
}
  1. 移除 TODO 陳述式。

  2. 在 details pane 開啟 (visible) 時,啟用 OnBackPressedCallback callback。實際做法為呼叫 setEnabled() 函式並傳入 true。在 onPanelOpened() 中編寫以下程式碼:

1
setEnabled(true)
  1. 使用屬性存取語法可以簡化上述的程式碼。
1
2
3
override fun onPanelOpened(panel: View) {
isEnabled = true
}
  1. 同樣地,當 details pane 關閉(closed)時,請將 isEnabled 設為 false
1
2
3
override fun onPanelClosed(panel: View) {
isEnabled = false
}
  1. 如要完成 callback,最後一個步驟是將 SportsListOnBackPressedCallbacklistener class 新增至 listenerslist 中,該 list 將會收到 details pane 滑動事件(slide events)的通知。將 init 區塊新增至 SportsListOnBackPressedCallback 類別。在 init 區塊內,呼叫 slidingPaneLayout.addPanelSlideListener() 以傳入 this
1
2
3
init {
slidingPaneLayout.addPanelSlideListener(this)
}

完成的 SportsListOnBackPressedCallback 類別看起來應如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SportsListOnBackPressedCallback(
private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
SlidingPaneLayout.PanelSlideListener{

init {
slidingPaneLayout.addPanelSlideListener(this)
}

override fun handleOnBackPressed() {
slidingPaneLayout.closePane()
}

override fun onPanelSlide(panel: View, slideOffset: Float) {
}

override fun onPanelOpened(panel: View) {
isEnabled = true
}

override fun onPanelClosed(panel: View) {
isEnabled = false
}
}

註冊 callback

如要查看 callback 的實際效果,請使用 dispatcher OnBackPressedDispatcher 註冊 callback。

FragmentActivity 的基礎類別可讓您透過 OnBackPressedDispatcher 控制 Back button 的行為OnBackPressedDispatcher 可控管系統如何將 Back button events 分派到一或多個 OnBackPressedCallback 物件

使用 addCallback() 方法新增 callback。這個方法需要使用 LifecycleOwner。此方法可確保只有在 LifecycleOwnerLifecycle.State.STARTED 時,才會新增 OnBackPressedCallback當相關 LifecycleOwner 遭到刪除時activity 或 fragment 也會移除已註冊的 callbacks,藉此防止記憶體流失,並讓 callbacks 適合用於 fragments 或其他 lifecycle owners (生命週期較短時)。

addCallback() 方法也會採用將 callback class 做為第二個參數的 instance。請按照下列步驟註冊 callback:

  1. SportsListFragment 檔案的 onViewCreated() 函式中,於 binding 變數宣告的下方為 SlidingPaneLayout 建立 instance,並為其指派 binding.slidingPaneLayout 的值。
1
val slidingPaneLayout = binding.slidingPaneLayout
  1. SportsListFragment 檔案的 onViewCreated() 函式中,緊接在 slidingPaneLayout 宣告下方,新增下列程式碼:
1
2
3
4
5
// Connect the SlidingPaneLayout to the system back button.
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
SportsListOnBackPressedCallback(slidingPaneLayout)
)
  • 上述程式碼使用了 addCallback(),並傳入 viewLifecycleOwnerSportsListOnBackPressedCallback 的 instance。這個 callback 的有效期間僅限於 fragment 的生命週期。
  1. 您現在可以在具手機設定檔的模擬器上執行應用程式,看看 custom back button 的實際運作情形。

鎖定模式(Lock mode)

根據預設,在手機等小螢幕裝置上,當 list 和 detail panes 重疊時,使用者可以左右滑動,不必使用手勢操作即可切換這兩個窗格。您可以設定 SlidingPaneLayout 的鎖定模式(lock mode),鎖定或解鎖 details pane。

  1. 在具手機設定檔的模擬器中,嘗試將 details pane 在畫面中滑開。
  2. 你也可以在 details pane 中滑動,請自行嘗試。
  3. 在 Sports 應用程式中,您不必使用這項功能。建議您鎖定 SlidingPaneLayout,防止使用者透過手勢向內及向外滑動。如要實作這項功能,請在 onViewCreated() 方法的 slidingPaneLayout 定義下方,將 lockMode 設為 LOCK_MODE_LOCKED
1
slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
  • 如要進一步瞭解其他 lock mode,請參閱說明文件

注意:鎖定模式(lock mode)只能控制哪些是可用的使用者手勢。無論設定哪一種鎖定模式,您隨時都能透過程式化方式開啟或關閉 SlidingPaneLayout

  1. 再次執行應用程式,注意 detail pane 現已鎖定。

恭喜!您已成功將 SlidingPaneLayout 新增至應用程式!