تک‌ضرب زدن و فشار دادن

بسیاری از ترکیب‌پذیرها پشتیبانی داخلی برای تک‌ضرب یا کلیک دارند و شامل یک onClick لامبدا هستند. برای مثال، می‌توانید Surface قابل‌کلیکی ایجاد کنید که شامل همه رفتارهای «طراحی مواد» مناسب برای تعامل با سطوح باشد:

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

اما کلیک کردن تنها راهی نیست که کاربر می‌تواند با ترکیب‌شدنی‌ها تعامل داشته باشد. این صفحه بر اشاره‌هایی تمرکز دارد که شامل یک اشاره‌گر هستند، جایی که موقعیت آن اشاره‌گر برای مدیریت آن رویداد مهم نیست. جدول زیر این نوع اشاره‌ها را فهرست می‌کند:

اشاره

شرح

تک‌ضرب بزنید (یا کلیک کنید)

اشاره‌گر پایین و سپس بالا می‌رود

دو تک‌ضرب زدن

اشاره‌گر پایین، بالا، پایین، بالا می‌رود

فشار طولانی

اشاره‌گر پایین می‌رود و برای مدت طولانی‌تری نگه داشته می‌شود

مطبوعات

اشاره‌گر پایین می‌رود

پاسخ دادن به تک‌ضرب یا کلیک

clickable اصلاح‌کننده‌ای است که معمولاً استفاده می‌شود و باعث می‌شود که یک composable به تک‌ضرب‌ها یا کلیک‌ها واکنش نشان دهد. این اصلاح‌کننده همچنین ویژگی‌های اضافی مانند پشتیبانی از تمرکز، نگه داشتن موشواره و قلم، و نشانگر بصری قابل‌سفارشی‌سازی هنگام فشار دادن را اضافه می‌کند. این اصلاح‌کننده به «کلیک‌ها» در گسترده‌ترین معنای کلمه پاسخ می‌دهد - نه فقط با موشواره یا انگشت، بلکه به رویدادهای کلیک ازطریق ورودی صفحه‌کلید یا هنگام استفاده از سرویس‌های دسترس‌پذیری نیز پاسخ می‌دهد.

شبکه‌ای از تصاویر را تصور کنید که وقتی کاربر روی تصویری کلیک می‌کند، آن تصویر به‌صورت تمام‌صفحه نشان داده می‌شود:

می‌توانید اصلاح‌کننده 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، که به‌طور پیش‌فرض وقتی کاربر روی composable تک‌ضرب می‌زند، موجی را ترسیم می‌کنند. با نحوه سفارشی‌سازی این موارد در صفحه مدیریت تعاملات کاربر آشنا شوید.
  • به سرویس‌های دسترس‌پذیری اجازه می‌دهد با تنظیم اطلاعات معنایی با عنصر تعامل برقرار کنند.
  • از تعامل با صفحه‌کلید یا دسته کنترل با امکان تمرکز و فشار دادن Enter یا مرکز پد کنترل برای تعامل پشتیبانی می‌کند.
  • عنصر را شناور کنید تا به نگه داشتن موشواره یا قلم روی آن پاسخ دهد.

برای نمایش منوِ بافت زمینه‌ای، طولانی فشار دهید

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 است.

بستن composable با ضربه زدن روی 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 اطلاعات کافی برای پاسخ دادن به تعامل به روش صحیح را ندارند. برای مثال، ممکن است ترکیب‌پذیرها به دسترسی به موقعیت در محدوده ترکیب‌پذیر که تعامل در آنجا رخ داده است نیاز داشته باشند.

بیایید دوباره به صفحه جزئیات تصویر نگاه کنیم. یک روال مطلوب این است که امکان بزرگ‌نمایی تصویر با دوضربه زدن فراهم شود:

همان‌طور که در ویدیو می‌بینید، بزرگ‌نمایی در اطراف موقعیت رویداد ضربه زدن اتفاق می‌افتد. وقتی روی بخش چپ تصویر زوم‌پیش می‌کنیم، نتیجه با زمانی که روی بخش راست زوم‌پیش می‌کنیم متفاوت است. می‌توانیم از اصلاح‌کننده pointerInput در ترکیب با detectTapGestures استفاده کنیم تا موقعیت ضربه زدن را در محاسباتمان بگنجانیم:

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
        }
)