تغییر رفتار تمرکز

گاهی اوقات لازم است رفتار فوکوس پیش فرض عناصر روی صفحه نمایش خود را نادیده بگیرید. به عنوان مثال، ممکن است بخواهید موارد ترکیب‌سازی را گروه‌بندی کنید ، از تمرکز بر روی یک قابل ترکیب خاص جلوگیری کنید ، صراحتاً فوکوس روی یکی را درخواست کنید ، فوکوس را بگیرید یا رها کنید، یا فوکوس را به ورود یا خروج تغییر جهت دهید . این بخش نحوه تغییر رفتار فوکوس را در زمانی که پیش‌فرض‌ها آن چیزی که نیاز دارید نیست، توضیح می‌دهد.

ناوبری منسجمی را با گروه های کانونی ارائه دهید

گاهی اوقات، Jetpack Compose فوراً مورد بعدی صحیح را برای پیمایش برگه‌ها حدس نمی‌زند، به‌ویژه زمانی که Composables والد پیچیده مانند برگه‌ها و فهرست‌ها وارد بازی می‌شوند.

در حالی که جستجوی فوکوس معمولاً از ترتیب اعلان Composables پیروی می کند، این در برخی موارد غیرممکن است، مانند زمانی که یکی از Composables در سلسله مراتب یک اسکرول افقی است که به طور کامل قابل مشاهده نیست. این در مثال زیر نشان داده شده است.

Jetpack Compose ممکن است تصمیم بگیرد که به جای ادامه مسیری که برای پیمایش یک جهته انتظار دارید، مورد بعدی را که نزدیک‌ترین نقطه به شروع صفحه نمایش نشان داده شده است، متمرکز کند.

انیمیشن یک برنامه که یک پیمایش افقی بالا و لیستی از موارد زیر را نشان می دهد.
شکل 1 . انیمیشن یک برنامه که یک پیمایش افقی بالا و لیستی از موارد زیر را نشان می دهد

در این مثال، واضح است که توسعه دهندگان قصد نداشتند که فوکوس از تب Chocolates به اولین تصویر زیر و سپس به تب Pastries برگردد. در عوض، آنها می‌خواستند که تمرکز روی برگه‌ها تا آخرین برگه ادامه داشته باشد و سپس روی محتوای درونی تمرکز کند:

انیمیشن یک برنامه که یک پیمایش افقی بالا و لیستی از موارد زیر را نشان می دهد.
شکل 2 . انیمیشن یک برنامه که یک پیمایش افقی بالا و لیستی از موارد زیر را نشان می دهد

در شرایطی که مهم است که گروهی از composable ها به صورت متوالی فوکوس کنند، مانند ردیف Tab از مثال قبلی، باید Composable در والدی قرار دهید که دارای اصلاح کننده focusGroup() است:

LazyVerticalGrid(columns = GridCells.Fixed(4)) {
    item(span = { GridItemSpan(maxLineSpan) }) {
        Row(modifier = Modifier.focusGroup()) {
            FilterChipA()
            FilterChipB()
            FilterChipC()
        }
    }
    items(chocolates) {
        SweetsCard(sweets = it)
    }
}

پیمایش دو جهته به دنبال نزدیک‌ترین ترکیب برای جهت معین می‌گردد—اگر عنصری از گروه دیگر از یک مورد کاملاً قابل مشاهده در گروه فعلی نزدیک‌تر باشد، ناوبری نزدیک‌ترین مورد را انتخاب می‌کند. برای جلوگیری از این رفتار، می توانید اصلاح کننده focusGroup() اعمال کنید.

FocusGroup باعث می‌شود که کل گروه از نظر تمرکز مانند یک موجودیت واحد به نظر برسد، اما خود گروه تمرکز را جلب نمی‌کند - در عوض، نزدیک‌ترین کودک در عوض تمرکز خواهد داشت. به این ترتیب، ناوبری می داند که قبل از خروج از گروه، به آیتم غیر قابل مشاهده کاملاً قابل مشاهده می رود.

