نمای کلی رویدادهای ورودی

روش نوشتن را امتحان کنید
Jetpack Compose ابزار رابط کاربری پیشنهادی برای اندروید است. یاد بگیرید که چگونه از لمس و ورودی در Compose استفاده کنید.

در اندروید، بیش از یک راه برای رهگیری رویدادها از تعامل کاربر با برنامه شما وجود دارد. هنگام بررسی رویدادها در رابط کاربری خود، رویکرد این است که رویدادها را از شیء View خاصی که کاربر با آن تعامل دارد، ضبط کنید. کلاس View ابزار انجام این کار را فراهم می‌کند.

در کلاس‌های مختلف View که برای ساخت طرح‌بندی خود استفاده خواهید کرد، ممکن است متوجه چندین متد فراخوانی عمومی شوید که برای رویدادهای رابط کاربری مفید به نظر می‌رسند. این متدها توسط چارچوب اندروید هنگامی که عمل مربوطه روی آن شیء رخ می‌دهد، فراخوانی می‌شوند. به عنوان مثال، هنگامی که یک View (مانند یک Button) لمس می‌شود، متد onTouchEvent() روی آن شیء فراخوانی می‌شود. با این حال، برای رهگیری این، باید کلاس را گسترش داده و متد را لغو کنید. با این حال، گسترش هر شیء View به منظور مدیریت چنین رویدادی عملی نخواهد بود. به همین دلیل است که کلاس View همچنین شامل مجموعه‌ای از رابط‌های تو در تو با فراخوانی‌هایی است که می‌توانید آنها را بسیار راحت‌تر تعریف کنید. این رابط‌ها، که شنونده‌های رویداد نامیده می‌شوند، بلیط شما برای ثبت تعامل کاربر با رابط کاربری شما هستند.

اگرچه شما معمولاً از شنونده‌های رویداد برای گوش دادن به تعامل کاربر استفاده می‌کنید، اما ممکن است زمانی پیش بیاید که بخواهید یک کلاس View را به منظور ساخت یک کامپوننت سفارشی گسترش دهید. شاید بخواهید کلاس Button را به منظور ایجاد چیزی شیک‌تر گسترش دهید. در این حالت، می‌توانید با استفاده از کنترل‌کننده‌های رویداد کلاس، رفتارهای رویداد پیش‌فرض را برای کلاس خود تعریف کنید.

شنوندگان رویداد

یک شنونده‌ی رویداد، رابطی در کلاس View است که شامل یک متد فراخوانی واحد است. این متدها توسط چارچوب اندروید زمانی فراخوانی می‌شوند که View که شنونده در آن ثبت شده است، توسط تعامل کاربر با آیتم در رابط کاربری فعال شود.

رابط‌های شنونده رویداد شامل متدهای فراخوانی زیر هستند:

onClick()
From View.OnClickListener . این زمانی فراخوانی می‌شود که کاربر یا آیتم را لمس می‌کند (در حالت لمسی)، یا با کلیدهای جهت‌یابی یا گوی لمسی روی آیتم فوکوس می‌کند و کلید "enter" مناسب را فشار می‌دهد یا گوی لمسی را فشار می‌دهد.
onLongClick()
From View.OnLongClickListener . این زمانی فراخوانی می‌شود که کاربر یا آیتم را لمس کرده و نگه می‌دارد (در حالت لمسی)، یا با کلیدهای جهت‌یابی یا گوی لمسی روی آیتم تمرکز می‌کند و کلید "enter" مناسب را فشار داده و نگه می‌دارد یا گوی لمسی را فشار داده و نگه می‌دارد (برای یک ثانیه).
onFocusChange()
From View.OnFocusChangeListener . این متد زمانی فراخوانی می‌شود که کاربر با استفاده از کلیدهای ناوبری یا ترک‌بال، به سمت آیتم مورد نظر حرکت می‌کند یا از آن دور می‌شود.
onKey()
From View.OnKeyListener . این تابع زمانی فراخوانی می‌شود که کاربر روی آیتم مورد نظر تمرکز کرده و یک کلید سخت‌افزاری روی دستگاه را فشار داده یا رها می‌کند.
onTouch()
From View.OnTouchListener . این زمانی فراخوانی می‌شود که کاربر عملی را که به عنوان یک رویداد لمسی تعریف شده است، انجام دهد، از جمله فشار دادن، رها کردن یا هر حرکت دیگری روی صفحه (در محدوده آیتم).
onCreateContextMenu()
From View.OnCreateContextMenuListener . این زمانی فراخوانی می‌شود که یک منوی زمینه (Context Menu) در حال ساخت است (در نتیجه‌ی یک "کلیک طولانی" مداوم). به بحث در مورد منوهای زمینه در راهنمای توسعه‌دهندگان منوها مراجعه کنید.

