নেস্টেড স্ক্রোলিং মডিফায়ার

নেস্টেড স্ক্রলিং হলো এমন একটি সিস্টেম যেখানে একে অপরের মধ্যে থাকা একাধিক স্ক্রলিং কম্পোনেন্ট একটিমাত্র স্ক্রল জেসচারের প্রতিক্রিয়ায় তাদের স্ক্রলিং ডেল্টা (পরিবর্তন) জানিয়ে একত্রে কাজ করে।

নেস্টেড স্ক্রলিং সিস্টেমটি স্ক্রলযোগ্য এবং শ্রেণিবদ্ধভাবে সংযুক্ত (বেশিরভাগ ক্ষেত্রে একই প্যারেন্ট শেয়ার করার মাধ্যমে) কম্পোনেন্টগুলোর মধ্যে সমন্বয় সাধন করতে দেয়। এই সিস্টেমটি স্ক্রলিং কন্টেইনারগুলোকে সংযুক্ত করে এবং এদের মধ্যে প্রচারিত ও শেয়ার হওয়া স্ক্রলিং ডেল্টাগুলোর সাথে ইন্টারঅ্যাকশনের সুযোগ দেয়।

Compose কম্পোজেবলগুলোর মধ্যে নেস্টেড স্ক্রলিং পরিচালনার জন্য একাধিক উপায় প্রদান করে। নেস্টেড স্ক্রলিং-এর একটি সাধারণ উদাহরণ হলো একটি তালিকার ভেতরে আরেকটি তালিকা, এবং আরও জটিল একটি ক্ষেত্র হলো একটি সংকুচিত হতে থাকা টুলবার

স্বয়ংক্রিয় নেস্টেড স্ক্রোলিং

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

Compose-এর কিছু কম্পোনেন্ট এবং মডিফায়ার—যেমন verticalScroll , horizontalScroll , scrollable , Lazy APIs এবং TextField এর মাধ্যমে স্বয়ংক্রিয় নেস্টেড স্ক্রলিং স্বয়ংক্রিয়ভাবে সমর্থিত এবং প্রদান করা হয়। এর মানে হলো, যখন ব্যবহারকারী নেস্টেড কম্পোনেন্টের কোনো ভেতরের চাইল্ডকে স্ক্রল করেন, তখন পূর্ববর্তী মডিফায়ারগুলো স্ক্রলিং ডেল্টাগুলোকে সেইসব প্যারেন্ট কম্পোনেন্টে পৌঁছে দেয় যেগুলোতে নেস্টেড স্ক্রলিং সাপোর্ট রয়েছে।

নিম্নলিখিত উদাহরণটি এমন একটি কন্টেইনারের ভিতরে থাকা এলিমেন্টগুলো দেখায়, যেটিতেও একটি verticalScroll মডিফায়ার প্রয়োগ করা হয়েছে এবং এলিমেন্টগুলোতেও একটি verticalScroll মডিফায়ার প্রয়োগ করা আছে।

@Composable
private fun AutomaticNestedScroll() {
    val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
    Box(
        modifier = Modifier
            .background(Color.LightGray)
            .verticalScroll(rememberScrollState())
            .padding(32.dp)
    ) {
        Column {
            repeat(6) {
                Box(
                    modifier = Modifier
                        .height(128.dp)
                        .verticalScroll(rememberScrollState())
                ) {
                    Text(
                        "Scroll here",
                        modifier = Modifier
                            .border(12.dp, Color.DarkGray)
                            .background(brush = gradient)
                            .padding(24.dp)
                            .height(150.dp)
                    )
                }
            }
        }
    }
}

দুটি নেস্টেড ভার্টিকাল স্ক্রলিং UI এলিমেন্ট, যা ভেতরের এলিমেন্টের ভিতরে এবং বাইরে থাকা জেসচারের প্রতি সাড়া দেয়।
চিত্র ১। দুটি নেস্টেড ভার্টিকাল স্ক্রলিং UI এলিমেন্ট, যা ভেতরের এলিমেন্টের ভিতরে এবং বাইরে থেকে করা জেসচারে সাড়া দেয়।

nestedScroll মডিফায়ার ব্যবহার করে

একাধিক এলিমেন্টের মধ্যে একটি উন্নত সমন্বিত স্ক্রল তৈরি করার প্রয়োজন হলে, nestedScroll মডিফায়ারটি একটি নেস্টেড স্ক্রলিং হায়ারার্কি সংজ্ঞায়িত করার মাধ্যমে আপনাকে আরও বেশি নমনীয়তা দেয়। পূর্ববর্তী বিভাগে যেমন উল্লেখ করা হয়েছে, কিছু কম্পোনেন্টের বিল্ট-ইন নেস্টেড স্ক্রল সাপোর্ট রয়েছে। তবে, Box বা Column মতো যে সমস্ত কম্পোজেবল কম্পোনেন্ট স্বয়ংক্রিয়ভাবে স্ক্রলযোগ্য নয়, সেগুলির স্ক্রল ডেল্টা নেস্টেড স্ক্রল সিস্টেমে সঞ্চারিত হয় না এবং ডেল্টাগুলি NestedScrollConnection বা প্যারেন্ট কম্পোনেন্ট পর্যন্ত পৌঁছায় না। এর সমাধান করতে, আপনি কাস্টম কম্পোনেন্ট সহ অন্যান্য কম্পোনেন্টগুলিতে এই ধরনের সাপোর্ট প্রদান করার জন্য nestedScroll ব্যবহার করতে পারেন।

নেস্টেড স্ক্রোলিং চক্র

নেস্টেড স্ক্রল সাইকেল হলো স্ক্রল ডেল্টাগুলোর একটি প্রবাহ, যা নেস্টেড স্ক্রলিং সিস্টেমের অংশ এমন সমস্ত কম্পোনেন্ট (বা নোড)-এর মধ্য দিয়ে হায়ারার্কি ট্রি-এর উপর ও নীচে প্রেরণ করা হয়। উদাহরণস্বরূপ, স্ক্রলেবল কম্পোনেন্ট ও মডিফায়ার, অথবা nestedScroll (nestedScroll) ব্যবহারের মাধ্যমে এটি করা হয়।

নেস্টেড স্ক্রোলিং চক্রের পর্যায়গুলি

যখন কোনো স্ক্রোলযোগ্য কম্পোনেন্ট একটি ট্রিগার ইভেন্ট (যেমন, একটি জেসচার) শনাক্ত করে, তখন প্রকৃত স্ক্রোলিং অ্যাকশনটি শুরু হওয়ার আগেই, উৎপন্ন ডেল্টাগুলো নেস্টেড স্ক্রোল সিস্টেমে পাঠানো হয় এবং তিনটি ধাপের মধ্য দিয়ে যায়: প্রি-স্ক্রোল, নোড কনসাম্পশন এবং পোস্ট-স্ক্রোল।

নেস্টেড স্ক্রোলিং চক্রের পর্যায়সমূহ
চিত্র ২. নেস্টেড স্ক্রলিং চক্রের পর্যায়সমূহ।

প্রথম, প্রি-স্ক্রোল পর্যায়ে, যে কম্পোনেন্টটি ট্রিগার ইভেন্ট ডেল্টা গ্রহণ করেছে, সেটি সেই ইভেন্টগুলোকে হায়ারার্কি ট্রি-এর মধ্য দিয়ে উপরের দিকে, অর্থাৎ সর্বোচ্চ প্যারেন্টের কাছে প্রেরণ করবে। এরপর ডেল্টা ইভেন্টগুলো নিচের দিকে ছড়িয়ে পড়বে, যার অর্থ হলো ডেল্টাগুলো একেবারে গোড়ার প্যারেন্ট থেকে নিচের দিকে সেই চাইল্ডের দিকে বাহিত হবে, যে নেস্টেড স্ক্রোল চক্রটি শুরু করেছে।

প্রি-স্ক্রোল পর্যায় - প্রেরণ উপরে
চিত্র ৩. প্রি-স্ক্রোল পর্যায়: ঊর্ধ্বমুখী প্রেরণ।

এর ফলে নেস্টেড স্ক্রল প্যারেন্টরা ( nestedScroll বা স্ক্রলেবল মডিফায়ার ব্যবহার করে গঠিত কম্পোজেবলগুলো) নোডটি নিজে ডেল্টাটি ব্যবহার করার আগেই তা নিয়ে কোনো কাজ করার সুযোগ পায়।

প্রি-স্ক্রোল পর্যায় - বুদবুদ নিচে
চিত্র ৪। প্রাক-স্ক্রোল পর্যায় - বুদবুদ আকারে নিচে নেমে আসা।

নোড ব্যবহারের পর্যায়ে, নোডটি তার প্যারেন্টদের দ্বারা অব্যবহৃত ডেল্টা নিজেই ব্যবহার করে। এই সময়েই স্ক্রোলিং মুভমেন্টটি প্রকৃতপক্ষে সম্পন্ন হয় এবং দৃশ্যমান হয়।

নোড ব্যবহার পর্যায়
চিত্র ৫. নোড ব্যবহার পর্যায়।

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

অবশেষে, স্ক্রোল-পরবর্তী পর্যায়ে, নোডটি নিজে যা গ্রহণ করেনি, তা ব্যবহারের জন্য আবার তার পূর্বপুরুষদের কাছে পাঠিয়ে দেওয়া হবে।

স্ক্রোল-পরবর্তী পর্যায় - প্রেরণ উপরে
চিত্র ৬। স্ক্রল-পরবর্তী পর্যায় - ঊর্ধ্বমুখী প্রেরণ।

পোস্ট-স্ক্রোল পর্যায়টি প্রি-স্ক্রোল পর্যায়ের মতোই কাজ করে, যেখানে যেকোনো অভিভাবক তা গ্রহণ করবেন কি না, সেই সিদ্ধান্ত নিতে পারেন।

স্ক্রোল-পরবর্তী পর্যায় - বুদবুদ নিচে
চিত্র ৭। স্ক্রোল-পরবর্তী পর্যায় - বুদবুদ আকারে নিচে নেমে আসা।

স্ক্রলের মতোই, যখন একটি ড্র্যাগ জেসচার শেষ হয়, তখন ব্যবহারকারীর অভিপ্রায় একটি বেগে রূপান্তরিত হতে পারে যা স্ক্রলযোগ্য কন্টেইনারটিকে ফ্লিং (অ্যানিমেশন ব্যবহার করে স্ক্রল) করতে ব্যবহৃত হয়। ফ্লিংও নেস্টেড স্ক্রল সাইকেলের একটি অংশ, এবং ড্র্যাগ ইভেন্ট দ্বারা উৎপন্ন বেগগুলো একই ধরনের পর্যায়গুলোর মধ্য দিয়ে যায়: প্রি-ফ্লিং, নোড কনসাম্পশন এবং পোস্ট-ফ্লিং। উল্লেখ্য যে, ফ্লিং অ্যানিমেশন শুধুমাত্র টাচ জেসচারের সাথেই যুক্ত থাকে এবং এটি অন্যান্য ইভেন্ট, যেমন অ্যাক্সেসিবিলিটি (a11y) বা হার্ডওয়্যার স্ক্রল দ্বারা ট্রিগার হবে না।

নেস্টেড স্ক্রোলিং চক্রে অংশগ্রহণ করুন

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

যদি নেস্টেড স্ক্রল সাইকেলটি নোডের একটি শৃঙ্খলের উপর ক্রিয়াশীল একটি সিস্টেম হয়, তবে nestedScroll মডিফায়ারটি হলো এই পরিবর্তনগুলিকে বাধা দেওয়া ও সেগুলিতে সংযোজন করার এবং শৃঙ্খলে প্রচারিত ডেটাকে (স্ক্রল ডেল্টা) প্রভাবিত করার একটি উপায়। এই মডিফায়ারটিকে হায়ারার্কির যেকোনো স্থানে স্থাপন করা যেতে পারে, এবং এটি ট্রি-এর উপরের দিকের নেস্টেড স্ক্রল মডিফায়ার ইনস্ট্যান্সগুলির সাথে যোগাযোগ করে, যাতে এই চ্যানেলের মাধ্যমে তথ্য আদান-প্রদান করা যায়। এই মডিফায়ারের মূল উপাদানগুলি হলো NestedScrollConnection এবং NestedScrollDispatcher

NestedScrollConnection নেস্টেড স্ক্রল চক্রের পর্যায়গুলিতে সাড়া দেওয়ার এবং নেস্টেড স্ক্রল সিস্টেমকে প্রভাবিত করার একটি উপায় প্রদান করে। এটি চারটি কলব্যাক মেথড দ্বারা গঠিত, যার প্রতিটি ব্যবহারের একটি পর্যায়কে প্রতিনিধিত্ব করে: প্রি/পোস্ট-স্ক্রল এবং প্রি/পোস্ট-ফ্লিং।

val nestedScrollConnection = object : NestedScrollConnection {
    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        println("Received onPreScroll callback.")
        return Offset.Zero
    }

    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset {
        println("Received onPostScroll callback.")
        return Offset.Zero
    }
}

প্রতিটি কলব্যাক প্রচারিত ডেল্টা সম্পর্কেও তথ্য দেয়: সেই নির্দিষ্ট পর্যায়ের জন্য available ডেল্টা, এবং পূর্ববর্তী পর্যায়গুলিতে consumed ডেল্টা। যদি কোনো সময়ে আপনি হায়ারার্কির উপরের দিকে ডেল্টা প্রচার বন্ধ করতে চান, তবে আপনি নেস্টেড স্ক্রোল সংযোগ ব্যবহার করে তা করতে পারেন:

val disabledNestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPostScroll(
            consumed: Offset,
            available: Offset,
            source: NestedScrollSource
        ): Offset {
            return if (source == NestedScrollSource.SideEffect) {
                available
            } else {
                Offset.Zero
            }
        }
    }
}

সমস্ত কলব্যাক NestedScrollSource টাইপ সম্পর্কে তথ্য প্রদান করে।

NestedScrollDispatcher নেস্টেড স্ক্রল সাইকেল শুরু করে। একটি ডিসপ্যাচার ব্যবহার করে এবং এর মেথডগুলো কল করার মাধ্যমে এই সাইকেলটি চালু হয়। স্ক্রলযোগ্য কন্টেইনারগুলোতে একটি বিল্ট-ইন ডিসপ্যাচার থাকে যা জেসচারের সময় ক্যাপচার করা ডেল্টাগুলো সিস্টেমে পাঠায়। এই কারণে, নেস্টেড স্ক্রলিং কাস্টমাইজ করার বেশিরভাগ ক্ষেত্রে ডিসপ্যাচারের পরিবর্তে NestedScrollConnection ব্যবহার করা হয়, যাতে নতুন ডেল্টা পাঠানোর বদলে আগে থেকে বিদ্যমান ডেল্টাগুলোর ওপর ভিত্তি করে প্রতিক্রিয়া জানানো যায়। আরও ব্যবহারের জন্য NestedScrollDispatcherSample দেখুন।

স্ক্রোল করার সময় ছবির আকার পরিবর্তন করুন

ব্যবহারকারী যখন স্ক্রল করবেন, তখন আপনি একটি ডাইনামিক ভিজ্যুয়াল ইফেক্ট তৈরি করতে পারেন, যেখানে স্ক্রলের অবস্থানের ওপর ভিত্তি করে ছবিটির আকার পরিবর্তিত হবে।

স্ক্রোল অবস্থানের উপর ভিত্তি করে একটি ছবির আকার পরিবর্তন করুন

