Android dan ChromeOS menyediakan berbagai API untuk membantu Anda membangun aplikasi yang menawarkan
pengalaman stilus yang luar biasa kepada pengguna. Tujuan
Eksposur class MotionEvent
informasi tentang interaksi {i>stylus<i} dengan layar, termasuk tekanan {i>stylus<i},
orientasi, kemiringan, pengarahan kursor, dan deteksi telapak tangan. Grafis dan gerakan latensi rendah
library prediksi meningkatkan rendering stilus di layar untuk memberikan
pengalaman yang alami, seperti pena dan kertas.
MotionEvent
Class MotionEvent
mewakili interaksi input pengguna seperti posisi
dan pergerakan pointer sentuh di layar. Untuk input stilus, MotionEvent
juga menampilkan data tekanan, orientasi, kemiringan, dan pengarahan kursor.
Data peristiwa
Untuk mengakses data MotionEvent
, tambahkan pengubah pointerInput
ke komponen:
@Composable
fun Greeting() {
Text(
text = "Hello, Android!", textAlign = TextAlign.Center, style = TextStyle(fontSize = 5.em),
modifier = Modifier
.pointerInput(Unit) {
awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { println(it) }
}
}
},
)
}
Objek MotionEvent
memberikan data yang terkait dengan aspek UI berikut
acara:
- Tindakan: Interaksi fisik dengan perangkat—menyentuh layar, menggerakkan pointer ke permukaan layar, mengarahkan kursor ke layar permukaan
- 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 berperforma tinggi.
MotionEvent
menyediakan berbagai konstanta ACTION
yang menentukan gerakan
peristiwa. 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 ketika
ACTION_UP
dipicu.
Kumpulan tindakan MotionEvent
dari ACTION_DOWN
hingga ACTION_UP
untuk suatu
pointer disebut himpunan gerakan.
Pointer
Sebagian besar layar multi-sentuh: sistem menetapkan pointer untuk setiap jari, stilus, mouse, atau objek pointer lainnya yang berinteraksi dengan layar. Pointer Index 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()
kurang 1.
Nilai sumbu pointer dapat diakses dengan metode getAxisValue(axis,
pointerIndex)
.
Jika indeks pointer dihilangkan, sistem akan mengembalikan nilai untuk yang pertama
pointer, pointer nol (0).
Objek MotionEvent
berisi informasi tentang jenis pointer yang digunakan. Anda
bisa mendapatkan tipe pointer dengan melakukan iterasi melalui indeks pointer dan memanggil
tindakan
getToolType(pointerIndex)
.
Untuk mempelajari pointer lebih lanjut, lihat Menangani multi-sentuh gestur.
Input stilus
Anda dapat memfilter input stilus dengan
TOOL_TYPE_STYLUS
:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Stilus juga dapat melaporkan bahwa stilus digunakan sebagai penghapus dengan
TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Data sumbu stilus
ACTION_DOWN
dan ACTION_MOVE
menyediakan data sumbu tentang stilus, yaitu x dan
koordinat y, tekanan, orientasi, kemiringan, dan pengarahan kursor.
Untuk mengaktifkan akses ke data ini, MotionEvent
API menyediakan
getAxisValue(int)
,
dengan parameternya adalah 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 multi-sentuh gestur.
Posisi
Anda dapat mengambil koordinat x dan y pointer dengan panggilan berikut:
MotionEvent#getAxisValue(AXIS_X)
atauMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
atauMotionEvent#getY()
![Gambar stilus di layar dengan koordinat x dan y yang dipetakan.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_x_y_coordinates.png?authuser=8&hl=id)
Tekanan
Anda dapat mengambil tekanan {i>pointer<i} dengan
MotionEvent#getAxisValue(AXIS_PRESSURE)
, atau untuk pointer pertama,
MotionEvent#getPressure()
.
Nilai tekanan untuk layar sentuh atau touchpad adalah nilai antara 0 (tidak tekanan) dan 1, namun nilai yang lebih tinggi dapat dikembalikan tergantung pada layar untuk kalibrasi.
![Goresan stilus yang merepresentasikan kontinum tekanan rendah hingga tinggi. Gaya goresan sempit dan lemah di sebelah kiri, menunjukkan tekanan rendah. Goresan menjadi lebih lebar dan lebih gelap dari kiri ke kanan hingga menjadi paling luas dan paling gelap di ujung kanan, menunjukkan tekanan tertinggi.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_pressure.png?authuser=8&hl=id)
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 ke -pi berlawanan arah jarum jam.
Orientasi memungkinkan Anda menerapkan kuas sungguhan. Misalnya, jika {i>stylus <i}mewakili kuas datar, lebar kuas datar tergantung pada orientasi stilus.
![](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_orientation.png?authuser=8&hl=id)
Kemiringan
Kemiringan mengukur kemiringan stilus relatif terhadap layar.
Kemiringan menampilkan sudut positif stilus dalam radian, dengan nol adalah 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.
![Stilus miring ke sekitar 40 derajat dari permukaan layar.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_tilt.png?authuser=8&hl=id)
Arahkan kursor
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. Pengarahan kursor
jarak antara layar dan ujung pena (titik) stilus bergantung pada
produsen layar dan stilus. Karena implementasi 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 akan dipilih.
![](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_hover.png?authuser=8&hl=id)
Catatan: Compose menyediakan pengubah yang memengaruhi status interaktif 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, ketika
pengguna secara alami meletakkan tangannya di layar
untuk penyangga 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
adalah
keduanya dirancang untuk menginformasikan kepada Anda bahwa
kumpulan MotionEvent
sebelumnya harus
dibatalkan dari ACTION_DOWN
terakhir, sehingga Anda dapat, misalnya, mengurungkan
{i>stroke<i} 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. Penandanya adalah
biasanya disetel ketika pengguna secara tidak sengaja
menyentuh layar, misalnya dengan menggenggam
perangkat atau meletakkan telapak tangan di layar.
Anda mengakses nilai flag sebagai berikut:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Jika tanda ini disetel, Anda harus mengurungkan kumpulan MotionEvent
terakhir, dari
ACTION_DOWN
dari pointer ini.
Seperti ACTION_CANCEL
, pointer dapat ditemukan dengan getPointerId(actionIndex)
.
MotionEvent
. Sentuhan telapak tangan dibatalkan, dan layar dirender ulang.
Layar penuh, tata letak layar penuh, dan gestur navigasi
Jika aplikasi dalam mode layar penuh dan memiliki elemen yang dapat ditindaklanjuti di dekat tepi, seperti kanvas aplikasi menggambar atau pencatat, menggeser dari bagian bawah layar untuk menampilkan navigasi atau memindahkan aplikasi ke latar belakang dapat mengakibatkan sentuhan yang tidak diinginkan pada kanvas.
Untuk mencegah gestur memicu sentuhan yang tidak diinginkan di aplikasi, Anda dapat melakukan
keunggulan inset dan
ACTION_CANCEL
.
Lihat juga Penolakan, navigasi, dan input yang tidak diinginkan bagian.
Gunakan
setSystemBarsBehavior()
metode dan
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
pengguna
WindowInsetsController
untuk mencegah gestur navigasi menyebabkan peristiwa sentuh yang tidak diinginkan:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
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
![Latensi menyebabkan goresan yang dirender tertinggal di belakang posisi stilus. Jeda antara goresan yang dirender dan posisi stilus mewakili latensi.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_latency.png?authuser=8&hl=id)
Sumber latensi
- Mendaftarkan stilus dengan layar sentuh (hardware): Koneksi nirkabel awal saat stilus dan OS berkomunikasi untuk didaftarkan dan disinkronkan.
- Frekuensi sampling sentuhan (hardware): Frekuensi layar sentuh per detik memeriksa apakah pointer menyentuh permukaan, berkisar antara 60 hingga 1000 Hz.
- Pemrosesan input (aplikasi): Menerapkan warna, efek grafis, dan transformasi 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, yang berarti menulis langsung ke layar.
Rendering buffer depan
Buffer depan adalah memori yang digunakan layar untuk rendering. Ini adalah yang terdekat aplikasi bisa menggambar langsung ke layar. Library latensi rendah memungkinkan aplikasi untuk merender langsung ke buffer depan. Hal ini meningkatkan performa dengan mencegah pertukaran buffer, yang dapat terjadi untuk rendering multi-buffer reguler atau rendering buffer ganda (kasus yang paling umum).
![Aplikasi menulis ke buffer layar dan membaca dari buffer layar.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/front_buffer_rendering.png?authuser=8&hl=id)
![Aplikasi menulis ke multi-buffer yang ditukar dengan buffer layar. Aplikasi membaca dari buffer layar.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/multi-buffer_rendering.png?authuser=8&hl=id)
Meskipun rendering buffer depan merupakan teknik yang baik untuk merender area kecil dari layar, tidak dirancang untuk digunakan untuk memuat ulang seluruh layar. Dengan rendering buffer depan, aplikasi sedang merender konten ke buffer layar sedang membaca. Akibatnya, ada kemungkinan rendering artefak atau tearing (lihat di bawah).
Library latensi rendah tersedia dari Android 10 (API level 29) dan yang lebih tinggi dan di perangkat ChromeOS yang menjalankan Android 10 (level API 29) dan yang lebih tinggi.
Dependensi
Library latensi rendah menyediakan komponen untuk rendering buffer depan
terlepas dari implementasi layanan. Library ditambahkan sebagai dependensi dalam modul aplikasi
File build.gradle
:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Callback GLFrontBufferRenderer
Library latensi rendah menyertakan
GLFrontBufferRenderer.Callback
, yang menentukan metode berikut:
Library latensi rendah tidak memiliki opini terkait jenis data yang Anda gunakan dengan
GLFrontBufferRenderer
.
Namun, library ini memproses data sebagai aliran dari ratusan titik data; dan kemudian, rancang data Anda untuk mengoptimalkan penggunaan dan alokasi memori.
Callback
Untuk mengaktifkan callback rendering, terapkan GLFrontBufferedRenderer.Callback
dan
menggantikan onDrawFrontBufferedLayer()
dan onDrawDoubleBufferedLayer()
.
GLFrontBufferedRenderer
menggunakan callback untuk merender data Anda
seoptimal mungkin.
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.
}
}
Mendeklarasikan instance GLFrontBufferedRenderer
Siapkan GLFrontBufferedRenderer
dengan menyediakan SurfaceView
dan
yang telah Anda buat sebelumnya. GLFrontBufferedRenderer
mengoptimalkan rendering
ke buffer depan dan ganda menggunakan callback Anda:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Rendering
Rendering buffer depan dimulai saat Anda memanggil
renderFrontBufferedLayer()
, yang memicu callback onDrawFrontBufferedLayer()
.
Rendering buffer ganda dilanjutkan saat Anda memanggil
commit()
, yang akan memicu callback onDrawMultiDoubleBufferedLayer()
.
Dalam contoh berikut, proses merender ke buffer depan (cepat
rendering) saat pengguna mulai menggambar di layar (ACTION_DOWN
) dan bergerak
kursor di sekitar (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 kejadian gerakan tetapi mengirimkan
segera setelah tersedia:
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()
}
}
Merendering anjuran dan larangan
Sebagian kecil layar, tulisan tangan, gambar, dan sketsa.
Pembaruan layar penuh, penggeseran, zoom. Dapat mengakibatkan tearing.
Tearing
Tearing terjadi saat layar dimuat ulang saat buffer layar sedang dimodifikasi secara bersamaan. Sebagian layar menampilkan data baru, sementara layar yang lain menampilkan data lama.
![Bagian atas dan bawah gambar Android tidak sejajar karena adanya tearing saat layar dimuat ulang.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/tearing.png?authuser=8&hl=id)
Prediksi gerakan
Prediksi gerakan Jetpack library mengurangi latensi yang dirasakan dengan memperkirakan jalur {i>stroke<i} pengguna dan memberikan titik buatan ke perender.
Library prediksi gerakan mendapatkan input pengguna yang sebenarnya sebagai objek MotionEvent
.
Objek berisi informasi tentang koordinat x dan y, tekanan, dan waktu,
yang dimanfaatkan oleh prediktor gerakan untuk memprediksi MotionEvent
mendatang
objek terstruktur dalam jumlah besar.
Objek MotionEvent
yang diprediksi hanyalah perkiraan. Peristiwa yang diprediksi dapat mengurangi
latensi yang dirasakan, tetapi data yang diprediksi harus diganti dengan MotionEvent
yang sebenarnya
data setelah diterima.
Library prediksi gerakan tersedia mulai dari Android 4.4 (API level 19) dan yang lebih tinggi dan di perangkat ChromeOS yang menjalankan Android 9 (level API 28) dan yang lebih tinggi.
![Latensi menyebabkan goresan yang dirender tertinggal di belakang posisi stilus. Jeda antara goresan dan stilus diisi dengan titik prediksi. Jeda yang tersisa adalah latensi yang dirasakan.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_reduced_latency.png?authuser=8&hl=id)
Dependensi
Library prediksi gerakan menyediakan implementasi prediksi. Tujuan
library ditambahkan sebagai dependensi dalam file build.gradle
modul aplikasi:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementasi
Library prediksi gerakan menyertakan
MotionEventPredictor
, yang menentukan metode berikut:
record()
: Menyimpan objekMotionEvent
sebagai catatan tindakan penggunapredict()
: Menampilkan prediksiMotionEvent
Deklarasikan instance MotionEventPredictor
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Melakukan feed pada prediktor menggunakan data
motionEventPredictor.record(motionEvent)
Prediksi
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
Anjuran dan larangan terkait gerakan
Hapus titik prediksi jika titik prediksi baru ditambahkan.
Jangan gunakan titik prediksi untuk rendering akhir.
Aplikasi pencatatan
ChromeOS memungkinkan aplikasi Anda mendeklarasikan beberapa tindakan pencatatan.
Untuk mendaftarkan aplikasi sebagai aplikasi pencatatan di ChromeOS, lihat Input kompatibilitas yang berbeda.
Untuk mendaftarkan aplikasi sebagai aplikasi pencatatan di Android, lihat Membuat pencatatan aplikasi.
Android 14 (level API 34), memperkenalkan
ACTION_CREATE_NOTE
, yang memungkinkan aplikasi Anda memulai aktivitas pencatatan pada kunci
layar.
Pengenalan tinta digital dengan ML Kit
Dengan tinta digital ML Kit pengenalan objek, aplikasi Anda dapat mengenali teks tulisan tangan pada platform digital dalam ratusan bahasa. Anda juga dapat mengklasifikasikan sketsa.
ML Kit menyediakan
Ink.Stroke.Builder
class untuk membuat objek Ink
yang dapat diproses oleh model machine learning
untuk mengonversi tulisan tangan menjadi teks.
Selain pengenalan tulis tangan, model ini dapat mengenali gestur, seperti hapus dan lingkari.
Lihat Tinta digital pengenalan untuk mempelajari lebih lanjut.