این متدها تنها ساکنین رابط مربوطه خود هستند. برای تعریف یکی از این متدها و مدیریت رویدادهای خود، رابط تو در تو را در Activity خود پیاده‌سازی کنید یا آن را به عنوان یک کلاس ناشناس تعریف کنید. سپس، نمونه‌ای از پیاده‌سازی خود را به متد View.set...Listener() مربوطه ارسال کنید. (مثلاً، setOnClickListener() را فراخوانی کنید و پیاده‌سازی خود از OnClickListener را به آن ارسال کنید.)

مثال زیر نحوه ثبت یک شنونده کلیک برای یک دکمه (Button) را نشان می‌دهد.

کاتلین

protected void onCreate(savedValues: Bundle) {
    ...
    val button: Button = findViewById(R.id.corky)
    // Register the onClick listener with the implementation above
    button.setOnClickListener { view ->
        // do something when the button is clicked
    }
    ...
}

جاوا

// Create an anonymous implementation of OnClickListener
private OnClickListener corkyListener = new OnClickListener() {
    public void onClick(View v) {
      // do something when the button is clicked
    }
};

protected void onCreate(Bundle savedValues) {
    ...
    // Capture our button from layout
    Button button = (Button)findViewById(R.id.corky);
    // Register the onClick listener with the implementation above
    button.setOnClickListener(corkyListener);
    ...
}

همچنین ممکن است پیاده‌سازی OnClickListener به عنوان بخشی از Activity خود را راحت‌تر بیابید. این کار از بارگذاری کلاس اضافی و تخصیص شیء جلوگیری می‌کند. برای مثال:

کاتلین

class ExampleActivity : Activity(), OnClickListener {
  
    protected fun onCreate(savedValues: Bundle) {
        val button: Button = findViewById(R.id.corky)
        button.setOnClickListener(this)
    }

    // Implement the OnClickListener callback
    fun onClick(v: View) {
        // do something when the button is clicked
    }
}

جاوا

public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);
        button.setOnClickListener(this);
    }

    // Implement the OnClickListener callback
    public void onClick(View v) {
      // do something when the button is clicked
    }
    ...
}

توجه داشته باشید که تابع فراخوانی onClick() در مثال بالا هیچ مقدار بازگشتی ندارد، اما برخی از متدهای دیگر شنونده رویداد باید یک مقدار بولی (boolean) برگردانند. دلیل آن به رویداد بستگی دارد. برای تعداد کمی از آنها، دلیلش به شرح زیر است:

  • onLongClick() - این یک مقدار بولی برمی‌گرداند تا نشان دهد که آیا شما رویداد را مدیریت کرده‌اید و نباید بیشتر ادامه یابد. یعنی، مقدار true را برمی‌گرداند تا نشان دهد که شما رویداد را مدیریت کرده‌اید و باید در اینجا متوقف شود؛ اگر آن را مدیریت نکرده‌اید و/یا رویداد باید به هر شنونده‌ی کلیک دیگری ادامه یابد، مقدار false را برمی‌گرداند.
  • onKey() - این یک مقدار بولی برمی‌گرداند تا نشان دهد که آیا شما رویداد را مدیریت کرده‌اید و نباید بیشتر ادامه یابد. یعنی، مقدار true را برمی‌گرداند تا نشان دهد که شما رویداد را مدیریت کرده‌اید و باید در اینجا متوقف شود؛ اگر آن را مدیریت نکرده‌اید و/یا رویداد باید به هر شنونده on-key دیگری ادامه یابد، مقدار false را برمی‌گرداند.
  • onTouch() - این یک مقدار بولی برمی‌گرداند تا نشان دهد که آیا شنونده شما این رویداد را مصرف می‌کند یا خیر. نکته مهم این است که این رویداد می‌تواند چندین عمل داشته باشد که به دنبال یکدیگر می‌آیند. بنابراین، اگر هنگام دریافت رویداد اقدام down مقدار false را برگردانید، نشان می‌دهید که این رویداد را مصرف نکرده‌اید و همچنین به اقدامات بعدی از این رویداد علاقه‌ای ندارید. بنابراین، برای هیچ اقدام دیگری در این رویداد، مانند حرکت انگشت یا رویداد اقدام up نهایی، فراخوانی نخواهید شد.

به یاد داشته باشید که رویدادهای کلید سخت‌افزاری همیشه به Viewای که در حال حاضر در فوکوس است، ارسال می‌شوند. آن‌ها از بالای سلسله مراتب View شروع می‌شوند و سپس به پایین، تا زمانی که به مقصد مناسب برسند. اگر View شما (یا فرزند View شما) در حال حاضر فوکوس دارد، می‌توانید مسیر رویداد را از طریق متد dispatchKeyEvent() مشاهده کنید. به عنوان جایگزینی برای ثبت رویدادهای کلیدی از طریق View، می‌توانید تمام رویدادهای داخل Activity خود را با onKeyDown() و onKeyUp() نیز دریافت کنید.

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

نکته: اندروید ابتدا کنترل‌کننده‌های رویداد و سپس کنترل‌کننده‌های پیش‌فرض مناسب از تعریف کلاس را فراخوانی می‌کند. به این ترتیب، برگرداندن مقدار true از این شنونده‌های رویداد، انتشار رویداد به سایر شنونده‌های رویداد را متوقف می‌کند و همچنین فراخوانی کنترل‌کننده رویداد پیش‌فرض در View را مسدود می‌کند. بنابراین مطمئن شوید که می‌خواهید رویداد را هنگام برگرداندن مقدار true خاتمه دهید.

گرداننده‌های رویداد

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

متدهای دیگری هم وجود دارند که باید از آنها آگاه باشید، که بخشی از کلاس View نیستند، اما می‌توانند مستقیماً بر نحوه‌ی مدیریت رویدادها تأثیر بگذارند. بنابراین، هنگام مدیریت رویدادهای پیچیده‌تر در یک طرح‌بندی، این متدهای دیگر را نیز در نظر بگیرید:

حالت لمسی

وقتی کاربر با کلیدهای جهت‌نما یا ترک‌بال در حال پیمایش رابط کاربری است، لازم است که به موارد قابل اجرا (مانند دکمه‌ها) تمرکز داده شود تا کاربر بتواند ببیند چه چیزی ورودی را می‌پذیرد. با این حال، اگر دستگاه دارای قابلیت‌های لمسی باشد و کاربر با لمس رابط شروع به تعامل با آن کند، دیگر نیازی به برجسته کردن موارد یا تمرکز بر یک نمای خاص نیست. بنابراین، حالتی برای تعامل به نام "حالت لمسی" وجود دارد.

برای یک دستگاه لمسی، به محض اینکه کاربر صفحه را لمس کند، دستگاه وارد حالت لمسی می‌شود. از این نقطه به بعد، فقط نماهایی که isFocusableInTouchMode() برای آنها برابر با true است، قابل فوکوس خواهند بود، مانند ویجت‌های ویرایش متن. نماهای دیگری که قابل لمس هستند، مانند دکمه‌ها، هنگام لمس فوکوس نمی‌شوند. آنها به سادگی هنگام فشار دادن، شنونده‌های کلیک خود را فعال می‌کنند.

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