در این حالت، سه نمونه از FilterChip قبل از آیتم های SweetsCard متمرکز می شوند، حتی زمانی که SweetsCards به طور کامل برای کاربر قابل مشاهده هستند و ممکن است برخی از FilterChip پنهان باشند. این به این دلیل اتفاق می‌افتد که اصلاح‌کننده focusGroup به مدیر تمرکز می‌گوید ترتیب فوکوس کردن آیتم‌ها را طوری تنظیم کند که پیمایش آسان‌تر و هماهنگ‌تر با UI باشد.

بدون تغییر دهنده focusGroup ، اگر FilterChipC قابل مشاهده نبود، ناوبری فوکوس آخرین بار آن را انتخاب می کند. با این حال، افزودن چنین اصلاح‌کننده‌ای نه تنها آن را قابل کشف می‌کند، بلکه همان طور که کاربران انتظار دارند، بلافاصله پس از FilterChipB فوکوس پیدا می‌کند.

ساختن یک ترکیب قابل تمرکز

برخی از کامپوزیشن‌ها بر اساس طراحی قابل فوکوس هستند، مانند یک دکمه یا ترکیبی که اصلاح‌کننده clickable به آن متصل است. اگر می خواهید به طور خاص رفتار قابل تمرکز را به یک composable اضافه کنید، از اصلاح کننده focusable استفاده می کنید:

var color by remember { mutableStateOf(Green) }
Box(
    Modifier
        .background(color)
        .onFocusChanged { color = if (it.isFocused) Blue else Green }
        .focusable()
) {
    Text("Focusable 1")
}

ساختن یک کامپوزیشن غیر قابل تمرکز

ممکن است شرایطی وجود داشته باشد که برخی از عناصر شما نباید در تمرکز شرکت کنند. در این مواقع نادر، می‌توانید از canFocus property استفاده کنید تا یک Composable از قابلیت فوکوس‌پذیر بودن حذف کنید.

var checked by remember { mutableStateOf(false) }

Switch(
    checked = checked,
    onCheckedChange = { checked = it },
    // Prevent component from being focused
    modifier = Modifier
        .focusProperties { canFocus = false }
)

فوکوس صفحه کلید را با FocusRequester درخواست کنید

در برخی موارد، ممکن است بخواهید صریحاً به عنوان پاسخی به تعامل کاربر، فوکوس را درخواست کنید. برای مثال، ممکن است از یک کاربر بپرسید که آیا می‌خواهد پر کردن یک فرم را مجدداً شروع کند، و اگر «بله» را فشار داد، می‌خواهید فیلد اول آن فرم را دوباره فوکوس کنید.

اولین کاری که باید انجام دهید این است که یک شی FocusRequester را با ترکیبی که می خواهید فوکوس صفحه کلید را به آن منتقل کنید، مرتبط کنید. در قطعه کد زیر، یک شی FocusRequester با تنظیم یک اصلاح کننده به نام Modifier.focusRequester با یک TextField مرتبط می شود:

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

برای ارسال درخواست‌های فوکوس واقعی می‌توانید با متد requestFocus FocusRequester تماس بگیرید. شما باید این روش را خارج از یک زمینه Composable فراخوانی کنید (در غیر این صورت، در هر ترکیب مجدد دوباره اجرا می شود). قطعه زیر نحوه درخواست از سیستم را برای جابجایی فوکوس صفحه کلید با کلیک روی دکمه نشان می دهد:

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

Button(onClick = { focusRequester.requestFocus() }) {
    Text("Request focus on TextField")
}

فوکوس را ضبط و رها کنید

می‌توانید از تمرکز استفاده کنید تا کاربرانتان را راهنمایی کنید تا داده‌های مناسبی را که برنامه‌تان برای انجام وظیفه‌تان نیاز دارد ارائه دهند - به عنوان مثال، دریافت یک آدرس ایمیل یا شماره تلفن معتبر. اگرچه حالت‌های خطا به کاربران شما اطلاع می‌دهند که چه اتفاقی در حال رخ دادن است، ممکن است به فیلدی حاوی اطلاعات اشتباه نیاز داشته باشید تا تا زمانی که برطرف شود، متمرکز بمانید.

برای گرفتن فوکوس، می‌توانید متد captureFocus() را فراخوانی کنید و سپس آن را با متد freeFocus() رها کنید، مانند مثال زیر:

