ট্যাপ ও প্রেস করুন

অনেক কম্পোজ করার মতো আইটেমে ট্যাপ বা ক্লিক করার জন্য বিল্ট-ইন সহায়তা থাকে এবং এতে onClick ল্যাম্বডা থাকে। যেমন, আপনি এমন ক্লিক করা যায় এমন Surface তৈরি করতে পারেন যা সারফেসের সাথে ইন্টার‍্যাকশনের জন্য উপযুক্ত সব মেটেরিয়াল ডিজাইন আচরণ অন্তর্ভুক্ত করে:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

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

জেসচার

বিবরণ

ট্যাপ করুন (বা ক্লিক করুন)

পয়েন্টারটি নিচে ও তারপর উপরে যায়

ডবল ট্যাপ

পয়েন্টার নিচে, উপরে, নিচে, উপরে যায়

দীর্ঘ সময় ধরে চাপুন

পয়েন্টার নিচের দিকে যায় এবং বেশিক্ষণ ধরে রাখা হয়

প্রেস

পয়েন্টার নিচের দিকে যায়

ট্যাপ বা ক্লিক করার পরে উত্তর দেওয়া

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

ছবির একটি গ্রিড কল্পনা করুন, যেখানে কোনও ব্যবহারকারী সেটিতে ক্লিক করলে একটি ছবি ফুল-স্ক্রিনে দেখা যায়:

এই আচরণ প্রয়োগ করতে, আপনি গ্রিডের প্রতিটি আইটেমে clickable মডিফায়ার যোগ করতে পারেন:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

clickable মডিফায়ার অতিরিক্ত আচরণও যোগ করে:

  • interactionSource ও indication, যা কোনও ব্যবহারকারী কম্পোজ করার উপযুক্ত আইটেমে ট্যাপ করলে ডিফল্ট হিসেবে রিপেল ড্র করে। ব্যবহারকারীর সাথে ইন্টার‍্যাকশন ম্যানেজ করা পৃষ্ঠা থেকে কীভাবে এগুলি কাস্টমাইজ করতে হয় তা জানুন।
  • অর্থ সংক্রান্ত তথ্য সেট করার মাধ্যমে অ্যাক্সেসিবিলিটি পরিষেবাগুলিকে এলিমেন্টের সাথে ইন্টার‍্যাক্ট করার অনুমতি দেয়।
  • ফোকাস করার অনুমতি দিয়ে এবং ইন্টার‍্যাক্ট করার জন্য Enter বা d-প্যাডের কেন্দ্রে প্রেস করার মাধ্যমে কীবোর্ড বা জয়স্টিক ইন্টার‍্যাকশন কাজ করে।
  • এলিমেন্টটিকে হোভার করার উপযুক্ত করে তুলুন, যাতে এটি মাউস বা স্টাইলাস হোভার করলে প্রতিক্রিয়া জানায়।

প্রাসঙ্গিক মেনু দেখতে কিছুক্ষণ প্রেস করে রাখুন

combinedClickable আপনাকে স্বাভাবিক ক্লিক করার আচরণের পাশাপাশি ডবল ট্যাপ বা অনেকক্ষণ প্রেস করার আচরণ যোগ করতে দেয়। কোনও ব্যবহারকারী গ্রিড ছবি টাচ করে ধরে রাখলে, আপনি combinedClickable ব্যবহার করে একটি প্রাসঙ্গিক মেনু দেখাতে পারেন:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

পেশাদার পদ্ধতি হিসেবে, ব্যবহারকারী কোনও এলিমেন্ট লং-প্রেস করলে আপনাকে হ্যাপ্টিক ফিডব্যাক যোগ করতে হবে, সেই কারণেই স্নিপেটে performHapticFeedback ইনভোকেশন যোগ করা হয়েছে।

Scrim-এ ট্যাপ করে কম্পোজ করার উপযুক্ত আইটেম বাতিল করা

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

আসুন, ছবির বিবরণ স্ক্রিন দেখে নেওয়া যাক। ব্যাকগ্রাউন্ডটি যেন কিছুটা স্বচ্ছ হয় এবং ব্যবহারকারী যেন সেই ব্যাকগ্রাউন্ডে ট্যাপ করে বিবরণের স্ক্রিনটি বন্ধ করতে পারেন:

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

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

pointerInput মডিফায়ারের কী হিসেবে আপনি onClose ল্যাম্বডা পাস করেন। ব্যবহারকারী স্ক্রিমে ট্যাপ করলে সঠিক কলব্যাক কল করা হয়েছে কিনা তা নিশ্চিত করতে এটি অটোমেটিক ল্যাম্বডা আবার এক্সিকিউট করে।

জুম করতে ডবল ট্যাপ করুন

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

আসুন, আমরা আবার ছবির বিবরণ স্ক্রিনটি দেখে নিই। ডবল ট্যাপ করে ছবি জুম করার সুবিধা রাখলে ভালো হয়:

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

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)