ইশারা বুঝুন

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

সংজ্ঞা

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

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

বিমূর্তকরণের বিভিন্ন স্তর

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

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

উপাদান সমর্থন

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

অভ্যন্তরীণ অঙ্গভঙ্গি পরিচালনার পাশে, অনেক উপাদানেরও অঙ্গভঙ্গি পরিচালনা করার জন্য কলারের প্রয়োজন হয়৷ উদাহরণস্বরূপ, একটি Button স্বয়ংক্রিয়ভাবে ট্যাপ সনাক্ত করে এবং একটি ক্লিক ইভেন্ট ট্রিগার করে। অঙ্গভঙ্গিতে প্রতিক্রিয়া জানাতে আপনি Button একটি onClick ল্যাম্বডা পাস করেন। একইভাবে, আপনি Slider একটি onValueChange lambda যোগ করুন যাতে ব্যবহারকারী স্লাইডার হ্যান্ডেলটি টেনে নিয়ে যায়।

যখন এটি আপনার ব্যবহারের ক্ষেত্রে উপযুক্ত হয়, তখন উপাদানগুলিতে অন্তর্ভুক্ত অঙ্গভঙ্গিগুলি পছন্দ করুন, কারণ এতে ফোকাস এবং অ্যাক্সেসযোগ্যতার জন্য বাক্সের বাইরের সমর্থন অন্তর্ভুক্ত থাকে এবং সেগুলি ভালভাবে পরীক্ষা করা হয়। উদাহরণস্বরূপ, একটি 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!") }

রচনায় অ্যাক্সেসযোগ্যতা সম্পর্কে আরও জানতে, রচনায় অ্যাক্সেসযোগ্যতা দেখুন।

সংশোধক সহ নির্বিচারে কম্পোজেবলগুলিতে নির্দিষ্ট অঙ্গভঙ্গি যোগ করুন

অঙ্গভঙ্গি শোনার জন্য কম্পোজেবল কম্পোজেবল করার জন্য আপনি যেকোনো ইচ্ছামত কম্পোজেবলে জেসচার মডিফায়ার প্রয়োগ করতে পারেন। উদাহরণস্বরূপ, আপনি একটি সাধারণ 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 একটি coroutine সুযোগ তৈরি করে যা পয়েন্টার ইভেন্টের জন্য অপেক্ষা করতে ব্যবহার করা যেতে পারে।
  • awaitPointerEvent পরবর্তী পয়েন্টার ইভেন্ট না হওয়া পর্যন্ত coroutine স্থগিত করে।

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

সম্পূর্ণ অঙ্গভঙ্গি সনাক্ত করুন

কাঁচা পয়েন্টার ইভেন্টগুলি পরিচালনা করার পরিবর্তে, আপনি নির্দিষ্ট অঙ্গভঙ্গিগুলি ঘটতে শুনতে এবং যথাযথভাবে প্রতিক্রিয়া জানাতে পারেন। 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 পদ্ধতিটি coroutine ব্লক করে, এবং দ্বিতীয় ডিটেক্টর কখনই পৌঁছায় না। আপনি যদি একটি কম্পোজেবলে একাধিক অঙ্গভঙ্গি শ্রোতা যোগ করতে চান তবে পরিবর্তে পৃথক 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 কে সত্যে সেট করে এই আচরণটিকে ওভাররাইড করতে পারেন।
  • একই পয়েন্টারের জন্য আরও ইভেন্টগুলি কম্পোজেবলের একই শৃঙ্খলে প্রেরণ করা হয় এবং ঘটনা প্রচারের যুক্তি অনুসারে প্রবাহিত হয়। সিস্টেম এই পয়েন্টারের জন্য আর কোনো হিট-পরীক্ষা করে না। এর মানে হল যে চেইনের প্রতিটি কম্পোজেবল সেই পয়েন্টারের জন্য সমস্ত ইভেন্ট গ্রহণ করে, এমনকি যখন সেই কম্পোজেবলের সীমার বাইরে ঘটে। যে কম্পোজেবলগুলি চেইনের মধ্যে নেই সেগুলি কখনই পয়েন্টার ইভেন্টগুলি গ্রহণ করে না, এমনকি যখন পয়েন্টারটি তাদের সীমানার ভিতরে থাকে।

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

ইভেন্ট খরচ

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

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

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

দৃশ্যত, ইভেন্ট প্রবাহটি নিম্নরূপ উপস্থাপন করা যেতে পারে:

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

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

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

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

পরীক্ষা অঙ্গভঙ্গি

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

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

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

আরও জানুন

আপনি নিম্নলিখিত সংস্থানগুলি থেকে জেটপ্যাক রচনায় অঙ্গভঙ্গি সম্পর্কে আরও জানতে পারেন:

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}