val textField = FocusRequester()

TextField(
    value = text,
    onValueChange = {
        text = it

        if (it.length > 3) {
            textField.captureFocus()
        } else {
            textField.freeFocus()
        }
    },
    modifier = Modifier.focusRequester(textField)
)

تقدم اصلاح کننده های فوکوس

Modifiers می‌توان به‌عنوان عناصری دید که فقط یک فرزند دارند، بنابراین وقتی آنها را در صف قرار می‌دهید، هر Modifier در سمت چپ (یا بالا) Modifier که در سمت راست (یا پایین) دنبال می‌شود، می‌پیچد. این به این معنی است که دومین Modifier در داخل مورد اول قرار دارد، به طوری که هنگام اعلام دو focusProperties ، تنها بالاترین آن کار می کند، همانطور که موارد زیر در بالاترین قرار دارند.

برای توضیح بیشتر مفهوم، کد زیر را ببینید:

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

در این حالت، focusProperties که item2 را به عنوان فوکوس مناسب نشان می دهد، استفاده نمی شود، همانطور که در مورد قبلی موجود است. بنابراین، item1 مورد استفاده خواهد بود.

با استفاده از این رویکرد، والدین همچنین می توانند با استفاده از FocusRequester.Default ، رفتار را به حالت پیش فرض بازنشانی کنند:

Modifier
    .focusProperties { right = Default }
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

لازم نیست والد بخشی از یک زنجیره اصلاح کننده باشد. یک Composable والد می تواند یک ویژگی تمرکز از یک فرزند composable را بازنویسی کند. به عنوان مثال، این FancyButton در نظر بگیرید که باعث می شود دکمه قابل فوکوس نباشد:

@Composable
fun FancyButton(modifier: Modifier = Modifier) {
    Row(modifier.focusProperties { canFocus = false }) {
        Text("Click me")
        Button(onClick = { }) { Text("OK") }
    }
}

کاربر می تواند با تنظیم canFocus روی true این دکمه را دوباره قابل فوکوس کند:

FancyButton(Modifier.focusProperties { canFocus = true })

مانند هر Modifier ، موارد مرتبط با تمرکز بر اساس ترتیبی که آنها را اعلام می کنید، رفتار متفاوتی دارند. به عنوان مثال، کدی مانند زیر Box را قابل فوکوس می‌کند، اما FocusRequester با این قابلیت فوکوس‌پذیر مرتبط نیست، زیرا بعد از فوکوس‌پذیر اعلام می‌شود.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

مهم است که به یاد داشته باشید که یک focusRequester با اولین قابل تمرکز زیر آن در سلسله مراتب مرتبط است، بنابراین این focusRequester به اولین فرزند قابل تمرکز اشاره می کند. در صورتی که هیچ کدام در دسترس نباشد، به چیزی اشاره نخواهد کرد. با این حال، از آنجایی که Box قابل فوکوس است (به لطف اصلاح کننده focusable() )، می توانید با استفاده از پیمایش دو جهته به آن بروید.

به عنوان مثالی دیگر، هر یک از موارد زیر کار می‌کند، زیرا اصلاح‌کننده onFocusChanged() به اولین عنصر قابل تمرکزی اشاره دارد که بعد از اصلاح‌کننده‌های focusable() یا focusTarget() ظاهر می‌شود.

Box(
    Modifier
        .onFocusChanged {}
        .focusRequester(Default)
        .focusable()
)
Box(
    Modifier
        .focusRequester(Default)
        .onFocusChanged {}
        .focusable()
)

تغییر جهت تمرکز هنگام ورود یا خروج

گاهی اوقات، شما باید نوع بسیار خاصی از ناوبری را ارائه دهید، مانند آنچه در انیمیشن زیر نشان داده شده است:

انیمیشن یک صفحه نمایش که دو ستون از دکمه‌ها را در کنار هم نشان می‌دهد و فوکوس را از یک ستون به ستون دیگر متحرک می‌کند.
شکل 3 . متحرک صفحه نمایش که دو ستون از دکمه ها را در کنار یکدیگر نشان می دهد و فوکوس را از یک ستون به ستون دیگر متحرک می کند.

