Stilus ini memungkinkan pengguna berinteraksi dengan aplikasi dengan nyaman dan dengan akurasi yang tepat untuk membuat catatan, membuat sketsa, bekerja dengan aplikasi produktivitas, serta untuk bersantai dan bersenang-senang dengan game dan aplikasi hiburan.
Android dan ChromeOS menawarkan berbagai API untuk mem-build pengalaman stilus yang luar biasa ke dalam aplikasi. Class MotionEvent
memberikan informasi tentang interaksi pengguna dengan layar, termasuk tekanan stilus, orientasi, kemiringan, pengarahan kursor, dan deteksi telapak tangan. Grafis latensi rendah dan library prediksi gerakan meningkatkan rendering di layar stilus untuk memberikan pengalaman seperti pena dan kertas yang alami.
MotionEvent
Class MotionEvent
mewakili interaksi input pengguna seperti posisi dan gerakan pointer sentuh di layar. Untuk input stilus, MotionEvent
juga menampilkan data tekanan, orientasi, kemiringan, dan pengarahan kursor.
Data peristiwa
Untuk mengakses data MotionEvent
di aplikasi berbasis tampilan, siapkan onTouchListener:
Kotlin
val onTouchListener = View.OnTouchListener { view, event -> // Process motion event. }
Java
View.OnTouchListener listener = (view, event) -> { // Process motion event. };
Pemroses menerima objek MotionEvent
dari sistem sehingga aplikasi Anda dapat memprosesnya.
Objek MotionEvent
memberikan data yang terkait dengan aspek peristiwa UI berikut:
- Tindakan: Interaksi fisik dengan perangkat—menyentuh layar, menggerakkan kursor ke permukaan layar, mengarahkan kursor ke permukaan layar
- Pointer: ID objek yang berinteraksi dengan layar—jari, stilus, mouse
- Sumbu: Jenis data—koordinat x dan y, tekanan, kemiringan, orientasi, dan arahkan kursor (jarak)
Tindakan
Untuk menerapkan dukungan stilus, Anda harus memahami tindakan yang dilakukan pengguna.
MotionEvent
menyediakan berbagai konstanta ACTION
yang menentukan peristiwa gerakan. Tindakan paling penting untuk stilus mencakup hal-hal berikut:
Tindakan | Deskripsi |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN |
Pointer telah melakukan kontak dengan layar. |
ACTION_MOVE | Pointer bergerak di layar. |
ACTION_UP ACTION_POINTER_UP |
Pointer tidak lagi terhubung dengan layar |
ACTION_CANCEL | Saat kumpulan gerakan sebelumnya atau saat ini harus dibatalkan. |
Aplikasi Anda dapat melakukan tugas seperti memulai goresan baru saat ACTION_DOWN
terjadi, menggambar goresan dengan ACTION_MOVE,
, dan menyelesaikan goresan saat ACTION_UP
dipicu.
Kumpulan tindakan MotionEvent
dari ACTION_DOWN
hingga ACTION_UP
untuk pointer tertentu disebut kumpulan gerakan.
Pointer
Sebagian besar layar multi-sentuh: sistem menetapkan pointer untuk setiap jari, stilus, mouse, atau objek pointer lainnya yang berinteraksi dengan layar. Indeks pointer memungkinkan Anda mendapatkan informasi sumbu untuk pointer tertentu, seperti posisi jari pertama yang menyentuh layar atau yang kedua.
Indeks pointer berkisar antara nol hingga jumlah pointer yang ditampilkan oleh MotionEvent#pointerCount()
dikurangi 1.
Nilai sumbu pointer dapat diakses dengan metode getAxisValue(axis, pointerIndex)
. Jika indeks pointer dihilangkan, sistem akan mengembalikan nilai untuk pointer pertama, pointer nol (0).
Objek MotionEvent
berisi informasi tentang jenis pointer yang digunakan. Anda bisa mendapatkan jenis pointer dengan melakukan iterasi melalui indeks pointer dan memanggil metode getToolType(pointerIndex)
.
Untuk mempelajari pointer lebih lanjut, lihat Menangani gestur multi-kontrol.
Input stilus
Anda dapat memfilter input stilus dengan TOOL_TYPE_STYLUS
:
Kotlin
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Java
boolean isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex);
Stilus juga dapat melaporkan bahwa stilus digunakan sebagai penghapus dengan TOOL_TYPE_ERASER
:
Kotlin
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Java
boolean isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex);
Data sumbu stilus
ACTION_DOWN
dan ACTION_MOVE
memberikan data sumbu tentang stilus, yaitu koordinat x dan y, tekanan, orientasi, kemiringan, dan pengarahan kursor.
Untuk mengaktifkan akses ke data ini, MotionEvent
API menyediakan getAxisValue(int)
, dengan parameternya merupakan salah satu ID sumbu berikut:
Sumbu | Nilai yang ditampilkan getAxisValue() |
---|---|
AXIS_X |
Koordinat X dari suatu peristiwa gerakan. |
AXIS_Y |
Koordinat Y dari suatu peristiwa gerakan. |
AXIS_PRESSURE |
Untuk layar sentuh atau touchpad, tekanan diaplikasikan oleh jari, stilus, atau pointer lainnya. Untuk mouse atau trackball, 1 jika tombol utama ditekan, 0 jika sebaliknya. |
AXIS_ORIENTATION |
Untuk layar sentuh atau touchpad, orientasi jari, stilus, atau pointer lainnya terkait dengan bidang vertikal perangkat. |
AXIS_TILT |
Sudut kemiringan stilus dalam radian. |
AXIS_DISTANCE |
Jarak stilus dari layar. |
Misalnya, MotionEvent.getAxisValue(AXIS_X)
menampilkan koordinat x untuk pointer pertama.
Lihat juga Menangani gestur multi-kontrol.
Posisi
Anda dapat mengambil koordinat x dan y pointer dengan panggilan berikut:
MotionEvent#getAxisValue(AXIS_X)
atauMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
atauMotionEvent#getY()
Tekanan
Anda dapat mengambil tekanan pointer dengan panggilan berikut:
getAxisValue(AXIS_PRESSURE)
atau getPressure()
untuk pointer pertama.
Nilai tekanan untuk layar sentuh atau touchpad adalah nilai antara 0 (tanpa tekanan) dan 1, tetapi nilai yang lebih tinggi dapat ditampilkan, bergantung pada kalibrasi layar.
Orientasi
Orientasi menunjukkan arah stilus yang ditunjuk.
Orientasi pointer dapat diambil menggunakan getAxisValue(AXIS_ORIENTATION)
atau getOrientation()
(untuk pointer pertama).
Untuk stilus, orientasi ditampilkan sebagai nilai radian antara 0 hingga pi (π) searah jarum jam atau 0 hingga -pi berlawanan arah jarum jam.
Orientasi memungkinkan Anda menerapkan kuas sungguhan. Misalnya, jika stilus mewakili kuas datar, lebar kuas datar bergantung pada orientasi stilus.
Kemiringan
Kemiringan mengukur kemiringan stilus relatif terhadap layar.
Kemiringan menampilkan sudut positif stilus dalam radian, dengan nol tegak lurus terhadap layar dan π/2 datar di permukaan.
Sudut kemiringan dapat diambil menggunakan getAxisValue(AXIS_TILT)
(tanpa pintasan untuk pointer pertama).
Kemiringan dapat digunakan untuk mereproduksi alat di dunia nyata semirip mungkin, seperti meniru bayangan dengan pensil miring.
Melayang
Jarak stilus dari layar dapat diperoleh dengan getAxisValue(AXIS_DISTANCE)
. Metode ini menampilkan nilai dari 0.0, kontak dengan layar, ke nilai yang lebih tinggi saat stilus bergerak menjauh dari layar. Jarak melayang antara layar dan ujung pena (titik) stilus bergantung pada produsen layar dan stilus. Karena implementasinya dapat bervariasi, jangan mengandalkan nilai yang tepat untuk fungsi yang penting bagi aplikasi.
Stilus dalam keadaan melayang dapat digunakan untuk melihat pratinjau ukuran kuas atau menunjukkan bahwa tombol akan dipilih.
Catatan: Compose menawarkan sekumpulan elemen pengubah untuk mengubah status elemen UI:
hoverable
: Mengonfigurasi komponen agar dapat diarahkan menggunakan peristiwa masuk dan keluar pointer.indication
: Menggambar efek visual untuk komponen ini saat interaksi terjadi.
Penolakan telapak tangan, navigasi, dan input yang tidak diinginkan
Terkadang layar multi-sentuh dapat mencatat sentuhan yang tidak diinginkan, misalnya, saat pengguna secara alami meletakkan tangannya di layar untuk mendapatkan dukungan saat menulis tangan. Penolakan telapak tangan adalah mekanisme yang mendeteksi perilaku ini dan memberi tahu Anda bahwa kumpulan MotionEvent
terakhir harus dibatalkan.
Oleh karena itu, Anda harus menyimpan histori input pengguna agar sentuhan yang tidak diinginkan dapat dihapus dari layar dan input pengguna yang sah dapat dirender ulang.
ACTION_CANCEL dan FLAG_CANCELED
ACTION_CANCEL
dan FLAG_CANCELED
dirancang untuk menginformasikan bahwa MotionEvent
yang ditetapkan sebelumnya harus dibatalkan dari ACTION_DOWN
terakhir sehingga Anda misalnya, dapat mengurungkan goresan terakhir untuk aplikasi menggambar untuk pointer tertentu.
ACTION_CANCEL
Ditambahkan di Android 1.0 (API level 1)
ACTION_CANCEL
menunjukkan kumpulan peristiwa gerakan sebelumnya yang harus dibatalkan.
ACTION_CANCEL
dipicu saat salah satu dari hal berikut terdeteksi:
- Gestur navigasi
- Penolakan telapak tangan
Saat ACTION_CANCEL
dipicu, Anda harus mengidentifikasi pointer aktif dengan getPointerId(getActionIndex())
. Kemudian, hapus goresan yang dibuat dengan pointer tersebut dari histori input, dan render ulang scene.
FLAG_CANCELED
Ditambahkan di Android 13 (API level 33)
FLAG_CANCELED
menunjukkan bahwa pointer naik merupakan sentuhan yang tidak disengaja oleh pengguna. Flag ini biasanya disetel saat pengguna tanpa sengaja menyentuh layar, misalnya dengan menggenggam perangkat atau meletakkan telapak tangan di layar.
Anda mengakses nilai flag sebagai berikut:
Kotlin
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Java
boolean cancel = (event.getFlags() & FLAG_CANCELED) == FLAG_CANCELED;
Jika tanda ini disetel, Anda harus mengurungkan kumpulan MotionEvent
terakhir, dari ACTION_DOWN
terakhir dari pointer ini.
Seperti ACTION_CANCEL
, pointer dapat ditemukan dengan getPointerId(actionIndex)
.
Gestur layar penuh, tata letak layar penuh, dan navigasi
Jika aplikasi dalam mode layar penuh dan memiliki elemen yang dapat ditindaklanjuti di dekat tepi, seperti kanvas aplikasi menggambar atau membuat catatan, menggeser dari bagian bawah layar untuk menampilkan navigasi atau memindahkan aplikasi ke latar belakang dapat mengakibatkan sentuhan yang tidak diinginkan di atas kanvas.
Untuk mencegah gestur memicu sentuhan yang tidak diinginkan di aplikasi, Anda dapat memanfaatkan inset dan ACTION_CANCEL
.
Lihat juga Penolakan, navigasi, dan input yang tidak diinginkan di atas.
Gunakan metode setSystemBarsBehavior()
dan BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
dari WindowInsetsController
untuk mencegah gestur navigasi menyebabkan peristiwa sentuh yang tidak diinginkan:
Kotlin
// Configure the behavior of the hidden system bars. windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Java
// Configure the behavior of the hidden system bars. windowInsetsController.setSystemBarsBehavior( WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE );
Untuk mempelajari pengelolaan inset dan gestur lebih lanjut, lihat:
- Menyembunyikan kolom sistem untuk mode imersif
- Memastikan kompatibilitas dengan navigasi gestur
- Menampilkan konten layar penuh di aplikasi Anda
Latensi rendah
Latensi adalah waktu yang dibutuhkan oleh hardware, sistem, dan aplikasi untuk memproses dan merender input pengguna.
Latensi = pemrosesan input hardware dan OS + pemrosesan aplikasi + komposisi sistem + rendering hardware
Sumber latensi
- Mendaftarkan stilus dengan layar sentuh (hardware): Koneksi nirkabel awal saat stilus dan OS berkomunikasi untuk didaftarkan dan disinkronkan.
- Frekuensi sampling sentuhan (hardware): jumlah per detik layar sentuh akan memeriksa apakah pointer menyentuh permukaan, berkisar antara 60 hingga 1000 Hz.
- Pemrosesan input (aplikasi): Menerapkan warna, efek grafis, dan transformasi pada input pengguna.
- Rendering grafis (OS + hardware): Pertukaran buffer, pemrosesan hardware.
Grafis latensi rendah
Library grafis latensi rendah Jetpack mengurangi waktu pemrosesan antara input pengguna dan rendering di layar.
Library mengurangi waktu pemrosesan dengan menghindari rendering multi-buffer dan memanfaatkan teknik rendering buffer depan sehingga dapat menulis langsung ke layar.
Rendering buffer depan
Buffer depan adalah memori yang digunakan layar untuk rendering. Rendering buffer depan ini merupakan aplikasi terdekat yang bisa menggambar secara langsung ke layar. Library latensi rendah memungkinkan aplikasi merender langsung ke buffer depan. Dengan ini, performa akan meningkat dengan cara mencegah pertukaran buffering, yang dapat terjadi untuk rendering multi-buffer reguler atau rendering buffer ganda (kasus yang paling umum).
Meskipun rendering buffer depan merupakan teknik yang baik untuk merender area kecil di layar, rendering ini tidak dirancang untuk memuat ulang seluruh layar. Dengan rendering buffer depan, aplikasi dapat merender konten ke buffer tempat layar dibaca. Karenanya, ada kemungkinan rendering artefak atau tearing (lihat di bawah).
Library latensi rendah tersedia dari Android 10 (API level 29) dan yang lebih tinggi, serta di perangkat ChromeOS yang menjalankan Android 10 (API level 29) dan yang lebih tinggi.
Dependensi
Library latensi rendah menyediakan komponen untuk implementasi rendering buffer depan. Library ini ditambahkan sebagai dependensi dalam file build.gradle
modul aplikasi:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Callback GLFrontBufferRenderer
Library latensi rendah menyertakan antarmuka GLFrontBufferRenderer.Callback
, yang menentukan metode berikut:
Library latensi rendah tidak memiliki opini terkait jenis data yang Anda gunakan dengan GLFrontBufferRenderer
.
Namun, library ini akan memproses data sebagai aliran dari ratusan titik data; oleh karena itu, desain data Anda agar dapat mengoptimalkan penggunaan dan alokasi memori.
Callback
Untuk mengaktifkan callback rendering, terapkan GLFrontBufferedRenderer.Callback
lalu ganti onDrawFrontBufferedLayer()
dan onDrawDoubleBufferedLayer()
. GLFrontBufferedRenderer
menggunakan callback untuk merender data Anda dengan cara yang paling optimal.
Kotlin
val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> { override fun onDrawFrontBufferedLayer( eglManager: EGLManager, bufferInfo: BufferInfo, transform: FloatArray, param: DATA_TYPE ) { // OpenGL for front buffer, short, affecting small area of the screen. } override fun onDrawMultiDoubleBufferedLayer( eglManager: EGLManager, bufferInfo: BufferInfo, transform: FloatArray, params: Collection<DATA_TYPE> ) { // OpenGL full scene rendering. } }
Java
GLFrontBufferedRenderer.Callback<DATA_TYPE> callbacks = new GLFrontBufferedRenderer.Callback<DATA_TYPE>() { @Override public void onDrawFrontBufferedLayer(@NonNull EGLManager eglManager, @NonNull BufferInfo bufferInfo, @NonNull float[] transform, DATA_TYPE data_type) { // OpenGL for front buffer, short, affecting small area of the screen. } @Override public void onDrawDoubleBufferedLayer(@NonNull EGLManager eglManager, @NonNull BufferInfo bufferInfo, @NonNull float[] transform, @NonNull Collection<? extends DATA_TYPE> collection) { // OpenGL full scene rendering. } };
Mendeklarasikan instance GLFrontBufferedRenderer
Siapkan GLFrontBufferedRenderer
dengan menyediakan SurfaceView
dan callback yang Anda buat sebelumnya. GLFrontBufferedRenderer
mengoptimalkan rendering ke buffer depan dan ganda menggunakan callback:
Kotlin
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Java
GLFrontBufferedRenderer<DATA_TYPE> glFrontBufferRenderer = new GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks);
Rendering
Rendering buffer depan dimulai saat Anda memanggil metode renderFrontBufferedLayer()
, yang memicu callback onDrawFrontBufferedLayer()
.
Rendering buffer ganda akan dilanjutkan saat Anda memanggil fungsi commit()
yang memicu callback onDrawMultiDoubleBufferedLayer()
.
Pada contoh berikut, proses dirender ke buffer depan (rendering cepat) saat pengguna mulai menggambar di layar (ACTION_DOWN
) dan menggerakkan pointer (ACTION_MOVE
). Proses merender ke buffer ganda saat pointer meninggalkan permukaan layar (ACTION_UP
).
Anda dapat menggunakan requestUnbufferedDispatch()
untuk meminta agar sistem input tidak mengelompokkan peristiwa gerakan, tetapi segera mengirimkannya setelah tersedia:
Kotlin
when (motionEvent.action) { MotionEvent.ACTION_DOWN -> { // Deliver input events as soon as they arrive. view.requestUnbufferedDispatch(motionEvent) // Pointer is in contact with the screen. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE) } MotionEvent.ACTION_MOVE -> { // Pointer is moving. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE) } MotionEvent.ACTION_UP -> { // Pointer is not in contact in the screen. glFrontBufferRenderer.commit() } MotionEvent.CANCEL -> { // Cancel front buffer; remove last motion set from the screen. glFrontBufferRenderer.cancel() } }
Java
switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { // Deliver input events as soon as they arrive. surfaceView.requestUnbufferedDispatch(motionEvent); // Pointer is in contact with the screen. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE); } break; case MotionEvent.ACTION_MOVE: { // Pointer is moving. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE); } break; case MotionEvent.ACTION_UP: { // Pointer is not in contact in the screen. glFrontBufferRenderer.commit(); } break; case MotionEvent.ACTION_CANCEL: { // Cancel front buffer; remove last motion set from the screen. glFrontBufferRenderer.cancel(); } break; }
Merendering anjuran dan larangan
Anjuran
Sebagian kecil layar, tulisan tangan, gambar, dan sketsa.
Larangan
Pembaruan layar penuh, penggeseran, zoom. Dapat mengakibatkan tearing.
Tearing
Tearing terjadi saat layar dimuat ulang saat buffer layar diubah secara bersamaan. Sebagian layar menampilkan data baru, sementara layar yang lain menampilkan data lama.
Prediksi gerakan
Library prediksi gerakan Jetpack mengurangi latensi yang dirasakan dengan memperkirakan jalur goresan yang dibuat pengguna dan menyediakan titik buatan sementara ke perender.
Library prediksi gerakan mendapatkan input pengguna yang sebenarnya sebagai objek MotionEvent
. Objek ini berisi informasi tentang koordinat x dan y, tekanan, serta waktu, yang dimanfaatkan oleh prediktor gerakan untuk memprediksi objek MotionEvent
di masa mendatang.
Objek MotionEvent
yang diprediksi hanyalah perkiraan. Peristiwa yang diprediksi dapat mengurangi latensi yang dirasakan, tetapi data yang diprediksi harus diganti dengan data MotionEvent
sebenarnya setelah diterima.
Library prediksi gerakan tersedia mulai dari Android 4.4 (API level 19) dan yang lebih tinggi, serta di perangkat ChromeOS yang menjalankan Android 9 (API level 28) dan yang lebih tinggi.
Dependensi
Library prediksi gerakan menyediakan implementasi prediksi. Library ini ditambahkan sebagai dependensi dalam file build.gradle
modul aplikasi:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementasi
Library prediksi gerakan menyertakan antarmuka MotionEventPredictor
yang menentukan metode berikut:
record()
: Menyimpan objekMotionEvent
sebagai catatan tindakan penggunapredict()
: Menampilkan prediksiMotionEvent
Deklarasikan instance MotionEventPredictor
Kotlin
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Java
MotionEventPredictor motionEventPredictor = MotionEventPredictor.newInstance(surfaceView);
Melakukan feed pada prediktor menggunakan data
Kotlin
motionEventPredictor.record(motionEvent)
Java
motionEventPredictor.record(motionEvent);
Prediksi
Kotlin
when (motionEvent.action) { MotionEvent.ACTION_MOVE -> { val predictedMotionEvent = motionEventPredictor?.predict() if(predictedMotionEvent != null) { // use predicted MotionEvent to inject a new artificial point } } }
Java
switch (motionEvent.getAction()) { case MotionEvent.ACTION_MOVE: { MotionEvent predictedMotionEvent = motionEventPredictor.predict(); if(predictedMotionEvent != null) { // use predicted MotionEvent to inject a new artificial point } } break; }
Anjuran dan larangan terkait gerakan
Anjuran
Hapus titik prediksi jika titik prediksi baru ditambahkan.
Larangan
Jangan gunakan titik prediksi untuk rendering akhir.
Aplikasi pencatatan
ChromeOS memungkinkan aplikasi Anda mendeklarasikan beberapa tindakan pencatatan.
Untuk mendaftarkan aplikasi sebagai aplikasi pencatat di ChromeOS, lihat Kompatibilitas input.
Untuk mendaftarkan aplikasi sebagai pembuat catatan di Android, lihat Membuat aplikasi pencatat.
Android 14 (API level 34), memperkenalkan intent ACTION_CREATE_NOTE
yang memungkinkan aplikasi Anda memulai aktivitas pencatatan di layar kunci.
Pengenalan tinta digital dengan ML Kit
Dengan pengenalan tinta digital ML Kit, aplikasi Anda dapat mengenali teks tulisan tangan pada platform digital dalam ratusan bahasa. Anda juga dapat mengklasifikasikan sketsa.
ML Kit menyediakan class Ink.Stroke.Builder
untuk membuat objek Ink
yang dapat diproses oleh model machine learning sehingga tulisan tangan dapat dikonversi menjadi teks.
Selain pengenalan tulis tangan, model ini dapat mengenali gestur, seperti hapus dan lingkaran.
Lihat Pengenalan tinta digital untuk mempelajari lebih lanjut.