يجب أن تكون كل شاشة في تطبيقك سريعة الاستجابة وأن تتكيف مع المساحة المتاحة.
يمكنك إنشاء واجهة مستخدم متجاوبة مع ConstraintLayout
تتيح ضبط حجم يتناسب مع جزء واحد مع أحجام عديدة، ولكن قد تستفيد الأجهزة الأكبر حجمًا من تقسيم التصميم إلى أجزاء متعدّدة. على سبيل المثال، قد ترغب في أن تعرض الشاشة
قائمة بالعناصر بجوار قائمة تفاصيل العنصر المحدد.
يتيح المكوِّن
SlidingPaneLayout
عرض جزأين جنبًا إلى جنب على الأجهزة الكبيرة
والأجهزة القابلة للطي مع تعديلهما تلقائيًا لعرض جزء واحد فقط في كل مرة على
الأجهزة الأصغر حجمًا، مثل الهواتف.
للحصول على إرشادات خاصة بجهاز محدّد، راجِع نظرة عامة على توافق الشاشة.
ضبط إعدادات الجهاز
لاستخدام ميزة SlidingPaneLayout
، يجب تضمين الاعتمادية التالية في ملف build.gradle
الخاص بتطبيقك:
رائع
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
ضبط تنسيق XML
SlidingPaneLayout
توفر تخطيطًا أفقياً من جزأين للاستخدام في المستوى الأعلى
من واجهة المستخدم. يستخدم هذا التخطيط الجزء الأول كقائمة محتوى أو متصفح، تابع لعرض التفاصيل الأساسي لعرض المحتوى في الجزء الآخر.
يستخدم SlidingPaneLayout
عرض الجزءين لتحديد ما إذا كان سيتم عرض الأجزاء جنبًا إلى جنب. على سبيل المثال، إذا تم قياس حجم جزء القائمة بحد أدنى يبلغ 200 بكسل مستقل الكثافة ويحتاج جزء التفاصيل إلى 400 بكسل مستقل الكثافة، سيعرض
SlidingPaneLayout
الجزءَين جنبًا إلى جنب تلقائيًا طالما أنّ عرضهما لا يقل عن 600 وحدة بكسل مستقلة الكثافة.
تتداخل طرق العرض الثانوية إذا كان عرضها المجمّع يتجاوز العرض المتاح في SlidingPaneLayout
. في هذه الحالة، يتم توسيع طرق العرض الفرعية لملء العرض
المتاح في SlidingPaneLayout
. يمكن للمستخدم تمرير العرض العلوي بعيدًا
عن طريق سحبه مرة أخرى من حافة الشاشة.
إذا لم تتداخل طرق العرض، تتيح SlidingPaneLayout
استخدام معلَمة التنسيق layout_weight
في الملفات الشخصية الثانوية لتحديد كيفية تقسيم المساحة المتبقية
بعد اكتمال القياس. هذه المَعلمة مرتبطة بالعرض فقط.
في الجهاز القابل للطي الذي يتضمّن مساحة على الشاشة لإظهار كلتا الوجهتين
جنبًا إلى جنب، يضبط "SlidingPaneLayout
" حجم الجزأين تلقائيًا
ليتم وضعهما على أيّ من جانبَي الطيّ أو المفصّلة المتداخلة. في هذه الحالة، تُعد قيم العرض المجموعة الحد الأدنى للعرض الذي يجب أن يكون موجودًا على كل جانب من جوانب ميزة الطي. وإذا لم تكن هناك مساحة كافية للحفاظ على هذا
الحد الأدنى للحجم، سيعود 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
إلى إجراء تغيير في جزء التفاصيل. عند استخدام الأجزاء، يتطلب ذلك إدراج رمز 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
ضمن التفاصيل لإدارة القائمة وأجزاء التفاصيل.
يتيح لك هذا الإجراء تبسيط ضبط تنسيق 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 لمعرفة تنفيذ كامل لتنسيق من لوحتين باستخدام مكوِّن التنقل.
الدمج مع زرّ الرجوع في النظام
على الأجهزة الأصغر حيث تتداخل أجزاء القائمة والتفاصيل، تأكد من أن زر الرجوع في النظام يعيد المستخدم من جزء التفاصيل إلى جزء القائمة. ويمكنك إجراء ذلك
من خلال توفير انتقال مخصّص
للخلف وربط
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); } }
يمكنك إضافة رقم معاودة الاتصال إلى
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);
مزيد من المعلومات
لمعرفة المزيد حول تصميم التخطيطات لمختلف أشكال الأجهزة، راجع الوثائق التالية: