জেসচার বোঝা

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

সংজ্ঞা

এই পৃষ্ঠায় বিভিন্ন ধারণা বুঝতে, আপনাকে ব্যবহৃত কিছু পরিভাষা বুঝতে হবে:

  • পয়েন্টার: একটি ফিজিক্যাল অবজেক্ট যা আপনি নিজের অ্যাপ্লিকেশনের সাথে ইন্টার‍্যাক্ট করতে ব্যবহার করতে পারবেন। মোবাইল ডিভাইসের ক্ষেত্রে, সবচেয়ে সাধারণ পয়েন্টার হল আপনার আঙুল যা টাচস্ক্রিনের সাথে ইন্টার‍্যাক্ট করে। বিকল্প হিসেবে, আঙুলের পরিবর্তে আপনি স্টাইলাস ব্যবহার করতে পারেন। বড় স্ক্রিনের ক্ষেত্রে, ডিসপ্লের সাথে পরোক্ষভাবে ইন্টার‍্যাক্ট করতে আপনি মাউস বা ট্র্যাকপ্যাড ব্যবহার করতে পারেন। পয়েন্টার হিসেবে বিবেচিত হতে হলে, ইনপুট ডিভাইসকে অবশ্যই কোনও কোঅর্ডিনেটে "পয়েন্ট" করতে পারতে হবে, তাই, উদাহরণস্বরূপ, কীবোর্ডকে পয়েন্টার হিসেবে বিবেচনা করা যাবে না। Compose-এ, PointerType ব্যবহার করে পয়েন্টার পরিবর্তনের মধ্যে পয়েন্টারের ধরন অন্তর্ভুক্ত করা হয়।
  • পয়েন্টার ইভেন্ট: নির্দিষ্ট সময়ে অ্যাপ্লিকেশনের সাথে এক বা একাধিক পয়েন্টারের নিম্ন-স্তরের ইন্টার‍্যাকশন বর্ণনা করে। যেকোনও পয়েন্টার ইন্টার‍্যাকশন, যেমন স্ক্রিনে আঙুল রাখা বা মাউস টেনে আনা, কোনও ইভেন্ট ট্রিগার করবে। Compose-এ, এই ধরনের ইভেন্টের জন্য সব প্রাসঙ্গিক তথ্য PointerEvent ক্লাসে থাকে।
  • জেসচার: পয়েন্টার ইভেন্টের একটি ক্রম যা একটি অ্যাকশন হিসেবে ব্যাখ্যা করা যেতে পারে। যেমন, ট্যাপ করার জেসচারকে ডাউন ইভেন্টের পরে আপ ইভেন্টের ক্রম হিসেবে বিবেচনা করা যেতে পারে। ট্যাপ, টেনে আনা বা ট্রান্সফর্ম করার মতো সাধারণ জেসচার অনেক অ্যাপে ব্যবহার করা হয়, তবে আপনি চাইলে নিজের কাস্টম জেসচারও তৈরি করতে পারেন।

অ্যাবস্ট্রাকশনের বিভিন্ন লেভেল

জেটপ্যাক কম্পোজ অঙ্গভঙ্গি পরিচালনার জন্য বিভিন্ন স্তরের বিমূর্ততা প্রদান করে। সবচেয়ে উপরে আছে কম্পোনেন্ট সাপোর্ট। Button-এর মতো কম্পোজেবল অটোমেটিক জেসচার সাপোর্ট অন্তর্ভুক্ত করে। কাস্টম কম্পোনেন্টে জেসচার সাপোর্ট যোগ করতে, আপনি ইচ্ছামতো কম্পোজেবলে clickable-এর মতো জেসচার মডিফায়ার যোগ করতে পারেন। শেষে, আপনার কাস্টম জেসচার প্রয়োজন হলে, আপনি pointerInput মডিফায়ার ব্যবহার করতে পারবেন।

নিয়ম অনুযায়ী, আপনার প্রয়োজনীয় কার্যকারিতা অফার করে এমন সর্বোচ্চ লেভেলের অ্যাবস্ট্রাকশন তৈরি করুন। এইভাবে, আপনি লেয়ারে অন্তর্ভুক্ত পেশাদার পদ্ধতি থেকে উপকৃত হবেন। যেমন, Button-এ clickable-এর চেয়ে বেশি শব্দার্থগত তথ্য থাকে যা অ্যাক্সেসিবিলিটির জন্য ব্যবহার করা হয়। clickable-এ আবার pointerInput-এর চেয়ে বেশি তথ্য থাকে।

কম্পোনেন্ট সংক্রান্ত সহায়তা

Compose-এ অনেক আউট-অফ-দ্য-বক্স কম্পোনেন্টে কিছু ধরনের ইন্টার্নাল জেসচার হ্যান্ডলিং অন্তর্ভুক্ত থাকে। যেমন, LazyColumn ড্র্যাগ জেসচারের উত্তরে কন্টেন্ট স্ক্রল করে, Button-এ প্রেস করলে রিপল দেখায় এবং SwipeToDismiss কম্পোনেন্টে কোনও এলিমেন্ট বাতিল করার জন্য সোয়াইপ লজিক থাকে। এই ধরনের জেসচার হ্যান্ডলিং অটোমেটিক কাজ করে।

ইন্টার্নাল জেসচার হ্যান্ডলিং ছাড়াও, অনেক কম্পোনেন্টের ক্ষেত্রে কলারকেও জেসচার হ্যান্ডেল করতে হয়। যেমন, Button অটোমেটিক ট্যাপ শনাক্ত করে এবং ক্লিক ইভেন্ট ট্রিগার করে। জেসচারের প্রতিক্রিয়া জানাতে আপনি Button-এ onClick ল্যাম্বডা পাস করেন। একইভাবে, ব্যবহারকারী স্লাইডার হ্যান্ডেল টেনে নিয়ে গেলে তার প্রতিক্রিয়া হিসেবে আপনি onValueChange ল্যাম্বডা যোগ করেন Slider-এ।

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

// Talkback: "Click me!, Button, double tap to activate"
Button(onClick = { /* TODO */ }) { Text("Click me!") }
// Talkback: "Click me!, double tap to activate"
Box(Modifier.clickable { /* TODO */ }) { Text("Click me!") }

Compose-এ অ্যাক্সেসিবিলিটি সম্পর্কে আরও জানতে, Compose-এ অ্যাক্সেসিবিলিটি দেখুন।

মডিফায়ারের সাহায্যে ইচ্ছামতো কম্পোজেবলে নির্দিষ্ট জেসচার যোগ করা

আপনি যেকোনও কম্পোজেবলে জেসচার মডিফায়ার প্রয়োগ করতে পারেন যাতে কম্পোজেবল জেসচার শুনতে পায়। যেমন, আপনি কোনও সাধারণ Box-কে clickable করে ট্যাপ জেসচার ম্যানেজ করতে দিতে পারেন অথবা verticalScroll প্রয়োগ করে কোনও Column-কে ভার্টিক্যাল স্ক্রল ম্যানেজ করতে দিতে পারেন।

বিভিন্ন ধরনের জেসচার ম্যানেজ করার জন্য অনেক মডিফায়ার আছে:

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

pointerInput মডিফায়ারের সাহায্যে ইচ্ছামতো কম্পোজেবলে কাস্টম জেসচার যোগ করুন

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

নিম্নলিখিত কোডটি র পয়েন্টার ইভেন্ট শোনে:

@Composable
private fun LogPointerEvents(filter: PointerEventType? = null) {
    var log by remember { mutableStateOf("") }
    Column {
        Text(log)
        Box(
            Modifier
                .size(100.dp)
                .background(Color.Red)
                .pointerInput(filter) {
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent()
                            // handle pointer event
                            if (filter == null || event.type == filter) {
                                log = "${event.type}, ${event.changes.first().position}"
                            }
                        }
                    }
                }
        )
    }
}

এই স্নিপেটটি ভেঙে দেখলে, এর মূল উপাদানগুলি হল:

  • pointerInput মডিফায়ার। আপনি এক বা একাধিক কী পাস করেন। এইসব 'কী'-এর মধ্যে কোনও একটির ভ্যালু পরিবর্তন হলে, মডিফায়ার কন্টেন্ট ল্যাম্বডা আবার এক্সিকিউট করা হয়। স্যাম্পেলটি কম্পোজেবলে একটি ঐচ্ছিক ফিল্টার পাস করে। যদি সেই ফিল্টারের মান পরিবর্তন হয়, তাহলে সঠিক ইভেন্ট লগ করা হয়েছে কিনা তা নিশ্চিত করতে পয়েন্টার ইভেন্ট হ্যান্ডলারটি আবার কার্যকর করা উচিত।
  • awaitPointerEventScope একটি কোরুটিন স্কোপ তৈরি করে যা পয়েন্টার ইভেন্টের জন্য অপেক্ষা করতে ব্যবহার করা যেতে পারে।
  • পরবর্তী পয়েন্টার ইভেন্ট না হওয়া পর্যন্ত awaitPointerEvent কো-রুটিন সাসপেন্ড করে রাখে।

র-ইনপুট ইভেন্ট শোনা শক্তিশালী হলেও, এই র-ডেটার উপর ভিত্তি করে একটি কাস্টম জেসচার লেখা জটিল। কাস্টম জেসচার তৈরি করার প্রসেস সহজ করতে, অনেক ইউটিলিটি পদ্ধতি উপলভ্য আছে।

সম্পূর্ণ জেসচার শনাক্ত করুন

র' পয়েন্টার ইভেন্ট ম্যানেজ করার পরিবর্তে, আপনি নির্দিষ্ট জেসচার ঘটার জন্য অপেক্ষা করতে পারেন এবং সেই অনুযায়ী উত্তর দিতে পারেন। AwaitPointerEventScope-এ এগুলি শোনার পদ্ধতি দেওয়া আছে:

এগুলি হল টপ-লেভেল ডিটেক্টর, তাই আপনি একটি pointerInput মডিফায়ারের মধ্যে একাধিক ডিটেক্টর যোগ করতে পারবেন না। নিম্নলিখিত স্নিপেট শুধু ট্যাপ শনাক্ত করে, টেনে আনার জেসচার নয়:

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
                // Never reached
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

