建立雙窗格版面配置

試用 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用版面配置。

應用程式中的每個畫面都必須是回應式內容,並配合可用空間進行調整。 您可以使用以下項目,建構回應式 UI: ConstraintLayout,可允許單一窗格 但大型裝置可能會適合 將該版面配置分成多個窗格例如,您可能希望螢幕上 的項目清單。

SlidingPaneLayout敬上 元件能在大型裝置上並排顯示兩個窗格,且 折疊式裝置則會自動調整,在開啟時一次只顯示一個窗格 若是手機等小型裝置

如需裝置適用的指引,請參閱 螢幕相容性總覽

設定

如要使用 SlidingPaneLayout,請在應用程式的 build.gradle 檔案:

Groovy

dependencies {
    implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0"
}

Kotlin

dependencies {
    implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
}

XML 版面配置設定

SlidingPaneLayout 提供水平的雙窗格版面配置,可在頂端使用 UI 層級的政策這個版面配置使用第一個窗格做為內容清單或瀏覽器。 歸入主要詳細資料檢視畫面,以在另一個窗格中顯示內容。

顯示 SlidingPaneLayout 範例的圖片
圖 1. 使用以下程式碼建立的版面配置範例: SlidingPaneLayout

SlidingPaneLayout 會依據兩個窗格的寬度來判斷是否要顯示 這兩個窗格舉例來說,如果經過測量後,清單窗格會有 大小下限為 200 dp,詳細資料窗格需要 400 dp, SlidingPaneLayout 會自動並排顯示兩個窗格,只要 可用寬度至少為 600 dp。

如果子項檢視畫面的組合寬度超過 SlidingPaneLayout。在本範例中,子項檢視畫面會展開來填滿可用的空間 SlidingPaneLayout 中的寬度。使用者可以將最上方的檢視畫面滑出 將焦點從螢幕邊緣往回拖曳

如果檢視畫面不會重疊,SlidingPaneLayout 支援使用版面配置 子檢視畫面上的 layout_weight 參數,可定義如何劃分剩餘空間 評估完成後這個參數僅適用於寬度。

使用摺疊式裝置時,如果螢幕空間會並排顯示兩個檢視畫面 SlidingPaneLayout 會自動調整兩個窗格的大小 放置在重疊摺疊或轉軸的任一側。在本 設定寬度時,會將每個 Pod 的寬度視為規定的最小寬度 就很適合使用摺疊功能如果沒有足夠的空間進行維護 最小尺寸,SlidingPaneLayout 會切換回重疊的檢視畫面。

以下範例使用 SlidingPaneLayout, 將 RecyclerView 設為 左側窗格 FragmentContainerView 做為主要詳細資料檢視畫面,以顯示左側窗格中的內容:

<!-- two_pane.xml -->
<androidx.slidingpanelayout.widget.SlidingPaneLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/sliding_pane_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <!-- The first child view becomes the left pane. When the combined needed
        width, expressed using android:layout_width, doesn't fit on-screen at
        once, the right pane is permitted to overlap the left. -->

   <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/list_pane"
             android:layout_width="280dp"
             android:layout_height="match_parent"
             android:layout_gravity="start"/>

   <!-- The second child becomes the right (content) pane. In this example,
        android:layout_weight is used to expand this detail pane to consume
        leftover available space when the entire window is wide enough to fit
        the left and right pane.-->
   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/detail_container"
       android:layout_width="300dp"
       android:layout_weight="1"
       android:layout_height="match_parent"
       android:background="#ff333333"
       android:name="com.example.SelectAnItemFragment" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

在此範例中,FragmentContainerView 上的 android:name 屬性會新增 最初的片段連結到詳細資料窗格,確保使用者是大螢幕 裝置首次啟動時,裝置不會看到空白的右側窗格。

透過程式化方式替換詳細資料窗格

在上述 XML 範例中,輕觸 RecyclerView 中的元素 觸發詳細資料窗格中的變更。使用片段時,這需要 FragmentTransaction敬上 稱為 open()SlidingPaneLayout 上並切換到新可見的片段:

Kotlin

