自動調整版面配置

1. 事前準備

Android 裝置有多種形狀、大小和板型規格。因此,建議將應用程式設計成在可順利在各種類型的裝置 (小螢幕或大螢幕裝置) 上執行。開發人員撰寫可正式發布的應用程式可能為 Android WearAndroid AutoAndroid TV 提供支援,但這些主題並不在本課程的涵蓋範圍內。只要應用程式盡量支援多種螢幕,就能提供給更多擁有不同裝置的使用者。

您的應用程式必須具有彈性的版面配置。版面配置應配合不同的螢幕大小和方向而彈性調整,而不是以特定顯示比例和螢幕大小的硬性尺寸來指定版面配置。在折疊式裝置上執行應用程式時,這個原則同樣適用,因為這類裝置的螢幕大小和長寬比可能會在應用程式執行過程中變動。在本程式碼研究室的結尾,您將會學到可折疊裝置的簡介。

aecb59fc49fb4abf.png

必要條件

  • 如何將程式碼下載到 Android Studio 並執行。
  • 熟悉 Android 架構元件 ViewModelLiveData
  • Navigation 元件的基本知識。

課程內容

  • 如何在應用程式中加入 SlidingPaneLayout

建構項目

  • 更新運動應用程式,配合大螢幕進行調整。

您將會需要的

  • 已安裝 Android Studio 的電腦。
  • 運動 應用程式的範例程式碼。

下載本程式碼研究室的範例程式碼

本程式碼研究室提供範例程式碼,可延伸至本程式碼研究室所教授的功能。範例程式碼可能包含程式碼研究室先前介紹過的程式碼,也可能會有之後才會介紹的程式碼,因此不盡然是您熟悉的內容。

如要從 GitHub 取得本程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Get from VCS」

61c42d01719e5b6d.png

  1. 在「Get from Version Control」對話方塊中,確認您已為「Version Control」選取「Git」

9284cfbe17219bbb.png

  1. 將提供的程式碼網址貼到「URL」方塊中。
  2. 您也可以將「Directory」變更為與建議預設值不同的內容。

5ddca7dd0d914255.png

  1. 按一下「Clone」。Android Studio 會開始擷取程式碼。
  2. 等待 Android Studio 開啟。
  3. 在程式碼研究室的範例程式碼、應用程式或解決方案程式碼中,選取正確的模組。

2919fe3e0c79d762.png

  1. 按一下「Run」按鈕 8de56cba7583251f.png,即可建構並執行程式碼。

2. 觀看程式設計示範影片 (可略過)

如果您想觀看課程老師示範完成此程式碼研究室,請觀看以下影片。

建議您在全螢幕模式下觀看影片 (點選影片右下角的 這個符號突顯出正方形的 4 個角,用來表示全螢幕模式。 圖示),才能清楚看見 Android Studio 和程式碼。

您可以跳過這個步驟,也可以不觀看這段影片,立即開始進行程式碼研究室的操作步驟。

3. 範例應用程式總覽

運動應用程式由兩個畫面組成。第一個畫面會顯示運動清單。使用者可以選取特定運動項目,系統便會顯示第二個畫面。第二個畫面會顯示所選運動新聞的詳細資料。詳細資料畫面會顯示預留位置文字,以簡化實作。

範例程式碼逐步操作說明

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

fragment_sports_list.xml

  • 在「設計」檢視畫面中開啟 res/layout/fragment_sports_list.xml
  • 這包含應用程式中第一個畫面的版面配置,也就是運動清單。
  • 這個版面配置包含顯示體育新聞清單的 Recyclerview。

f50d3e7b41fcb338.png

d9af155f87ddbcdf.png

sports_list_item.xml

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

b19fd0e779c1d7c3.png

fragment_sports_news.xml

  • 在「設計」檢視畫面中開啟 res/layout/fragment_sports_news.xml
  • 這包含應用程式中第二個畫面的版面配置。當使用者從 Recyclerview 選擇運動時,就會顯示這個畫面。
  • 這個版面配置包含體育項目的圖片橫幅和體育新聞的預留位置文字。

c2073b1752342d97.png

main_activity.xml 和 content_main.xml

這兩個函式數用單一片段定義了主要活動的版面配置。

導覽圖包含兩個目的地,一個用於運動清單,另一個用於運動新聞。

res/values 資料夾

您已熟悉此資料夾中的資源檔案。

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

MainActivity.kt

這個檔案包含預設範本產生的程式碼,可將活動的內容檢視畫面設為 main_activity.xml。為了處理應用程式列的預設向上導覽,我們覆寫了 onSupportNavigateUp() 方法。

model/Sport.kt

這是一種資料類別,用來存放運動名單 Recyclerview 顯示的各列資料。