অভ্যন্তরীণভাবে, detectTapGestures পদ্ধতিটি কোরুটিনকে ব্লক করে এবং দ্বিতীয় ডিটেক্টর কখনই পৌঁছায় না। কম্পোজেবলে একাধিক জেসচার লিসেনার যোগ করতে হলে, পরিবর্তে আলাদা pointerInput মডিফায়ার ইনস্ট্যান্স ব্যবহার করুন:

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
            }
            .pointerInput(Unit) {
                // These drag events will correctly be triggered
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

জেসচার অনুযায়ী ইভেন্ট ম্যানেজ করা

সংজ্ঞা অনুযায়ী, পয়েন্টার ডাউন ইভেন্টের মাধ্যমে জেসচার শুরু হয়। আপনি while(true) লুপের পরিবর্তে awaitEachGesture হেল্পার পদ্ধতি ব্যবহার করতে পারেন যা প্রতিটি র ইভেন্টের মাধ্যমে পাস করে। awaitEachGesture পদ্ধতিটি সব পয়েন্টার তুলে নেওয়ার পরে কন্টেনিং ব্লক রিস্টার্ট করে, যা থেকে বোঝা যায় যে জেসচার সম্পূর্ণ হয়েছে:

@Composable
private fun SimpleClickable(onClick: () -> Unit) {
    Box(
        Modifier
            .size(100.dp)
            .pointerInput(onClick) {
                awaitEachGesture {
                    awaitFirstDown().also { it.consume() }
                    val up = waitForUpOrCancellation()
                    if (up != null) {
                        up.consume()
                        onClick()
                    }
                }
            }
    )
}

বাস্তবে, আপনি প্রায় সবসময়ই awaitEachGesture ব্যবহার করতে চাইবেন, যদি না আপনি জেসচার শনাক্ত না করেই পয়েন্টার ইভেন্টের উত্তর দিচ্ছেন। এর একটি উদাহরণ হল hoverable, যা পয়েন্টার ডাউন বা আপ ইভেন্টে সাড়া দেয় না— এটি শুধু জানতে চায় যে কখন কোনও পয়েন্টার এর সীমানায় প্রবেশ করে বা বেরিয়ে যায়।

নির্দিষ্ট ইভেন্ট বা সাব-জেসচারের জন্য অপেক্ষা করুন

জেসচারের সাধারণ অংশ শনাক্ত করতে সাহায্য করে এমন পদ্ধতির একটি সেট আছে:

  • awaitFirstDown-এর সাথে একটি পয়েন্টার নিচে না যাওয়া পর্যন্ত সাসপেন্ড করুন অথবা waitForUpOrCancellation-এর সাথে সব পয়েন্টার উপরে না যাওয়া পর্যন্ত অপেক্ষা করুন।
  • awaitTouchSlopOrCancellation এবং awaitDragOrCancellation ব্যবহার করে লো-লেভেল ড্র্যাগ লিসেনার তৈরি করুন। পয়েন্টার টাচ স্লোপে না পৌঁছানো পর্যন্ত জেসচার হ্যান্ডলার প্রথমে সাসপেন্ড করে এবং তারপর প্রথম ড্র্যাগ ইভেন্ট না আসা পর্যন্ত সাসপেন্ড করে। আপনি যদি শুধুমাত্র একটি অক্ষ বরাবর ড্র্যাগ করতে চান, তাহলে awaitHorizontalTouchSlopOrCancellation এবং awaitHorizontalDragOrCancellation, অথবা awaitVerticalTouchSlopOrCancellation এবং awaitVerticalDragOrCancellation ব্যবহার করুন।
  • awaitLongPressOrCancellation-এর সাথে বেশিক্ষণ প্রেস না করা পর্যন্ত সাসপেন্ড করুন।
  • ড্র্যাগ ইভেন্ট ক্রমাগত শুনতে drag পদ্ধতি ব্যবহার করুন অথবা একটি অক্ষে ড্র্যাগ ইভেন্ট শুনতে horizontalDrag বা verticalDrag ব্যবহার করুন।

মাল্টি-টাচ ইভেন্টের জন্য গণনা প্রয়োগ করুন

একাধিক পয়েন্টার ব্যবহার করে কোনও ব্যবহারকারী মাল্টি-টাচ জেসচার করলে, র-ভ্যালুর উপর ভিত্তি করে প্রয়োজনীয় ট্রান্সফর্মেশন বোঝা জটিল হয়ে যায়। transformable মডিফায়ার বা detectTransformGestures পদ্ধতি আপনার ব্যবহারের ক্ষেত্রে যথেষ্ট সূক্ষ্ম কন্ট্রোল না দিলে, আপনি র ইভেন্ট শুনতে এবং সেগুলির উপর গণনা প্রয়োগ করতে পারেন। এই সহায়ক পদ্ধতিগুলি হল calculateCentroid, calculateCentroidSize, calculatePan, calculateRotation এবং calculateZoom।

ইভেন্ট ডিসপ্যাচিং ও হিট-টেস্টিং

প্রতিটি পয়েন্টার ইভেন্ট প্রতিটি pointerInput মডিফায়ারে পাঠানো হয় না। ইভেন্ট ডিসপ্যাচিং এইভাবে কাজ করে:

  • পয়েন্টার ইভেন্ট কম্পোজ করার উপযুক্ত হায়ারার্কি-তে ডিসপ্যাচ করা হয়। নতুন পয়েন্টার প্রথমবার পয়েন্টার ইভেন্ট ট্রিগার করার সাথে সাথে সিস্টেম "উপযুক্ত" কম্পোজেবল হিট-টেস্ট করা শুরু করে। কোনও কম্পোজেবলকে তখনই উপযুক্ত বলে বিবেচনা করা হয় যখন সেটির পয়েন্টার ইনপুট ম্যানেজ করার ক্ষমতা থাকে। হিট-টেস্টিং UI ট্রি-এর উপর থেকে নিচের দিকে ফ্লো করে। পয়েন্টার ইভেন্ট কম্পোজ করার উপযুক্ত আইটেমের সীমার মধ্যে ঘটলে, কম্পোজ করার উপযুক্ত আইটেম "হিট" হয়। এই প্রসেসের ফলে কম্পোজেবলের চেন তৈরি হয় যা হিট-টেস্ট পজিটিভ করে।
  • ডিফল্ট হিসেবে, ট্রি-এর একই লেভেলে একাধিক উপযুক্ত কম্পোজেবল থাকলে, শুধুমাত্র সর্বোচ্চ z-ইনডেক্স সহ কম্পোজেবল "হিট" হয়। যেমন, আপনি যখন একটি Box-এ দুটি ওভারল্যাপ করা Button কম্পোজেবল যোগ করেন, তখন শুধুমাত্র উপরে আঁকাটি যেকোনও পয়েন্টার ইভেন্ট পায়। আপনি তাত্ত্বিকভাবে নিজের PointerInputModifierNode ইমপ্লিমেন্টেশন তৈরি করে এবং sharePointerInputWithSiblings-কে true হিসেবে সেট করে এই আচরণ ওভাররাইড করতে পারবেন।
  • একই পয়েন্টারের জন্য আরও ইভেন্ট কম্পোজেবলের একই চেনে ডিসপ্যাচ করা হয় এবং ইভেন্ট প্রোপাগেশন লজিক অনুযায়ী ফ্লো করে। সিস্টেম এই পয়েন্টারের জন্য আর কোনও হিট-টেস্টিং করে না। এর অর্থ হল, চেনের প্রতিটি কম্পোজেবল সেই পয়েন্টারের জন্য সব ইভেন্ট পায়, এমনকি সেই কম্পোজেবলের সীমার বাইরে সেগুলি ঘটলেও। চেনে নেই এমন কম্পোজেবল কখনও পয়েন্টার ইভেন্ট পায় না, এমনকি পয়েন্টার তার সীমার মধ্যে থাকলেও।

হোভার ইভেন্ট, মাউস বা স্টাইলাস হোভারিংয়ের মাধ্যমে ট্রিগার করা হয়, যা এখানে সংজ্ঞায়িত নিয়মের ব্যতিক্রম। যে কম্পোজেবলকে হিট করা হয়, সেখানে হোভার ইভেন্ট পাঠানো হয়। তাই, কোনও ব্যবহারকারী যখন একটি কম্পোজেবলের বাউন্ড থেকে পরবর্তী কম্পোজেবলে পয়েন্টার নিয়ে যান, তখন প্রথম কম্পোজেবলে ইভেন্ট পাঠানোর পরিবর্তে, নতুন কম্পোজেবলে ইভেন্ট পাঠানো হয়।

ইভেন্ট কনজাম্পশন

একাধিক কম্পোজেবলে জেসচার হ্যান্ডলার অ্যাসাইন করা থাকলে, সেই হ্যান্ডলারগুলির মধ্যে কোনও দ্বন্দ্ব থাকা উচিত নয়। যেমন, এই UI দেখুন:

একটি ছবি, দুটি টেক্সট সহ একটি কলাম এবং একটি বোতাম সহ তালিকা আইটেম।

ব্যবহারকারী বুকমার্ক বোতামে ট্যাপ করলে, বোতামের onClick ল্যাম্বডা সেই জেসচার ম্যানেজ করে। ব্যবহারকারী তালিকার আইটেমের অন্য কোনও অংশে ট্যাপ করলে, ListItem সেই জেসচার ম্যানেজ করে এবং নিবন্ধে নেভিগেট করে। পয়েন্টার ইনপুটের ক্ষেত্রে, বোতামটিকে অবশ্যই এই ইভেন্ট ব্যবহার করতে হবে, যাতে এর পেরেন্ট জানে যে এতে আর প্রতিক্রিয়া জানাতে হবে না। আউট-অফ-দ্য-বক্স কম্পোনেন্টে অন্তর্ভুক্ত জেসচার এবং সাধারণ জেসচার মডিফায়ারে এই কনজাম্পশন আচরণ অন্তর্ভুক্ত থাকে, তবে আপনি নিজের কাস্টম জেসচার লিখলে, আপনাকে ম্যানুয়ালি ইভেন্ট কনজিউম করতে হবে। আপনি PointerInputChange.consume পদ্ধতির মাধ্যমে এটি করেন:

Modifier.pointerInput(Unit) {

    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            // consume all changes
            event.changes.forEach { it.consume() }
        }
    }
}

