একটি টু-প্যান লেআউট তৈরি করুন

কম্পোজ পদ্ধতিটি চেষ্টা করুন
অ্যান্ড্রয়েডের জন্য Jetpack Compose হলো প্রস্তাবিত UI টুলকিট। Compose-এ কীভাবে লেআউট নিয়ে কাজ করতে হয় তা শিখুন।

আপনার অ্যাপের প্রতিটি স্ক্রিন অবশ্যই রেসপন্সিভ হতে হবে এবং উপলব্ধ স্থানের সাথে নিজেকে মানিয়ে নিতে হবে। আপনি ConstraintLayout ব্যবহার করে একটি রেসপন্সিভ UI তৈরি করতে পারেন, যা একটি সিঙ্গেল-পেন পদ্ধতিকে বিভিন্ন আকারে স্কেল করতে দেয়, কিন্তু বড় ডিভাইসগুলোর জন্য লেআউটটিকে একাধিক পেনে ভাগ করলে বেশি সুবিধা হতে পারে। উদাহরণস্বরূপ, আপনি হয়তো একটি স্ক্রিনে আইটেমগুলোর তালিকার পাশে নির্বাচিত আইটেমটির বিস্তারিত বিবরণ দেখাতে চাইতে পারেন।

SlidingPaneLayout কম্পোনেন্টটি বড় ডিভাইস এবং ফোল্ডেবল ফোনে পাশাপাশি দুটি প্যান দেখানোর সুবিধা দেয়, এবং ফোনের মতো ছোট ডিভাইসে স্বয়ংক্রিয়ভাবে একবারে কেবল একটি প্যান দেখানোর জন্য নিজেকে মানিয়ে নেয়।

ডিভাইস-নির্দিষ্ট নির্দেশনার জন্য, স্ক্রিন সামঞ্জস্যতার সংক্ষিপ্ত বিবরণ দেখুন।

সেটআপ

SlidingPaneLayout ব্যবহার করতে, আপনার অ্যাপের build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিটি অন্তর্ভুক্ত করুন:

গ্রোভি

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

কোটলিন

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

এক্সএমএল লেআউট কনফিগারেশন

SlidingPaneLayout একটি UI-এর শীর্ষ স্তরে ব্যবহারের জন্য একটি অনুভূমিক, দুই-পেন বিশিষ্ট লেআউট প্রদান করে। এই লেআউটটি প্রথম পেনটিকে একটি কন্টেন্ট তালিকা বা ব্রাউজার হিসেবে ব্যবহার করে, যা অন্য পেনে কন্টেন্ট প্রদর্শনের জন্য একটি প্রাথমিক ডিটেইল ভিউ-এর অধীনস্থ থাকে।

স্লাইডিংপেনলেআউটের একটি উদাহরণ দেখানো ছবি।
চিত্র ১. SlidingPaneLayout দিয়ে তৈরি একটি লেআউটের উদাহরণ।

SlidingPaneLayout দুটি পেনের প্রস্থ ব্যবহার করে নির্ধারণ করে যে পেন দুটি পাশাপাশি দেখানো হবে কিনা। উদাহরণস্বরূপ, যদি লিস্ট পেনের ন্যূনতম আকার ২০০ ডিপি এবং ডিটেইল পেনের জন্য ৪০০ ডিপি প্রয়োজন হয়, তাহলে SlidingPaneLayout স্বয়ংক্রিয়ভাবে পেন দুটিকে পাশাপাশি দেখাবে, যতক্ষণ পর্যন্ত এর কাছে কমপক্ষে ৬০০ ডিপি প্রস্থ উপলব্ধ থাকে।

চাইল্ড ভিউগুলোর সম্মিলিত প্রস্থ যদি 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>

এই উদাহরণে, FragmentContainerView এর android:name অ্যাট্রিবিউটটি ডিটেইল প্যানে প্রাথমিক ফ্র্যাগমেন্টটি যুক্ত করে, যা নিশ্চিত করে যে বড় স্ক্রিনের ডিভাইসের ব্যবহারকারীরা অ্যাপটি প্রথমবার চালু হওয়ার সময় ডানদিকের প্যানটি খালি দেখতে পাবেন না।

প্রোগ্রামের মাধ্যমে ডিটেইল প্যানটি অদলবদল করুন

পূর্ববর্তী XML উদাহরণে, RecyclerView এর কোনো এলিমেন্টে ট্যাপ করলে ডিটেইল প্যানে একটি পরিবর্তন ঘটে। ফ্র্যাগমেন্ট ব্যবহার করার সময়, এর জন্য একটি FragmentTransaction প্রয়োজন হয় যা ডানদিকের প্যানটিকে প্রতিস্থাপন করে এবং নতুন দৃশ্যমান ফ্র্যাগমেন্টে সোয়াপ করার জন্য SlidingPaneLayout এর open() কল করে:

কোটলিন

// 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();
}

এই কোডটি বিশেষভাবে FragmentTransaction উপর addToBackStack() কল করে না। এর ফলে ডিটেইল প্যানে একটি ব্যাক স্ট্যাক তৈরি হওয়া এড়ানো যায়।

এই পৃষ্ঠার উদাহরণগুলিতে সরাসরি SlidingPaneLayout ব্যবহার করা হয়েছে এবং এর জন্য আপনাকে ম্যানুয়ালি ফ্র্যাগমেন্ট ট্রানজ্যাকশন পরিচালনা করতে হবে। তবে, নেভিগেশন কম্পোনেন্টটি AbstractListDetailFragment মাধ্যমে একটি দুই-পেন লেআউটের পূর্ব-নির্মিত ইমপ্লিমেন্টেশন প্রদান করে। এটি একটি এপিআই ক্লাস যা আপনার লিস্ট এবং ডিটেইল পেনগুলি পরিচালনা করার জন্য অভ্যন্তরীণভাবে একটি SlidingPaneLayout ব্যবহার করে।

এটি আপনাকে আপনার XML লেআউট কনফিগারেশন সহজ করতে সাহায্য করে। একটি SlidingPaneLayout এবং আপনার উভয় প্যানকে স্পষ্টভাবে ঘোষণা করার পরিবর্তে, আপনার লেআউটে AbstractListDetailFragment ইমপ্লিমেন্টেশন ধারণ করার জন্য শুধুমাত্র একটি FragmentContainerView প্রয়োজন:

<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 গন্তব্যে এবং তারপর সঠিক ডিটেইল পেনের গন্তব্যে নেভিগেট করবে।

Navigation কম্পোনেন্ট ব্যবহার করে দুই-পেন লেআউটের সম্পূর্ণ বাস্তবায়নের জন্য TwoPaneFragment উদাহরণটি দেখুন।

সিস্টেমের ব্যাক বাটনের সাথে একীভূত করুন

ছোট ডিভাইসগুলিতে যেখানে লিস্ট এবং ডিটেইল পেইন একে অপরের উপর এসে পড়ে, সেখানে নিশ্চিত করুন যেন সিস্টেমের ব্যাক বাটনটি ব্যবহারকারীকে ডিটেইল পেইন থেকে লিস্ট পেইনে ফিরিয়ে নিয়ে যায়। এটি করার জন্য, কাস্টম ব্যাক নেভিগেশন প্রদান করুন এবং একটি 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);

আরও জানুন

বিভিন্ন ফর্ম ফ্যাক্টরের জন্য লেআউট ডিজাইন সম্পর্কে আরও জানতে, নিম্নলিখিত ডকুমেন্টেশন দেখুন:

অতিরিক্ত সম্পদ