// A method on the Fragment that owns the SlidingPaneLayout,called by the
// adapter when an item is selected.
fun openDetails(itemId: Int) {
    childFragmentManager.commit {
        setReorderingAllowed(true)
        replace<ItemFragment>(R.id.detail_container,
            bundleOf("itemId" to itemId))
        // If it's already open and the detail pane is visible, crossfade
        // between the fragments.
        if (binding.slidingPaneLayout.isOpen) {
            setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
        }
    }
    binding.slidingPaneLayout.open()
}

Java

// A method on the Fragment that owns the SlidingPaneLayout, called by the
// adapter when an item is selected.
void openDetails(int itemId) {
    Bundle arguments = new Bundle();
    arguments.putInt("itemId", itemId);
    FragmentTransaction ft = getChildFragmentManager().beginTransaction()
            .setReorderingAllowed(true)
            .replace(R.id.detail_container, ItemFragment.class, arguments);
    // If it's already open and the detail pane is visible, crossfade
    // between the fragments.
    if (binding.getSlidingPaneLayout().isOpen()) {
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    }
    ft.commit();
    binding.getSlidingPaneLayout().open();
}

這段程式碼不會呼叫 addToBackStack()敬上 在 FragmentTransaction。這樣就能避免詳細建構返回堆疊 窗格。

本頁中的範例直接使用 SlidingPaneLayout,並要求您 手動管理片段交易不過, 導覽元件提供預先建構的 雙窗格版面配置 AbstractListDetailFragment、 直接使用 SlidingPaneLayout 管理清單的 API 類別 和詳細資料窗格

這可讓您簡化 XML 版面配置設定。而不是明確 如果宣告 SlidingPaneLayout 和兩個窗格,版面配置只需要 請FragmentContainerView以保留AbstractListDetailFragment 實作:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/two_pane_container"
        <!-- The name of your AbstractListDetailFragment implementation.-->
        android:name="com.example.testapp.TwoPaneFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        <!-- The navigation graph for your detail pane.-->
        app:navGraph="@navigation/two_pane_navigation" />
</FrameLayout>

導入設定 onCreateListPaneView()敬上 和 onListPaneViewCreated()。 為清單窗格提供自訂檢視畫面。在詳細資料窗格中 AbstractListDetailFragment使用 NavHostFragment。 也就是說,您可以定義導覽 圖表,其中只包含 顯示在詳細資料窗格中的目的地。接著,您就能 使用 NavController替換 在獨立導覽圖中,與目的地之間的詳細資料窗格:

Kotlin

fun openDetails(itemId: Int) {
    val navController = navHostFragment.navController
    navController.navigate(
        // Assume the itemId is the android:id of a destination in the graph.
        itemId,
        null,
        NavOptions.Builder()
            // Pop all destinations off the back stack.
            .setPopUpTo(navController.graph.startDestination, true)
            .apply {
                // If it's already open and the detail pane is visible,
                // crossfade between the destinations.
                if (binding.slidingPaneLayout.isOpen) {
                    setEnterAnim(R.animator.nav_default_enter_anim)
                    setExitAnim(R.animator.nav_default_exit_anim)
                }
            }
            .build()
    )
    binding.slidingPaneLayout.open()
}

Java

void openDetails(int itemId) {
    NavController navController = navHostFragment.getNavController();
    NavOptions.Builder builder = new NavOptions.Builder()
            // Pop all destinations off the back stack.
            .setPopUpTo(navController.getGraph().getStartDestination(), true);
    // If it's already open and the detail pane is visible, crossfade between
    // the destinations.
    if (binding.getSlidingPaneLayout().isOpen()) {
        builder.setEnterAnim(R.animator.nav_default_enter_anim)
                .setExitAnim(R.animator.nav_default_exit_anim);
    }
    navController.navigate(
        // Assume the itemId is the android:id of a destination in the graph.
        itemId,
        null,
        builder.build()
    );
    binding.getSlidingPaneLayout().open();
}

詳細資料窗格導覽圖表中的目的地「不得」顯示在 。不過詳細資料中包含任何深層連結 窗格的導覽圖必須附加至代管 SlidingPaneLayout。這有助於確保外部深層連結先瀏覽 前往 SlidingPaneLayout 目的地,然後前往正確的詳細資料 窗格目的地