কোনও ইভেন্ট কনজিউম করলে, অন্যান্য কম্পোজেবলে ইভেন্ট প্রোপাগেশন বন্ধ হয় না। পরিবর্তে, কম্পোজেবলকে স্পষ্টভাবে কনজিউম করা ইভেন্ট উপেক্ষা করতে হবে। কাস্টম জেসচার লেখার সময়, আপনাকে চেক করে দেখতে হবে যে কোনও ইভেন্ট অন্য কোনও এলিমেন্ট দ্বারা আগেই ব্যবহার করা হয়েছে কিনা:

Modifier.pointerInput(Unit) {
    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            if (event.changes.any { it.isConsumed }) {
                // A pointer is consumed by another gesture handler
            } else {
                // Handle unconsumed event
            }
        }
    }
}

ইভেন্ট প্রোপাগেশন

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

ট্রি স্ট্রাকচার। সবচেয়ে উপরের লেয়ার হল ListItem, দ্বিতীয় লেয়ারে আছে Image, Column ও Button এবং Column-টি দুটি Text-এ বিভক্ত হয়ে যায়। ListItem ও বোতাম হাইলাইট করা হয়।

পয়েন্টার ইভেন্টগুলি এই প্রতিটি কম্পোজেবলের মাধ্যমে তিনবার প্রবাহিত হয়, তিনটি "পাস" চলাকালীন:

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