data/SportsData.kt

這個檔案內含名為 getSportsData() 的函式,此函式會傳回預先填入硬式編碼運動資料的 ArrayList

SportsViewModel.kt

這是應用程式的共享 ViewModel。您可以透過 SportsListFragment 來分享 ViewModel,第一個畫面包含運動清單和 NewsDetailsFragment,第二個畫面含有詳細的運動新聞。

  • _currentSport 屬性為 MutableLiveData, 類型,此類型會儲存使用者選取的現有運動。currentSport 屬性是 _currentSport 的備用屬性,並以公開唯讀版本的形式公開,方便其他類別使用。
  • _sportsData 屬性包含運動資料清單。與上一個屬性類似,sportsData 是這項屬性的公開唯讀版本,
  • 初始化器 init{} 區塊會初始化 _currentSport_sportsData_sportsData 會透過來自 data/SportsData.kt 的完整運動清單進行初始化。_currentSport 會使用清單中的第一個項目進行初始化。
  • 函式 updateCurrentSport() 包含 Sports 執行個體,並以傳送的值更新 _currentSport

SportsAdapter.kt

這是 RecyclerView 的轉接介面。在建構函式中,會傳入點擊事件監聽器。這個檔案中的大部分程式碼都是先前的程式碼研究室介紹過的樣板程式碼。

SportsListFragment.kt

這是第一個畫面片段,是顯示運動清單之處。

  • onCreateView() 函式會使用繫結物件加載 fragment_sports_list 版面配置 XML。
  • onViewCreated() 函式會設定 RecyclerView 轉接介面。這個函式會將使用者選取的運動項目更新為共用 ViewModel (也就是 SportsViewModel) 中的目前運動項目,並導覽至內含運動新聞的詳細資料畫面,使用 submitList(List) 將要顯示的運動清單提交至轉接介面。

NewsDetailsFragment.kt

這是您應用程式的第二個畫面,其中會顯示運動新聞的預留位置文字。

  • onCreateView() 函式會使用繫結物件加載 fragment_sports_news 版面配置 XML。
  • onViewCreated() 函式會在 SportsViewModel 的屬性上附加觀察器 currentSport,在資料有變時自動更新 UI。運動標題、圖片和新聞會在觀察器中更新。

建立應用程式並加以執行

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

4. 清單/詳細資料模式

目前的範例應用程式無法充分運用大螢幕裝置 (如平板電腦) 的螢幕空間。為解決這個問題,您稍後會透過本程式碼研究室介紹的「清單詳細資料」模式來顯示應用程式 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」或任何其他類似的硬體設定檔。

8303f9b3e70321eb.png

  1. 按一下「Next」
  2. 在撰寫這個程式碼研究室時,選取最新的系統映像檔 R (API 級別 30)。
  3. 按一下「Next」
  4. 您現在可以選擇是否要重新命名虛擬裝置,這不是必須選項。
  5. 按一下「Finish」
  6. 系統會將您帶回「Android Virtual Device Manager」視窗。在新建的虛擬裝置旁邊按一下啟動圖示 38752506de85d293.png
  7. 這應會啟動具平板電腦設定檔的模擬器。請耐心等候,這可能需要一點時間。
  8. 關閉「Android Virtual Device Manager」視窗。
  9. 在新建的模擬器上執行運動應用程式。

200e209de7a2f0ad.png

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

以下是清單-詳細資料模式的範例圖片:

71698910dd129a91.png

如上所示,清單詳細資料模式會在左側顯示項目清單,在右側則會顯示所選項目的詳細資料。

同理,如果您在運動應用程式中使用上述模式,則新聞片段將成為詳細資料畫面。

51c9542717d2f875.png

在本程式碼研究室中,您將瞭解如何使用 SlidingPaneLayout 實作清單/詳細資料 UI。

5. SlidingPaneLayout 模式

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

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

b0a205de3494e95d.gif

請注意詳細資料窗格在小螢幕上滑過清單窗格的方式。

下方圖片說明 SlidingPaneLayout 在小螢幕上的顯示方式。請在清單項目處於選取狀態時,觀察詳細資料窗格與清單窗格如何重疊,才能讓這兩個窗格始終存在!

e26f94d9579b6121.png

471b0b38d4dfa95a.png

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

6. 新增程式庫依附元件

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

7. 設定體育清單片段的 XML

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

在運動應用程式中,第一個窗格會是顯示運動清單的 RecyclerView,第二個窗格則顯示運動新聞。

新增 SlidingPaneLayout

  1. 開啟 fragment_sports_list.xml。請注意根版面配置為 FrameLayout
  2. FrameLayout 變更為 androidx.slidingpanelayout.widget.SlidingPaneLayout.
<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 的值。
<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 這個屬性值。請注意,您稍後會更新這些值。
<androidx.fragment.app.FragmentContainerView
   android:layout_height="match_parent"
   android:layout_width="match_parent"/>
  1. FragmentContainerView 中加入 android:id 屬性,並指定一個 @+id/detail_container 的值。
android:id="@+id/detail_container"
  1. 使用 android:name 屬性在 FragmentContainerView 中新增 NewsDetailsFragment
android:name="com.example.android.sports.NewsDetailsFragment"

更新 layout_width 屬性

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

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

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

寬度

中斷點

裝置佔比

精簡寬度

< 600dp

99.96% 直向模式的手機

中等寬度

600dp+

93.73% 以 portraitLarge 顯示的平板電腦,會以直向展開內部的顯示項目

展開寬度

840dp+

97.22% 以 landscapeLarge 顯示的平板電腦,會橫向展開內部的顯示項目

a247a843310d061a.png

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

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

<androidx.fragment.app.FragmentContainerView
   ...
   android:layout_width="300dp"
   .../>
  1. 分別在採用平板電腦設定檔和手機設定檔的模擬器上執行應用程式。

ad148a96d7487e66.png

請注意,平板電腦會顯示兩個窗格。您將在後續步驟中將平板電腦上的第二個窗格設為固定寬度。

  1. 具手機設定檔的模擬器上執行應用程式。

a6be6d199d2975ac.png

新增 layout_weight

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

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

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

ce3a93fe501ee5dc.png

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

8. 替換詳細資料窗格

在具平板電腦設定檔的模擬器上執行應用程式。從運動清單中選取清單項目。請注意,應用程式會導航至詳細資料窗格。

8fedee8d4837909.png

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

  1. SportsListFragment 檔案的 onViewCreated() 函式中,找到以下程式行以前往詳細資料畫面。
// Navigate to the details screen
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
  1. 使用下列程式碼取代以上這幾行:
binding.slidingPaneLayout.openPane()

SlidingPaneLayout 呼叫 openPane(),將第一個窗格切換至第二個窗格。如果兩個窗格皆處於顯示狀態 (例如在平板電腦上),這項操作不會造成任何可見的影響。

  1. 在平板電腦和手機模擬器上執行應用程式。請注意,雙窗格內容會不斷正確更新。

b0d3c8c263be15f8.png

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

9. 新增自訂返回導覽功能

小型裝置上的清單和詳細資料窗格會重疊,因此您必須確保系統返回按鈕可讓使用者從詳細資料窗格返回清單窗格。方法是 提供自訂返回導覽功能,並將 OnBackPressedCallback 連結到 SlidingPaneLayout 目前的狀態。

返回導覽

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

自訂返回導覽

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

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

同樣地,您必須在 SlidingPaneLayout 中提供自訂的返回導覽功能,將應用程式從詳細資料窗格導航回清單窗格。

導入自訂的返回導覽功能

如要在 Sports 應用程式中實作自訂返回導覽,需要執行以下作業:

  • 定義自訂回呼來處理返回鍵按鍵動作,這會覆寫 OnBackPressedCallback
  • 註冊並新增回呼例項。

首先,請定義自訂回呼。

  1. SportsListFragment 檔案中的 SportsListFragment 類別定義下方新增類別。將其命名為 SportsListOnBackPressedCallback
  2. 傳入 SlidingPaneLayoutprivate 例項做為建構函式參數。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
)
  1. 透過 OnBackPressedCallback 擴充類別。OnBackPressedCallback 類別會處理 onBackPressed 回呼。您會在不久之後修正這項建構函式參數錯誤。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback()

OnBackPressedCallback 的建構函式會以布林值表示初始啟用狀態。只有在已啟用回呼的情況下 (即 isEnabled() 傳回 true),分派器才會呼叫回呼的 handleOnBackPressed() 來處理返回按鈕事件。

  1. slidingPaneLayout.isSlideable* && slidingPaneLayout.isOpen* 做為建構函式參數傳入 OnBackPressedCallback只有在第二個窗格可滑動的情況下,布林值 isSlideable 才會為 true;這種情況可能會發生在顯示單一窗格的小螢幕裝置上。如果第二個窗格 (內容窗格) 完全開啟,則 isOpen 的值將為 true
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen)

這段程式碼可確保只有在小螢幕裝置上且內容窗格開啟的情況下,才會啟用這個回呼。

  1. 如要修正未實作方法造成的錯誤,請按一下紅色燈泡圖示 5fdf362480bfe665.png,然後選取「Implement members」
  2. 在「Implement members」彈出式視窗中按一下「OK」,覆寫 handleOnBackPressed 方法。

您的類別應如下所示:

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 陳述式,然後新增下列程式碼來關閉內容窗格並返回清單窗格。
slidingPaneLayout.closePane()

監控 SlidingPaneLayout 的事件

除了處理返回事件外,您也必須監聽及監控與滑動窗格相關的事件。內容窗格滑動時,應視滑動情況啟用或停用回呼。您將使用 PanelSlideListener 進行這項操作。

SlidingPaneLayout.PanelSlideListener 介面包含三個抽象方法:onPanelSlide()onPanelOpened()onPanelClosed()。詳細資料窗格滑動、開啟及關閉時,系統會呼叫這些方法。

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

ad52135eecbee09f.png

  1. 您的 SportsListOnBackPressedCallback 類別應如下所示:
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. 在詳細資料窗格開啟 (可見) 時,啟用 OnBackPressedCallback 回呼。實際做法為呼叫 setEnabled() 函式並傳入 true。在 onPanelOpened() 中編寫以下程式碼:
setEnabled(true)
  1. 使用屬性存取語法可以簡化上述的程式碼。
override fun onPanelOpened(panel: View) {
   isEnabled = true
}
  1. 同樣地,當詳細資料窗格關閉時,請將「isEnabled 設為 false
override fun onPanelClosed(panel: View) {
   isEnabled = false
}
  1. 如要完成回呼,最後一個步驟是將 SportsListOnBackPressedCallback 事件監聽器類別新增至事件監聽器清單中,該清單將會收到詳細資料窗格滑動事件的通知。將 init 區塊新增至 SportsListOnBackPressedCallback 類別。在 init 區塊內,呼叫 slidingPaneLayout.addPanelSlideListener() 以傳入 this
init {
   slidingPaneLayout.addPanelSlideListener(this)
}

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

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

註冊回呼

如要查看回呼的實際效果,請使用分派器 OnBackPressedDispatcher 註冊回呼。

FragmentActivity 的基礎類別可讓您透過 OnBackPressedDispatcher 控制返回按鈕的行為。OnBackPressedDispatcher 可控管系統如何將返回按鈕事件分派到一或多個 OnBackPressedCallback 物件。

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

addCallback() 方法也會採用將回呼類別做為第二個參數的例項。請按照下列步驟註冊回呼:

  1. SportsListFragment 檔案的 onViewCreated() 函式中,於繫結變數宣告的下方為 SlidingPaneLayout 建立例項,並為其指派 binding.slidingPaneLayout 的值。
val slidingPaneLayout = binding.slidingPaneLayout
  1. SportsListFragment 檔案的 onViewCreated() 函式中,緊接在 slidingPaneLayout 宣告下方,新增下列程式碼:
// Connect the SlidingPaneLayout to the system back button.
requireActivity().onBackPressedDispatcher.addCallback(
   viewLifecycleOwner,
   SportsListOnBackPressedCallback(slidingPaneLayout)
)

上述程式碼使用了 addCallback(),並傳入 viewLifecycleOwnerSportsListOnBackPressedCallback 的例項。這個回呼的有效期間僅限於片段的生命週期。

  1. 您現在可以在具手機設定檔的模擬器上執行應用程式,看看自訂返回按鈕的實際運作情形。

33967fa8fde5b902.gif

10. 鎖定模式

根據預設,在手機等小螢幕裝置上,當清單和詳細資料窗格重疊時,使用者可以左右滑動,不必使用手勢操作即可切換這兩個窗格。您可以設定 SlidingPaneLayout 的鎖定模式,鎖定或解鎖詳細資料窗格。

  1. 在具手機設定檔的模擬器中,嘗試將詳細資料窗格在畫面中滑開。
  2. 你也可以在詳細資料窗格中滑動,請自行嘗試。
  3. Sports 應用程式中,您不必使用這項功能。建議您鎖定 SlidingPaneLayout,防止使用者透過手勢向內及向外滑動。如要實作這項功能,請在 onViewCreated() 方法的 slidingPaneLayout 定義下方,將 lockMode 設為 LOCK_MODE_LOCKED
slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED

如要進一步瞭解其他鎖定模式,請參閱說明文件

  1. 再次執行應用程式,注意詳細資料窗格現已鎖定。

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

11. 解決方案程式碼

本程式碼研究室的解決方案程式碼位於下方顯示的專案和模組中。

  1. 前往專案所在的 GitHub 存放區頁面。
  2. 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

1e4c0d2c081a8fd2.png

  1. 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。

1debcf330fd04c7b.png

  1. 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
  2. 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
  3. 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open」

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。

8d1fda7396afe8e5.png

  1. 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 8de56cba7583251f.png,即可建構並執行應用程式。請確認應用程式的建構作業符合預期。

12. 瞭解詳情