詳情請參閱 TwoPaneFragment 範例 瞭解使用 Navigation 元件的完整實作雙窗格版面配置。

與系統返回按鈕整合

在清單和詳細資料窗格重疊的小型裝置上,確認系統 返回按鈕會將使用者從詳細資料窗格移回清單窗格。建議做法 並提供自訂回饋 導航並連接 OnBackPressedCallbackSlidingPaneLayout 目前的狀態:

Kotlin

class TwoPaneOnBackPressedCallback(
    private val slidingPaneLayout: SlidingPaneLayout
) : OnBackPressedCallback(
    // Set the default 'enabled' state to true only if it is slidable, such as
    // when the panes overlap, and open, such as when the detail pane is
    // visible.
    slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen
), SlidingPaneLayout.PanelSlideListener {

    init {
        slidingPaneLayout.addPanelSlideListener(this)
    }

    override fun handleOnBackPressed() {
        // Return to the list pane when the system back button is tapped.
        slidingPaneLayout.closePane()
    }

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

    override fun onPanelOpened(panel: View) {
        // Intercept the system back button when the detail pane becomes
        // visible.
        isEnabled = true
    }

    override fun onPanelClosed(panel: View) {
        // Disable intercepting the system back button when the user returns to
        // the list pane.
        isEnabled = false
    }
}

Java

class TwoPaneOnBackPressedCallback extends OnBackPressedCallback
        implements SlidingPaneLayout.PanelSlideListener {

    private final SlidingPaneLayout mSlidingPaneLayout;

    TwoPaneOnBackPressedCallback(@NonNull SlidingPaneLayout slidingPaneLayout) {
        // Set the default 'enabled' state to true only if it is slideable, such
        // as when the panes overlap, and open, such as when the detail pane is
        // visible.
        super(slidingPaneLayout.isSlideable() && slidingPaneLayout.isOpen());
        mSlidingPaneLayout = slidingPaneLayout;
        slidingPaneLayout.addPanelSlideListener(this);
    }

    @Override
    public void handleOnBackPressed() {
        // Return to the list pane when the system back button is tapped.
        mSlidingPaneLayout.closePane();
    }

    @Override
    public void onPanelSlide(@NonNull View panel, float slideOffset) { }

    @Override
    public void onPanelOpened(@NonNull View panel) {
        // Intercept the system back button when the detail pane becomes
        // visible.
        setEnabled(true);
    }

    @Override
    public void onPanelClosed(@NonNull View panel) {
        // Disable intercepting the system back button when the user returns to
        // the list pane.
        setEnabled(false);
    }
}

您可以將回呼新增至 OnBackPressedDispatcher敬上 使用 addCallback():

Kotlin

class TwoPaneFragment : Fragment(R.layout.two_pane) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = TwoPaneBinding.bind(view)

        // Connect the SlidingPaneLayout to the system back button.
        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
            TwoPaneOnBackPressedCallback(binding.slidingPaneLayout))

        // Set up the RecyclerView adapter.
    }
}

Java

class TwoPaneFragment extends Fragment {

    public TwoPaneFragment() {
        super(R.layout.two_pane);
    }

    @Override
    public void onViewCreated(@NonNull View view,
             @Nullable Bundle savedInstanceState) {
        TwoPaneBinding binding = TwoPaneBinding.bind(view);

        // Connect the SlidingPaneLayout to the system back button.
        requireActivity().getOnBackPressedDispatcher().addCallback(
            getViewLifecycleOwner(),
            new TwoPaneOnBackPressedCallback(binding.getSlidingPaneLayout()));

        // Set up the RecyclerView adapter.
    }
}

鎖定模式

SlidingPaneLayout 可讓你手動呼叫 open()close()敬上 在手機上切換清單窗格和詳細資料窗格。這些方法沒有 。

清單和詳細資料窗格重疊時,使用者可以左右滑動, 預設情況下,即使不使用手勢,也可以自由切換這兩個窗格 導覽頁面。你可以控制滑動方向 設定 SlidingPaneLayout 的鎖定模式:

Kotlin

binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED

Java

binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);

瞭解詳情

如要進一步瞭解如何為不同的板型規格設計版面配置,請參閱 以下文件:

其他資源