حالت لمسی در کل سیستم (تمام پنجره‌ها و فعالیت‌ها) حفظ می‌شود. برای پرس‌وجو از وضعیت فعلی، می‌توانید تابع isInTouchMode() را فراخوانی کنید تا ببینید آیا دستگاه در حال حاضر در حالت لمسی است یا خیر.

مدیریت تمرکز

این چارچوب، حرکت روتین فوکوس را در پاسخ به ورودی کاربر مدیریت می‌کند. این شامل تغییر فوکوس هنگام حذف یا پنهان شدن نماها یا هنگام در دسترس قرار گرفتن نماهای جدید می‌شود. نماها تمایل خود را برای گرفتن فوکوس از طریق متد isFocusable() نشان می‌دهند. برای تغییر اینکه آیا یک نما می‌تواند فوکوس بگیرد یا خیر، setFocusable() را فراخوانی کنید. وقتی در حالت لمسی هستید، می‌توانید با isFocusableInTouchMode() از یک نما بپرسید که آیا اجازه فوکوس می‌دهد یا خیر. می‌توانید این را با setFocusableInTouchMode() تغییر دهید.

در دستگاه‌هایی که اندروید ۹ (سطح API 28) یا بالاتر را اجرا می‌کنند، فعالیت‌ها فوکوس اولیه را تعیین نمی‌کنند. در عوض، در صورت تمایل، باید صریحاً درخواست فوکوس اولیه را بدهید.

حرکت فوکوس بر اساس الگوریتمی است که نزدیکترین همسایه را در جهت مشخص شده پیدا می‌کند. در موارد نادر، الگوریتم پیش‌فرض ممکن است با رفتار مورد نظر توسعه‌دهنده مطابقت نداشته باشد. در این شرایط، می‌توانید با استفاده از ویژگی‌های XML زیر در فایل طرح‌بندی، overrideهای صریح ارائه دهید: nextFocusDown ، nextFocusLeft ، nextFocusRight و nextFocusUp . یکی از این ویژگی‌ها را به نمایی که فوکوس از آن خارج می‌شود اضافه کنید. مقدار ویژگی را به عنوان شناسه نمایی که فوکوس باید به آن داده شود، تعریف کنید. به عنوان مثال:

<LinearLayout
    android:orientation="vertical"
    ... >
  <Button android:id="@+id/top"
          android:nextFocusUp="@+id/bottom"
          ... />
  <Button android:id="@+id/bottom"
          android:nextFocusDown="@+id/top"
          ... />
</LinearLayout>

معمولاً در این طرح عمودی، حرکت از دکمه اول به بالا و حرکت از دکمه دوم به پایین به جایی نمی‌رسد. اکنون که دکمه بالایی، دکمه پایینی را به عنوان دکمه nextFocusUp (و برعکس) تعریف کرده است، فوکوس ناوبری از بالا به پایین و از پایین به بالا تغییر می‌کند.

اگر می‌خواهید یک نما را در رابط کاربری خود به عنوان قابل فوکوس اعلام کنید (در حالی که به طور سنتی اینطور نیست)، ویژگی android:focusable XML را به نما، در اعلان طرح‌بندی خود اضافه کنید. مقدار آن را true قرار دهید. همچنین می‌توانید یک نما را در حالت لمسی با android:focusableInTouchMode به عنوان قابل فوکوس اعلام کنید.

برای درخواست فوکوس روی یک نمای خاص، requestFocus() را فراخوانی کنید.

برای گوش دادن به رویدادهای focus (اطلاع‌رسانی در مورد دریافت یا از دست دادن focus توسط یک View)، onFocusChange() همانطور که در بخش Event listeners بحث شده است، استفاده کنید.