এই কোড স্নিপেটটি ভার্টিকাল স্ক্রল পজিশনের উপর ভিত্তি করে একটি LazyColumn মধ্যে থাকা ছবির আকার পরিবর্তন করার পদ্ধতি প্রদর্শন করে। ব্যবহারকারী নিচে স্ক্রল করলে ছবিটি ছোট হয়ে যায় এবং উপরে স্ক্রল করলে বড় হয়ে যায়, তবে এটি নির্ধারিত সর্বনিম্ন এবং সর্বোচ্চ আকারের সীমার মধ্যেই থাকে।

@Composable
fun ImageResizeOnScrollExample(
    modifier: Modifier = Modifier,
    maxImageSize: Dp = 300.dp,
    minImageSize: Dp = 100.dp
) {
    var currentImageSize by remember { mutableStateOf(maxImageSize) }
    var imageScale by remember { mutableFloatStateOf(1f) }

    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                // Calculate the change in image size based on scroll delta
                val delta = available.y
                val newImageSize = currentImageSize + delta.dp
                val previousImageSize = currentImageSize

                // Constrain the image size within the allowed bounds
                currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize)
                val consumed = currentImageSize - previousImageSize

                // Calculate the scale for the image
                imageScale = currentImageSize / maxImageSize

                // Return the consumed scroll amount
                return Offset(0f, consumed.value)
            }
        }
    }

    Box(Modifier.nestedScroll(nestedScrollConnection)) {
        LazyColumn(
            Modifier
                .fillMaxWidth()
                .padding(15.dp)
                .offset {
                    IntOffset(0, currentImageSize.roundToPx())
                }
        ) {
            // Placeholder list items
            items(100, key = { it }) {
                Text(
                    text = "Item: $it",
                    style = MaterialTheme.typography.bodyLarge
                )
            }
        }

        Image(
            painter = ColorPainter(Color.Red),
            contentDescription = "Red color image",
            Modifier
                .size(maxImageSize)
                .align(Alignment.TopCenter)
                .graphicsLayer {
                    scaleX = imageScale
                    scaleY = imageScale
                    // Center the image vertically as it scales
                    translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f
                }
        )
    }
}

কোড সম্পর্কে মূল বিষয়গুলো

  • এই কোডটি স্ক্রল ইভেন্টগুলো ইন্টারসেপ্ট করার জন্য একটি NestedScrollConnection ব্যবহার করে।
  • onPreScroll স্ক্রল ডেল্টার উপর ভিত্তি করে ছবির আকারের পরিবর্তন গণনা করে।
  • currentImageSize স্টেট ভেরিয়েবলটি ছবির বর্তমান আকার সংরক্ষণ করে, যা minImageSize এবং maxImageSize. imageScale ` currentImageSize থেকে নির্ধারিত হয়।
  • LazyColumn currentImageSize ) উপর ভিত্তি করে অফসেট নির্ধারণ করে।
  • Image গণনাকৃত স্কেল প্রয়োগ করতে একটি graphicsLayer মডিফায়ার ব্যবহার করে।
  • graphicsLayer এর ভেতরের translationY নিশ্চিত করে যে ছবিটি স্কেল করার সময় উল্লম্বভাবে কেন্দ্রিক থাকে।

ফলাফল

পূর্ববর্তী কোড স্নিপেটটির ফলে স্ক্রল করার সময় ছবিতে একটি স্কেলিং ইফেক্ট তৈরি হয়:

চিত্র ৮. স্ক্রলে একটি স্কেলিং ইমেজ ইফেক্ট।

নেস্টেড স্ক্রোলিং ইন্টারঅপ

যখন আপনি স্ক্রলযোগ্য কম্পোজেবল এলিমেন্টের মধ্যে স্ক্রলযোগ্য View এলিমেন্ট নেস্ট করার চেষ্টা করেন, বা এর উল্টোটা করেন, তখন আপনি সমস্যার সম্মুখীন হতে পারেন। সবচেয়ে লক্ষণীয় সমস্যাগুলো তখন দেখা দেয়, যখন আপনি চাইল্ড এলিমেন্টটি স্ক্রল করে তার শুরু বা শেষের সীমানায় পৌঁছান এবং আশা করেন যে প্যারেন্ট এলিমেন্টটি স্ক্রলিংয়ের দায়িত্ব নেবে। তবে, এই প্রত্যাশিত আচরণটি হয়তো ঘটবে না অথবা প্রত্যাশা অনুযায়ী কাজ করবে না।

এই সমস্যাটি স্ক্রোলেবল কম্পোজেবল-এর অন্তর্নিহিত প্রত্যাশার ফল। স্ক্রোলেবল কম্পোজেবল-এর একটি "নেস্টেড-স্ক্রোল-বাই-ডিফল্ট" নিয়ম আছে, যার অর্থ হলো যেকোনো স্ক্রোলেবল কন্টেইনারকে অবশ্যই নেস্টেড স্ক্রোল চেইনে অংশগ্রহণ করতে হবে—প্যারেন্ট হিসেবে NestedScrollConnection মাধ্যমে এবং চাইল্ড হিসেবে NestedScrollDispatcher মাধ্যমে। এরপর চাইল্ডটি যখন বাউন্ড-এ থাকে, তখন এটি প্যারেন্টের জন্য একটি নেস্টেড স্ক্রোল পরিচালনা করে। উদাহরণস্বরূপ, এই নিয়মটি Compose Pager এবং Compose LazyRow একসাথে ভালোভাবে কাজ করতে সাহায্য করে। কিন্তু, যখন ViewPager2 বা RecyclerView সাথে ইন্টারঅপারেবিলিটি স্ক্রোলিং করা হয়, তখন যেহেতু এগুলো NestedScrollingParent3 ইমপ্লিমেন্ট করে না, তাই চাইল্ড থেকে প্যারেন্টে নিরবচ্ছিন্ন স্ক্রোলিং সম্ভব হয় না।

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

একটি সহযোগী প্যারেন্ট View যার মধ্যে একটি চাইল্ড ComposeView রয়েছে।

একটি সহযোগী প্যারেন্ট View হলো সেটি যা ইতিমধ্যেই NestedScrollingParent3 ইমপ্লিমেন্ট করে এবং সেই কারণে একটি সহযোগী নেস্টেড চাইল্ড কম্পোজেবল থেকে স্ক্রলিং ডেল্টা গ্রহণ করতে সক্ষম। এই ক্ষেত্রে ComposeView একটি চাইল্ড হিসেবে কাজ করবে এবং এটিকে (পরোক্ষভাবে) NestedScrollingChild3 ইমপ্লিমেন্ট করতে হবে। একটি সহযোগী প্যারেন্টের উদাহরণ হলো androidx.coordinatorlayout.widget.CoordinatorLayout

যদি আপনার স্ক্রোলযোগ্য View প্যারেন্ট কন্টেইনার এবং নেস্টেড স্ক্রোলযোগ্য চাইল্ড কম্পোজেবলের মধ্যে নেস্টেড স্ক্রোলিং ইন্টারঅপারেবিলিটির প্রয়োজন হয়, তাহলে আপনি rememberNestedScrollInteropConnection() ব্যবহার করতে পারেন।

rememberNestedScrollInteropConnection() ফাংশনটি NestedScrollConnection অনুমোদন করে এবং মনে রাখে, যা NestedScrollingParent3 ইমপ্লিমেন্টকারী একটি View প্যারেন্ট এবং একটি Compose চাইল্ডের মধ্যে নেস্টেড স্ক্রল ইন্টারঅপারেবিলিটি সক্ষম করে। এটি একটি nestedScroll মডিফায়ারের সাথে একত্রে ব্যবহার করা উচিত। যেহেতু Compose সাইডে নেস্টেড স্ক্রলিং ডিফল্টরূপে সক্রিয় থাকে, তাই আপনি এই কানেকশনটি ব্যবহার করে View সাইডেও নেস্টেড স্ক্রল সক্রিয় করতে পারেন এবং Views ও Composable-গুলোর মধ্যে প্রয়োজনীয় গ্লু লজিক যোগ করতে পারেন।

এর একটি সাধারণ ব্যবহার হলো CoordinatorLayout , CollapsingToolbarLayout এবং একটি চাইল্ড কম্পোজেবল ব্যবহার করা, যা এই উদাহরণে দেখানো হয়েছে:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    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">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <!--...-->

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

আপনার Activity বা Fragment-এ, আপনাকে আপনার চাইল্ড কম্পোজেবল এবং প্রয়োজনীয় NestedScrollConnection সেট আপ করতে হবে:

open class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<ComposeView>(R.id.compose_view).apply {
            setContent {
                val nestedScrollInterop = rememberNestedScrollInteropConnection()
                // Add the nested scroll connection to your top level @Composable element
                // using the nestedScroll modifier.
                LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) {
                    items(20) { item ->
                        Box(
                            modifier = Modifier
                                .padding(16.dp)
                                .height(56.dp)
                                .fillMaxWidth()
                                .background(Color.Gray),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(item.toString())
                        }
                    }
                }
            }
        }
    }
}

একটি প্যারেন্ট কম্পোজেবল যার মধ্যে একটি চাইল্ড AndroidView রয়েছে

এই সিনারিওটি ​​Compose-এর দিকে নেস্টেড স্ক্রলিং ইন্টারঅপ API-এর বাস্তবায়ন নিয়ে আলোচনা করে – যখন একটি প্যারেন্ট কম্পোজেবল ভিউ-এর মধ্যে একটি চাইল্ড AndroidView থাকে। AndroidView টি NestedScrollDispatcher ইমপ্লিমেন্ট করে, কারণ এটি একটি Compose স্ক্রলিং প্যারেন্টের চাইল্ড হিসেবে কাজ করে, এবং NestedScrollingParent3 ও ইমপ্লিমেন্ট করে, কারণ এটি একটি View স্ক্রলিং চাইল্ডের প্যারেন্ট হিসেবে কাজ করে। এর ফলে Compose প্যারেন্টটি একটি নেস্টেড স্ক্রলেবল চাইল্ড View থেকে নেস্টেড স্ক্রল ডেল্টা গ্রহণ করতে সক্ষম হবে।

নিম্নলিখিত উদাহরণটি দেখায় যে কীভাবে আপনি এই পরিস্থিতিতে একটি কম্পোজ কোলাপসিং টুলবার সহ নেস্টেড স্ক্রোলিং ইন্টারঅপ অর্জন করতে পারেন:

@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
    val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
    val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }

    // Sets up the nested scroll connection between the Box composable parent
    // and the child AndroidView containing the RecyclerView
    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                // Updates the toolbar offset based on the scroll to enable
                // collapsible behaviour
                val delta = available.y
                val newOffset = toolbarOffsetHeightPx.value + delta
                toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
                return Offset.Zero
            }
        }
    }

    Box(
        Modifier
            .fillMaxSize()
            .nestedScroll(nestedScrollConnection)
    ) {
        TopAppBar(
            modifier = Modifier
                .height(ToolbarHeight)
                .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
        )

        AndroidView(
            { context ->
                LayoutInflater.from(context)
                    .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
                        with(findViewById<RecyclerView>(R.id.main_list)) {
                            layoutManager = LinearLayoutManager(context, VERTICAL, false)
                            adapter = NestedScrollInteropAdapter()
                        }
                    }.also {
                        // Nested scrolling interop is enabled when
                        // nested scroll is enabled for the root View
                        ViewCompat.setNestedScrollingEnabled(it, true)
                    }
            },
            // ...
        )
    }
}

private class NestedScrollInteropAdapter :
    Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
    val items = (1..10).map { it.toString() }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): NestedScrollInteropViewHolder {
        return NestedScrollInteropViewHolder(
            LayoutInflater.from(parent.context)
                .inflate(R.layout.list_item, parent, false)
        )
    }

    override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
        // ...
    }

    class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
        fun bind(item: String) {
            // ...
        }
    }
    // ...
}

এই উদাহরণটি দেখায় কিভাবে আপনি একটি scrollable মডিফায়ারের সাথে API ব্যবহার করতে পারেন:

@Composable
fun ViewInComposeNestedScrollInteropExample() {
    Box(
        Modifier
            .fillMaxSize()
            .scrollable(rememberScrollableState {
                // View component deltas should be reflected in Compose
                // components that participate in nested scrolling
                it
            }, Orientation.Vertical)
    ) {
        AndroidView(
            { context ->
                LayoutInflater.from(context)
                    .inflate(android.R.layout.list_item, null)
                    .apply {
                        // Nested scrolling interop is enabled when
                        // nested scroll is enabled for the root View
                        ViewCompat.setNestedScrollingEnabled(this, true)
                    }
            }
        )
    }
}

এবং পরিশেষে, এই উদাহরণটি দেখায় কিভাবে BottomSheetDialogFragment সাথে নেস্টেড স্ক্রলিং ইন্টারঅপ এপিআই ব্যবহার করে একটি সফল ড্র্যাগ এবং ডিসমিস আচরণ অর্জন করা হয়:

class BottomSheetFragment : BottomSheetDialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)

        rootView.findViewById<ComposeView>(R.id.compose_view).apply {
            setContent {
                val nestedScrollInterop = rememberNestedScrollInteropConnection()
                LazyColumn(
                    Modifier
                        .nestedScroll(nestedScrollInterop)
                        .fillMaxSize()
                ) {
                    item {
                        Text(text = "Bottom sheet title")
                    }
                    items(10) {
                        Text(
                            text = "List item number $it",
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                }
            }
            return rootView
        }
    }
}

মনে রাখবেন যে rememberNestedScrollInteropConnection() ফাংশনটি আপনি যে এলিমেন্টে এটি সংযুক্ত করবেন, সেখানে একটি NestedScrollConnection ইনস্টল করবে। NestedScrollConnection কাজ হলো Compose লেভেল থেকে View লেভেলে ডেল্টা (delta) প্রেরণ করা। এটি এলিমেন্টটিকে নেস্টেড স্ক্রলিং-এ অংশগ্রহণ করতে সক্ষম করে, কিন্তু এটি এলিমেন্টগুলোর স্বয়ংক্রিয় স্ক্রলিং সক্ষম করে না। যে সমস্ত কম্পোজেবল এলিমেন্ট স্বয়ংক্রিয়ভাবে স্ক্রলযোগ্য নয়, যেমন Box বা Column , সেগুলোর ক্ষেত্রে স্ক্রল ডেল্টা নেস্টেড স্ক্রল সিস্টেমে সঞ্চারিত হয় না এবং delta-গুলো rememberNestedScrollInteropConnection() দ্বারা প্রদত্ত NestedScrollConnection পর্যন্ত পৌঁছায় না, ফলে সেই delta-গুলো প্যারেন্ট View কম্পোনেন্টেও পৌঁছায় না। এর সমাধান করতে, নিশ্চিত করুন যে আপনি এই ধরনের নেস্টেড কম্পোজেবল এলিমেন্টগুলোতেও স্ক্রলেবল মডিফায়ার সেট করেছেন। আরও বিস্তারিত তথ্যের জন্য আপনি নেস্টেড স্ক্রলিং সম্পর্কিত পূর্ববর্তী বিভাগটি দেখতে পারেন।

একটি অসহযোগী প্যারেন্ট View যার মধ্যে একটি চাইল্ড ComposeView রয়েছে।

একটি নন-কোঅপারেটিং ভিউ হলো সেটি, যা তার View দিকে প্রয়োজনীয় NestedScrolling ইন্টারফেসগুলো ইমপ্লিমেন্ট করে না। উল্লেখ্য যে, এর মানে হলো এই Views সাথে নেস্টেড স্ক্রোলিং ইন্টারঅপারেবিলিটি ডিফল্টভাবে কাজ করে না। নন-কোঅপারেটিং Views হলো RecyclerView এবং ViewPager2

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

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}