هر صفحه در برنامه شما باید پاسخگو باشد و با فضای موجود سازگار باشد. میتوانید با ConstraintLayout
یک رابط کاربری پاسخگو بسازید که به یک رویکرد تکپنجره اجازه میدهد تا اندازههای زیادی را مقیاسبندی کند، اما دستگاههای بزرگتر ممکن است از تقسیم طرحبندی به چندین صفحه سود ببرند. برای مثال، ممکن است بخواهید یک صفحه نمایش لیستی از موارد را در کنار لیستی از جزئیات مورد انتخاب شده نشان دهد.
مؤلفه SlidingPaneLayout
از نمایش دو صفحه در کنار هم در دستگاههای بزرگتر و تاشوها پشتیبانی میکند، در حالی که بهطور خودکار برای نشان دادن تنها یک صفحه در یک زمان در دستگاههای کوچکتر مانند تلفنها، سازگار میشود.
برای راهنمایی های خاص دستگاه، به نمای کلی سازگاری صفحه نمایش مراجعه کنید.
راه اندازی
برای استفاده از SlidingPaneLayout
، وابستگی زیر را در فایل build.gradle
برنامه خود قرار دهید:
شیار
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
کاتلین
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
پیکربندی طرح XML
SlidingPaneLayout
یک طرح افقی و دو صفحه ای را برای استفاده در سطح بالای رابط کاربری فراهم می کند. این طرحبندی از اولین پنجره بهعنوان فهرست محتوا یا مرورگر استفاده میکند، که تابع نمای جزئیات اولیه برای نمایش محتوا در صفحه دیگر است.
![تصویری که نمونه ای از SlidingPaneLayout را نشان می دهد](https://developer.android.com/static/images/ui/slidingpanelayout-example.png?hl=fa)
SlidingPaneLayout
. SlidingPaneLayout
از عرض دو پنجره استفاده می کند تا مشخص کند که آیا پنجره ها در کنار هم نشان داده شوند یا خیر. به عنوان مثال، اگر اندازه صفحه لیست حداقل 200 dp باشد و صفحه جزئیات به 400 dp نیاز دارد، SlidingPaneLayout
به طور خودکار دو صفحه را در کنار هم نشان می دهد تا زمانی که حداقل 600 dp عرض داشته باشد.
اگر عرض ترکیبی آنها از عرض موجود در SlidingPaneLayout
بیشتر شود، نماهای کودک همپوشانی دارند. در این مورد، نماهای فرزند برای پر کردن عرض موجود در SlidingPaneLayout
گسترش مییابد. کاربر میتواند با کشیدن آن از لبه صفحه به عقب، بالاترین نمای را از مسیر خارج کند.
اگر نماها با هم تداخل نداشته باشند، SlidingPaneLayout
از استفاده از پارامتر layout 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
فراخوانی میکند تا به قطعه جدید قابل مشاهده تبدیل شود:
کاتلین
// 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() }
جاوا
// 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
استفاده میکنند و از شما میخواهند که تراکنشهای قطعه را به صورت دستی مدیریت کنید. با این حال، مولفه Navigation پیادهسازی از پیش ساختهشده یک طرحبندی دو صفحهای را از طریق AbstractListDetailFragment
، یک کلاس API که از یک 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
برای جابجایی پنجره جزئیات خود بین مقصدهای موجود در نمودار ناوبری مستقل استفاده کنید:
کاتلین
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() }
جاوا
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 ببینید.
با دکمه بازگشت سیستم یکپارچه شوید
در دستگاههای کوچکتری که فهرست و جزییات با هم همپوشانی دارند، مطمئن شوید که دکمه بازگشت سیستم کاربر را از صفحه جزئیات به صفحه فهرست برمیگرداند. این کار را با ارائه پیمایش برگشت سفارشی و اتصال OnBackPressedCallback
به وضعیت فعلی SlidingPaneLayout
انجام دهید:
کاتلین
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 } }
جاوا
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); } }
میتوانید با استفاده از addCallback()
پاسخ تماس را به OnBackPressedDispatcher
اضافه کنید:
کاتلین
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. } }
جاوا
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
می توانید جهت کشیدن انگشت را کنترل کنید:
کاتلین
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
جاوا
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
بیشتر بدانید
برای کسب اطلاعات بیشتر در مورد طراحی طرحبندی برای فاکتورهای مختلف، به مستندات زیر مراجعه کنید:
منابع اضافی
- کد Layouts تطبیقی
- مثال SlidingPaneLayout در GitHub.