قبل از اینکه به نحوه ایجاد آن بپردازیم، مهم است که رفتار پیش‌فرض جستجوی فوکوس را درک کنیم. بدون هیچ تغییری، هنگامی که جستجوی فوکوس به آیتم Clickable 3 رسید، با فشار دادن DOWN روی D-Pad (یا کلید پیکان معادل) تمرکز را به هر چیزی که در زیر Column نمایش داده می شود منتقل می کند، از گروه خارج می شود و مورد سمت راست نادیده می گیرد. . اگر آیتم های قابل فوکوس در دسترس نباشد، فوکوس به جایی منتقل نمی شود، اما روی Clickable 3 باقی می ماند.

برای تغییر این رفتار و ارائه پیمایش مورد نظر، می‌توانید از اصلاح‌کننده focusProperties استفاده کنید، که به شما کمک می‌کند هنگام ورود یا خروج جستجوی فوکوس Composable مدیریت کنید که چه اتفاقی می‌افتد:

val otherComposable = remember { FocusRequester() }

Modifier.focusProperties {
    exit = { focusDirection ->
        when (focusDirection) {
            Right -> Cancel
            Down -> otherComposable
            else -> Default
        }
    }
}

ممکن است هر زمان که یک Composable به بخش خاصی از سلسله مراتب وارد شد یا از آن خارج شد، فوکوس را به سمت یک Composable خاص هدایت کرد - به عنوان مثال، زمانی که UI شما دو ستون دارد و می خواهید مطمئن شوید که هر زمان که اولین مورد پردازش شد، فوکوس به دوم:

انیمیشن یک صفحه نمایش که دو ستون از دکمه‌ها را در کنار هم نشان می‌دهد و فوکوس را از یک ستون به ستون دیگر متحرک می‌کند.
شکل 4 . متحرک صفحه نمایش که دو ستون از دکمه ها را در کنار یکدیگر نشان می دهد و فوکوس را از یک ستون به ستون دیگر متحرک می کند.

در این گیف، هنگامی که فوکوس Clickable 3 Composable در Column 1 رسید، مورد بعدی که تمرکز می شود، Clickable 4 در Column دیگر است. این رفتار را می توان با ترکیب focusDirection با مقادیر enter و exit درون اصلاح کننده focusProperties بدست آورد. هر دوی آنها به یک لامبدا نیاز دارند که به عنوان پارامتر جهتی را که فوکوس از آن می‌آید گرفته و یک FocusRequester برمی‌گرداند. این لامبدا می تواند به سه روش مختلف رفتار کند: بازگشت FocusRequester.Cancel از ادامه فوکوس جلوگیری می کند، در حالی که FocusRequester.Default رفتار آن را تغییر نمی دهد. در عوض ارائه FocusRequester متصل به Composable دیگر باعث می‌شود تمرکز به آن Composable خاص بپرد.

جهت پیشبرد تمرکز را تغییر دهید

برای پیشبرد فوکوس به مورد بعدی یا به سمت یک جهت دقیق، می‌توانید از اصلاح‌کننده onPreviewKey استفاده کنید و LocalFocusManager برای پیشبرد فوکوس با Modifier moveFocus استفاده کنید.

مثال زیر رفتار پیش‌فرض مکانیسم فوکوس را نشان می‌دهد: هنگامی که یک کلید tab تشخیص داده می‌شود، فوکوس به عنصر بعدی در لیست فوکوس می‌رود. اگرچه این چیزی نیست که معمولاً نیاز به پیکربندی داشته باشید، مهم است که عملکرد درونی سیستم را بدانید تا بتوانید رفتار پیش فرض را تغییر دهید.

val focusManager = LocalFocusManager.current
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.onPreviewKeyEvent {
        when {
            KeyEventType.KeyUp == it.type && Key.Tab == it.key -> {
                focusManager.moveFocus(FocusDirection.Next)
                true
            }

            else -> false
        }
    }
)

در این نمونه، تابع focusManager.moveFocus() فوکوس را به آیتم مشخص شده یا جهتی که در پارامتر تابع ذکر شده است، پیش می برد.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}