ทุกหน้าจอในแอปต้องปรับเปลี่ยนตามอุปกรณ์และปรับให้เข้ากับพื้นที่ว่างที่มีอยู่
คุณสามารถสร้าง UI ที่ปรับเปลี่ยนตามอุปกรณ์ได้ด้วย
ConstraintLayout
ที่ช่วยให้ช่องเดียว
ปรับขนาดให้พอดีกับหลายขนาด แต่อุปกรณ์ที่ใหญ่กว่าอาจได้รับประโยชน์จากการแยก
เลย์เอาต์ให้เป็นช่องหลายช่อง เช่น คุณอาจต้องการให้หน้าจอแสดง
รายการถัดจากรายการรายละเอียดของรายการที่เลือก
SlidingPaneLayout
คอมโพเนนต์รองรับการแสดงหน้าต่าง 2 ช่องเคียงข้างกันในอุปกรณ์ขนาดใหญ่และ
อุปกรณ์แบบพับได้ขณะปรับอัตโนมัติเพื่อแสดงเพียงแผงเดียวต่อครั้ง
อุปกรณ์ขนาดเล็ก เช่น โทรศัพท์
ดูคำแนะนำเฉพาะอุปกรณ์ได้ที่ ภาพรวมความเข้ากันได้ของหน้าจอ
ตั้งค่า
หากต้องการใช้ SlidingPaneLayout
ให้ใส่ทรัพยากร Dependency ต่อไปนี้ในแอป
ไฟล์ build.gradle
:
Groovy
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
การกำหนดค่ารูปแบบ XML
SlidingPaneLayout
มีเลย์เอาต์แนวนอนแบบ 2 แผงไว้ด้านบน
ระดับ UI เลย์เอาต์นี้ใช้แผงแรกเป็นรายการเนื้อหาหรือเบราว์เซอร์
ย่อยไปยังมุมมองรายละเอียดหลักสำหรับการแสดงเนื้อหาในแผงอื่น
SlidingPaneLayout
ใช้ความกว้างของช่อง 2 ช่องเพื่อระบุว่าจะแสดงหรือไม่
ทีละหน้าต่าง ตัวอย่างเช่น หากวัดแผงรายการให้มีพารามิเตอร์
ขนาดขั้นต่ำ 200 dp และแผงรายละเอียดต้องมีขนาด 400 dp จากนั้น
SlidingPaneLayout
จะแสดงหน้าต่าง 2 แผงเคียงข้างกันโดยอัตโนมัติ
มีความกว้างอย่างน้อย 600 dp
มุมมองย่อยจะทับซ้อนกัน หากความกว้างรวมเกินความกว้างที่ใช้ได้ใน
SlidingPaneLayout
ในกรณีนี้ มุมมองย่อยจะขยายเพื่อเติม
ความกว้างของ SlidingPaneLayout
ผู้ใช้สามารถเลื่อนมุมมองด้านบนสุดออกจาก
โดยลากกลับไปจากขอบของหน้าจอ
หากมุมมองไม่ซ้อนทับกัน SlidingPaneLayout
จะรองรับการใช้งานเลย์เอาต์
พารามิเตอร์ layout_weight
ในมุมมองย่อยเพื่อกำหนดวิธีแบ่งพื้นที่ว่างที่เหลือ
หลังจากการวัดเสร็จสมบูรณ์ พารามิเตอร์นี้เกี่ยวข้องกับความกว้างเท่านั้น
บนอุปกรณ์แบบพับได้ที่มีพื้นที่บนหน้าจอแสดงมุมมองทั้ง 2 ข้างพร้อมกัน
SlidingPaneLayout
จะปรับขนาดช่อง 2 ช่องโดยอัตโนมัติเพื่อให้
พวกเขาอยู่ในตำแหน่งใดด้านหนึ่งของแนวพับหรือบานพับที่เหลื่อมซ้อนกัน ด้วยวิธีนี้
ความกว้างที่กำหนดจะถือว่าเป็นความกว้างขั้นต่ำที่ต้องมีในแต่ละ
ของฟีเจอร์พับ ถ้ามีพื้นที่ว่างไม่เพียงพอที่จะเก็บข้อมูลนั้น
ต่ำสุด 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>
ในตัวอย่างนี้ แอตทริบิวต์ android:name
ใน FragmentContainerView
จะเพิ่ม
ส่วนย่อยเริ่มต้นไปยังแผงรายละเอียด เพื่อให้มั่นใจว่าผู้ใช้บนหน้าจอขนาดใหญ่
อุปกรณ์จะไม่เห็นช่องด้านขวาว่างเมื่อแอปเปิดขึ้นเป็นครั้งแรก
สลับแผงรายละเอียดแบบเป็นโปรแกรม
ในตัวอย่าง XML ก่อนหน้านี้ การแตะองค์ประกอบใน RecyclerView
จะทำให้เกิดการเปลี่ยนแปลงในแผงรายละเอียด เมื่อใช้ Fragment จะต้อง
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
การดำเนินการนี้จะหลีกเลี่ยงการสร้าง Back Stack อย่างละเอียด
แผง
การใช้งานคอมโพเนนต์การนำทาง
ตัวอย่างในหน้านี้ใช้ SlidingPaneLayout
โดยตรงและต้องการให้คุณทำดังนี้
จัดการธุรกรรมส่วนย่อยด้วยตนเอง อย่างไรก็ตาม
คอมโพเนนต์การนำทางมีการติดตั้งใช้งานที่สร้างไว้ล่วงหน้า
เลย์เอาต์แบบ 2 แผง
AbstractListDetailFragment
คลาส API ที่ใช้ SlidingPaneLayout
ขั้นสูงเพื่อจัดการรายการ
และแผงรายละเอียด
ซึ่งจะช่วยให้คุณกำหนดค่าเลย์เอาต์ XML ได้ง่ายขึ้น แทนที่จะแสดงอย่างชัดแจ้ง
การประกาศ SlidingPaneLayout
และแผงทั้ง 2 ช่อง เลย์เอาต์ของคุณก็ต้องการเพียง
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(); }
ปลายทางในกราฟการนำทางของแผงรายละเอียดต้องไม่ปรากฏใน
กราฟการนำทางด้านนอกทั้งแอป อย่างไรก็ตาม Deep Link ใดก็ตามที่อยู่ในรายละเอียด
กราฟการนำทางของแผงต้องแนบอยู่กับปลายทางที่โฮสต์
SlidingPaneLayout
วิธีนี้จะช่วยให้มั่นใจได้ว่า Deep Link ภายนอกจะไปยังส่วนต่างๆ ได้
ไปยังปลายทาง SlidingPaneLayout
จากนั้นไปยังรายละเอียดที่ถูกต้อง
ปลายทางของแผง
โปรดดู ตัวอย่าง TwoPaneFragment สำหรับการใช้งานการออกแบบแบบ 2 แผงอย่างเต็มรูปแบบโดยใช้คอมโพเนนต์การนำทาง
ผสานรวมกับปุ่มย้อนกลับของระบบ
สำหรับอุปกรณ์ขนาดเล็กที่รายการและแผงรายละเอียดซ้อนทับกัน โปรดตรวจสอบว่าระบบ
ปุ่มย้อนกลับจะนําผู้ใช้จากแผงรายละเอียดกลับไปยังแผงรายการ ดำเนินการ
โดยการระบุด้านหลังที่กำหนดเอง
การนำทางและการเชื่อมต่อ
OnBackPressedCallback
ถึง
สถานะปัจจุบันของ SlidingPaneLayout
:
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); } }
คุณสามารถเพิ่ม Callback ลงในส่วน
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()
เพื่อเปลี่ยนไปมาระหว่างแผงรายการกับแผงรายละเอียดในโทรศัพท์ วิธีการเหล่านี้ไม่มี
จะมีผลหากมองเห็นแผงทั้ง 2 แผงและไม่ทับซ้อนกัน
เมื่อรายการและแผงรายละเอียดทับซ้อนกัน ผู้ใช้จะสามารถปัดไปในทั้ง 2 ทิศทางได้โดย
ตั้งค่าเริ่มต้น สามารถสลับระหว่างช่องสองช่องได้อย่างอิสระแม้ว่าจะไม่ได้ใช้ท่าทางสัมผัส
การนำทาง คุณควบคุมทิศทางการปัดได้
โดยการตั้งค่าโหมดล็อกของ SlidingPaneLayout
ดังนี้
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการออกแบบเลย์เอาต์สำหรับอุปกรณ์รูปแบบต่างๆ ได้ที่ เอกสารประกอบต่อไปนี้