ভিজ্যুয়ালি, ইভেন্ট ফ্লো এইভাবে দেখানো যেতে পারে:

ইনপুট পরিবর্তন হয়ে গেলে, এই তথ্য সেই পয়েন্ট থেকে ফ্লোতে পাস করা হয়:

কোডে, আপনি যে পাসে আগ্রহী তা নির্দিষ্ট করতে পারেন:

Modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        val eventOnInitialPass = awaitPointerEvent(PointerEventPass.Initial)
        val eventOnMainPass = awaitPointerEvent(PointerEventPass.Main) // default
        val eventOnFinalPass = awaitPointerEvent(PointerEventPass.Final)
    }
}

এই কোড স্নিপেটে, এই await মেথড কলের প্রত্যেকটি একই ইভেন্ট রিটার্ন করে, যদিও ব্যবহার সংক্রান্ত ডেটা পরিবর্তিত হয়ে থাকতে পারে।

জেসচার পরীক্ষা করুন

আপনার টেস্ট পদ্ধতিতে, আপনি ম্যানুয়ালি performTouchInput পদ্ধতি ব্যবহার করে পয়েন্টার ইভেন্ট পাঠাতে পারবেন। এটি আপনাকে উচ্চ-লেভেলের সম্পূর্ণ জেসচার (যেমন, পিঞ্চ বা দীর্ঘক্ষণ ক্লিক করা) অথবা নিম্ন-লেভেলের জেসচার (যেমন, নির্দিষ্ট পরিমাণ পিক্সেল অনুযায়ী কার্সর সরানো) পারফর্ম করতে দেয়:

composeTestRule.onNodeWithTag("MyList").performTouchInput {
    swipeUp()
    swipeDown()
    click()
}

আরও উদাহরণের জন্য performTouchInput ডকুমেন্টেশন দেখুন।

আরও জানুন

আপনি নিম্নলিখিত রিসোর্স থেকে Jetpack Compose-এ জেসচার সম্পর্কে আরও জানতে পারবেন: