Hỗ trợ các tính năng nâng cao của bút cảm ứng

Bút cảm ứng giúp người dùng tương tác với các ứng dụng một cách thoải mái và chính xác khi ghi chú, phác thảo, thao tác với các ứng dụng cải thiện hiệu suất, thư giãn và giải trí với những ứng dụng trò chơi cũng như ứng dụng giải trí.

Android và ChromeOS cung cấp nhiều API để tích hợp vào các ứng dụng trải nghiệm vượt trội khi dùng bút cảm ứng. Lớp MotionEvent cung cấp thông tin về hoạt động tương tác của người dùng với màn hình, bao gồm cả lực, hướng, độ nghiêng, khoảng cách di và khả năng phát hiện tì tay của bút cảm ứng. Các thư viện dự đoán chuyển động và đồ hoạ có độ trễ thấp giúp cải thiện khả năng kết xuất bút cảm ứng trên màn hình để mang lại trải nghiệm tự nhiên, giống như bút và giấy thật.

MotionEvent

Lớp MotionEvent biểu thị các hoạt động tương tác đầu vào của người dùng, chẳng hạn như vị trí và chuyển động của con trỏ cảm ứng trên màn hình. Đối với phương thức nhập bằng bút cảm ứng, MotionEvent cũng hiển thị dữ liệu về áp lực, hướng, độ nghiêng và khoảng cách di.

Dữ liệu sự kiện

Để truy cập dữ liệu MotionEvent trong các ứng dụng dựa trên khung hiển thị, hãy thiết lập một onTouchListener:

Kotlin

val onTouchListener = View.OnTouchListener { view, event ->
  // Process motion event.
}

Java

View.OnTouchListener listener = (view, event) -> {
  // Process motion event.
};

Trình nghe sẽ nhận các đối tượng MotionEvent từ hệ thống để ứng dụng của bạn có thể xử lý các đối tượng đó.

Đối tượng MotionEvent cung cấp dữ liệu liên quan đến các khía cạnh sau của một sự kiện trên giao diện người dùng:

  • Thao tác: Hoạt động tương tác vật lý với thiết bị – chạm vào màn hình, di chuyển con trỏ qua bề mặt màn hình, di con trỏ lên phía trên bề mặt màn hình
  • Con trỏ: Giá trị nhận dạng của các đối tượng tương tác với màn hình – ngón tay, bút cảm ứng, chuột
  • Trục: Loại dữ liệu – toạ độ x và y, áp lực, độ nghiêng, hướng và thao tác di (khoảng cách)

Thao tác

Để triển khai tính năng hỗ trợ bút cảm ứng, bạn cần nắm được người dùng đang thực hiện thao tác nào.

MotionEvent cung cấp nhiều hằng số ACTION để xác định sự kiện chuyển động. Sau đây là những thao tác quan trọng nhất đối với bút cảm ứng:

Hành động Nội dung mô tả
ACTION_DOWN
ACTION_POINTER_DOWN
Con trỏ đã tiếp xúc với màn hình.
ACTION_MOVE Con trỏ đang di chuyển trên màn hình.
ACTION_UP
ACTION_POINTER_UP
Con trỏ không tiếp xúc với màn hình nữa
ACTION_CANCEL Khi tập hợp chuyển động trước đó hoặc hiện tại bị huỷ.

Ứng dụng của bạn có thể thực hiện các thao tác như bắt đầu một nét vẽ mới khi ACTION_DOWN diễn ra, vẽ nét vẽ bằng ACTION_MOVE, và kết thúc nét vẽ khi ACTION_UP được kích hoạt.

Tập hợp các thao tác MotionEvent từ ACTION_DOWN đến ACTION_UP cho một con trỏ cụ thể được gọi là tập hợp chuyển động.

Con trỏ

Hầu hết màn hình đều có nhiều điểm chạm: hệ thống chỉ định một con trỏ cho từng ngón tay, bút cảm ứng, chuột hoặc đối tượng trỏ khác tương tác với màn hình. Chỉ mục con trỏ cho phép bạn nhận thông tin trục của một con trỏ cụ thể, chẳng hạn như vị trí của ngón tay thứ nhất chạm vào màn hình hoặc ngón tay thứ hai.

Chỉ mục con trỏ nằm trong khoảng từ 0 đến số lượng con trỏ do MotionEvent#pointerCount() trả về trừ đi 1.

Bạn có thể truy cập vào các giá trị trục của con trỏ bằng phương thức getAxisValue(axis, pointerIndex). Khi chỉ mục con trỏ bị bỏ qua, hệ thống sẽ trả về giá trị cho con trỏ đầu tiên, con trỏ 0.

Các đối tượng MotionEvent chứa thông tin về loại con trỏ đang sử dụng. Bạn có thể lấy loại con trỏ bằng cách lặp lại qua các chỉ mục con trỏ và gọi phương thức getToolType(pointerIndex).

Để tìm hiểu thêm về con trỏ, hãy xem bài viết Xử lý cử chỉ nhiều điểm chạm.

Phương thức nhập bằng bút cảm ứng

Bạn có thể lọc tìm phương thức nhập bằng bút cảm ứng thông qua TOOL_TYPE_STYLUS:

Kotlin

val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)

Java

boolean isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex);

Bút cảm ứng cũng có thể báo cáo rằng bút được dùng làm tẩy thông qua TOOL_TYPE_ERASER:

Kotlin

val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)

Java

boolean isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex);

Dữ liệu trục bút cảm ứng

ACTION_DOWNACTION_MOVE cung cấp dữ liệu trục về bút cảm ứng, cụ thể là các toạ độ x và y, áp lực, hướng, độ nghiêng và khoảng cách di.

Để cho phép truy cập vào dữ liệu này, API MotionEvent cung cấp getAxisValue(int), trong đó tham số là bất kỳ giá trị nhận dạng trục nào sau đây:

Trục Giá trị trả về của getAxisValue()
AXIS_X Toạ độ X của một sự kiện chuyển động.
AXIS_Y Toạ độ Y của một sự kiện chuyển động.
AXIS_PRESSURE Đối với màn hình cảm ứng hoặc bàn di chuột, áp lực chịu tác động của ngón tay, bút cảm ứng hoặc con trỏ khác. Đối với chuột hoặc bi xoay, giá trị áp lực sẽ là 1 nếu bạn nhấn nút chính, là 0 nếu không nhấn.
AXIS_ORIENTATION Đối với màn hình cảm ứng hoặc bàn di chuột, hướng của ngón tay, bút cảm ứng hoặc con trỏ khác so với mặt phẳng thẳng đứng của thiết bị.
AXIS_TILT Góc nghiêng của bút cảm ứng tính bằng radian.
AXIS_DISTANCE Khoảng cách của bút cảm ứng so với màn hình.

Ví dụ: MotionEvent.getAxisValue(AXIS_X) trả về toạ độ x của con trỏ đầu tiên.

Hãy xem thêm bài viết Xử lý cử chỉ nhiều điểm chạm.

Vị trí

Bạn có thể truy xuất toạ độ x và y của một con trỏ bằng các lệnh gọi sau:

Bút cảm ứng đang vẽ trên màn hình với toạ độ x và y được ánh xạ.
Hình 1. Toạ độ x và y trên màn hình của con trỏ bút cảm ứng.

Áp lực

Bạn có thể truy xuất áp lực của con trỏ bằng các lệnh gọi sau:

getAxisValue(AXIS_PRESSURE) hoặc getPressure() cho con trỏ đầu tiên.

Giá trị áp lực cho màn hình cảm ứng hoặc bàn di chuột là giá trị nằm trong khoảng từ 0 (không có áp lực) đến 1. Tuy nhiên, lệnh gọi có thể trả về giá trị cao hơn tuỳ thuộc vào việc hiệu chuẩn màn hình.

Nét vẽ bằng bút cảm ứng biểu thị miền liên tục của áp lực từ thấp đến cao. Nét vẽ hẹp và mờ ở bên trái, cho biết áp lực thấp. Nét vẽ ngày càng rộng hơn và đậm hơn từ trái sang phải cho đến khi rộng nhất và đậm nhất ở phía xa bên phải, cho biết áp lực cao nhất.
Hình 2. Hình biểu diễn lực – lực ít ở bên trái, lực nhiều ở bên phải.

Hướng

Hướng cho biết bút cảm ứng trỏ tới hướng nào.

Bạn có thể truy xuất hướng của con trỏ bằng cách sử dụng getAxisValue(AXIS_ORIENTATION) hoặc getOrientation() (đối với con trỏ đầu tiên).

Đối với bút cảm ứng, hướng được trả về ở dạng giá trị radian từ 0 đến pi (π) theo chiều kim đồng hồ hoặc 0 đến -pi ngược chiều kim đồng hồ.

Hướng cho phép bạn triển khai một bút vẽ thực tế. Ví dụ: nếu bút cảm ứng tượng trưng cho một cọ vẽ dẹt, thì chiều rộng của cọ vẽ dẹt đó sẽ phụ thuộc vào hướng của bút cảm ứng.

Hình 3. Bút cảm ứng đang trỏ sang trái khoảng -0,57 radian.

Độ nghiêng

Độ nghiêng đo độ nghiêng của bút cảm ứng so với màn hình.

Độ nghiêng trả về góc dương của bút cảm ứng tính bằng radian, trong đó 0 là vuông góc với màn hình và 𝛑/2 là bằng phẳng trên bề mặt.

Bạn có thể truy xuất góc nghiêng bằng getAxisValue(AXIS_TILT) (không có phím tắt cho con trỏ đầu tiên).

Độ nghiêng có thể được dùng để tái tạo các công cụ gần giống thực tế nhất có thể, chẳng hạn như mô phỏng hiệu ứng đổ bóng bằng bút chì nghiêng.

Bút cảm ứng được đặt nghiêng khoảng 40 độ so với bề mặt màn hình.
Hình 4. Bút cảm ứng được đặt nghiêng khoảng 0,785 radian hoặc 45 độ so với điểm vuông góc.

Khoảng cách di

Có thể lấy khoảng cách của bút cảm ứng so với màn hình bằng getAxisValue(AXIS_DISTANCE). Phương thức này trả về một giá trị từ 0 khi bút cảm ứng tiếp xúc với màn hình, đến các giá trị cao hơn khi bút cảm ứng di chuyển ra khỏi màn hình. Khoảng cách di giữa màn hình và đầu ngòi bút (điểm) của bút cảm ứng phụ thuộc vào nhà sản xuất của cả màn hình lẫn bút cảm ứng. Vì cách triển khai có thể khác nhau, nên đừng dựa vào giá trị chính xác cho chức năng quan trọng của ứng dụng.

Bạn có thể dùng thao tác di bằng bút cảm ứng để xem trước kích thước của bút vẽ hoặc cho biết rằng một nút sẽ được chọn.

Hình 5. Bút cảm ứng di qua màn hình. Ứng dụng phản ứng ngay cả khi bút cảm ứng chưa chạm vào bề mặt màn hình.

Lưu ý: Compose cung cấp một loạt các phần tử đối tượng sửa đổi để thay đổi trạng thái của các phần tử trên giao diện người dùng:

  • hoverable: Định cấu hình thành phần có thể di chuột bằng cách sử dụng các sự kiện nhập và thoát bằng con trỏ.
  • indication: Vẽ các hiệu ứng hình ảnh cho thành phần này khi diễn ra hoạt động tương tác.

Tính năng chống tì tay, điều hướng và hoạt động đầu vào không mong muốn

Đôi khi, màn hình cảm ứng đa điểm có thể đăng ký những thao tác chạm không mong muốn, chẳng hạn như khi người dùng tự nhiên tựa tay lên màn hình để đỡ trong khi viết tay. Tính năng chống tì tay là cơ chế phát hiện hành vi này và thông báo cho bạn rằng tập hợp MotionEvent gần đây nhất sẽ bị huỷ.

Do đó, bạn phải lưu lại nhật ký hoạt động đầu vào của người dùng để có thể loại bỏ những thao tác chạm không mong muốn khỏi màn hình và kết xuất lại hoạt động đầu vào hợp lệ của người dùng.

ACTION_CANCEL và FLAG_CANCELED

Cả ACTION_CANCELFLAG_CANCELED đều được thiết kế nhằm thông báo cho bạn rằng tập hợp MotionEvent trước đó sẽ bị huỷ khỏi ACTION_DOWN gần đây nhất, chẳng hạn như để bạn có thể hoàn tác nét vẽ sau cùng của một ứng dụng vẽ cho con trỏ cụ thể.

ACTION_CANCEL

Đã thêm vào Android 1.0 (API cấp 1)

ACTION_CANCEL cho biết tập hợp sự kiện chuyển động trước đó sẽ bị huỷ.

ACTION_CANCEL được kích hoạt khi phát hiện bất kỳ mục nào sau đây:

  • Cử chỉ điều hướng
  • Tính năng chống tì tay

Khi ACTION_CANCEL được kích hoạt, bạn cần xác định con trỏ đang hoạt động bằng getPointerId(getActionIndex()). Sau đó, loại bỏ nét vẽ được tạo bằng con trỏ đó khỏi nhật ký hoạt động đầu vào rồi kết xuất lại cảnh.

FLAG_CANCELED

Đã thêm vào Android 13 (API cấp 33)

FLAG_CANCELED cho biết rằng việc dịch chuyển con trỏ lên là thao tác chạm không chủ ý của người dùng. Cờ này thường được đặt khi người dùng vô tình chạm vào màn hình, chẳng hạn như bằng cách nắm chặt thiết bị hoặc tì tay trên màn hình.

Bạn truy cập vào giá trị của cờ này như sau:

Kotlin

val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED

Java

boolean cancel = (event.getFlags() & FLAG_CANCELED) == FLAG_CANCELED;

Nếu cờ này được đặt, bạn cần phải hoàn tác tập hợp MotionEvent gần đây nhất, từ ACTION_DOWN sau cùng của con trỏ này.

Giống như ACTION_CANCEL, bạn có thể tìm thấy con trỏ bằng getPointerId(actionIndex).

Hình 6. Nét vẽ bằng bút cảm ứng và thao tác chạm lòng bàn tay tạo ra tập hợp MotionEvent. Thao tác chạm lòng bàn tay bị huỷ và màn hình hiển thị được kết xuất lại.

Cử chỉ điều hướng, tràn viền và toàn màn hình

Nếu một ứng dụng ở chế độ toàn màn hình và chứa các phần tử có thể thao tác ở gần viền, chẳng hạn như canvas của một ứng dụng ghi chú hoặc vẽ, thì việc vuốt từ cuối màn hình để hiển thị ngăn điều hướng hoặc chuyển ứng dụng vào nền sau có thể dẫn đến thao tác chạm không mong muốn trên canvas đó.

Hình 7. Cử chỉ vuốt để chuyển một ứng dụng vào nền.

Để ngăn các cử chỉ kích hoạt thao tác chạm không mong muốn trong ứng dụng của bạn, bạn có thể tận dụng phần lồng ghépACTION_CANCEL.

Hãy xem thêm phần Tính năng chống tì tay, điều hướng và hoạt động đầu vào không mong muốn ở trên.

Sử dụng phương thức setSystemBarsBehavior()BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE của WindowInsetsController để ngăn các cử chỉ điều hướng gây ra sự kiện chạm không mong muốn:

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

Để tìm hiểu thêm về tính năng quản lý phần lồng ghép và cử chỉ, hãy xem:

Độ trễ thấp

Độ trễ là thời gian mà phần cứng, hệ thống và ứng dụng cần để xử lý và kết xuất hoạt động đầu vào của người dùng.

Độ trễ = thời gian xử lý hoạt động đầu vào phần cứng và hệ điều hành + xử lý ứng dụng + kết hợp hệ thống + kết xuất phần cứng

Độ trễ khiến nét vẽ được kết xuất bị trễ so với vị trí của bút cảm ứng. Khoảng cách giữa nét vẽ được kết xuất và vị trí của bút cảm ứng biểu thị độ trễ.
Hình 8. Độ trễ khiến nét vẽ được kết xuất bị trễ so với vị trí của bút cảm ứng.

Nguồn gây ra độ trễ

  • Đăng ký bút cảm ứng với màn hình cảm ứng (phần cứng): Kết nối không dây ban đầu khi bút cảm ứng và hệ điều hành giao tiếp với nhau để đăng ký và đồng bộ hoá.
  • Tốc độ lấy mẫu thao tác chạm (phần cứng): Số lần mỗi giây màn hình cảm ứng kiểm tra xem con trỏ có chạm vào bề mặt hay không, nằm trong khoảng từ 60 đến 1000 Hz.
  • Xử lý hoạt động đầu vào (ứng dụng): Áp dụng hiệu ứng màu, đồ hoạ và chuyển đổi hoạt động đầu vào của người dùng.
  • Kết xuất đồ hoạ (hệ điều hành + phần cứng): Hoán đổi vùng đệm, xử lý phần cứng.

Đồ hoạ có độ trễ thấp

Thư viện đồ hoạ có độ trễ thấp của Jetpack giúp giảm thời gian xử lý từ hoạt động đầu vào của người dùng đến khi kết xuất trên màn hình.

Thư viện này giúp giảm thời gian xử lý bằng cách tránh kết xuất nhiều vùng đệm và tận dụng kỹ thuật kết xuất vùng đệm trước, tức là ghi trực tiếp vào màn hình.

Kết xuất vùng đệm trước

Vùng đệm trước là bộ nhớ mà màn hình dùng để kết xuất. Đó là những ứng dụng gần nhất có thể vẽ trực tiếp lên màn hình. Thư viện có độ trễ thấp cho phép các ứng dụng kết xuất trực tiếp vào vùng đệm trước. Việc này giúp cải thiện hiệu suất bằng cách ngăn hoán đổi vùng đệm – điều có thể xảy ra khi kết xuất nhiều vùng đệm thông thường hoặc kết xuất vùng đệm kép (trường hợp phổ biến nhất).

Ứng dụng ghi vào vùng đệm màn hình và đọc từ vùng đệm màn hình.
Hình 9. Kết xuất vùng đệm trước.
Ứng dụng ghi vào nhiều vùng đệm, hoán đổi với vùng đệm màn hình. Ứng dụng đọc từ vùng đệm màn hình.
Hình 10. Kết xuất nhiều vùng đệm.

Mặc dù kỹ thuật kết xuất vùng đệm trước rất hữu ích khi kết xuất một vùng màn hình nhỏ, nhưng không được dùng để làm mới toàn bộ màn hình. Với tính năng kết xuất vùng đệm trước, ứng dụng sẽ kết xuất nội dung vào một vùng đệm mà màn hình sẽ đọc. Do đó, có khả năng xảy ra trường hợp kết xuất cấu phần phần mềm hoặc xé hình (xem bên dưới).

Thư viện có độ trễ thấp chỉ hoạt động trên Android 10 (API cấp 29) trở lên và trên các thiết bị ChromeOS chạy Android 10 (API cấp 29) trở lên.

Phần phụ thuộc

Thư viện có độ trễ thấp cung cấp các thành phần để triển khai tính năng kết xuất vùng đệm trước. Thư viện này được thêm làm phần phụ thuộc trong tệp build.gradle của mô-đun ứng dụng:

dependencies {
    implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}

Lệnh gọi lại GLFrontBufferRenderer

Thư viện có độ trễ thấp chứa giao diện GLFrontBufferRenderer.Callback, xác định các phương thức sau:

Thư viện có độ trễ thấp không quan tâm đến loại dữ liệu mà bạn dùng với GLFrontBufferRenderer.

Tuy nhiên, thư viện này xử lý dữ liệu ở dạng luồng gồm hàng trăm điểm dữ liệu. Do đó, hãy thiết kế dữ liệu của bạn để tối ưu hoá mức sử dụng và phân bổ bộ nhớ.

Lệnh gọi lại

Để gọi lại hoạt động kết xuất, hãy triển khai GLFrontBufferedRenderer.Callback rồi ghi đè onDrawFrontBufferedLayer()onDrawDoubleBufferedLayer(). GLFrontBufferedRenderer dùng các lệnh gọi lại để kết xuất dữ liệu của bạn theo cách tối ưu nhất có thể.

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.
    }
};
Khai báo một thực thể của GLFrontBufferedRenderer

Chuẩn bị GLFrontBufferedRenderer bằng cách cung cấp SurfaceView và các lệnh gọi lại mà bạn tạo trước đó. GLFrontBufferedRenderer tối ưu hoá quá trình kết xuất vào vùng đệm trước và vùng đệm kép thông qua các lệnh gọi lại của bạn:

Kotlin

var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)

Java

GLFrontBufferedRenderer<DATA_TYPE> glFrontBufferRenderer =
    new GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks);
Kết xuất

Quá trình kết xuất vùng đệm trước bắt đầu khi bạn gọi phương thức renderFrontBufferedLayer(), phương thức này sẽ kích hoạt lệnh gọi lại onDrawFrontBufferedLayer().

Quá trình kết xuất vùng đệm kép sẽ tiếp tục khi bạn gọi hàm commit(). Đây là hàm sẽ kích hoạt lệnh gọi lại onDrawMultiDoubleBufferedLayer().

Trong ví dụ dưới đây, quá trình này sẽ kết xuất vào vùng đệm trước (kết xuất nhanh) khi người dùng bắt đầu vẽ trên màn hình (ACTION_DOWN) và di chuyển con trỏ xung quanh (ACTION_MOVE). Quá trình này sẽ kết xuất vào vùng đệm kép khi con trỏ rời khỏi bề mặt màn hình (ACTION_UP).

Bạn có thể dùng requestUnbufferedDispatch() để yêu cầu hệ thống nhập không phân lô các sự kiện chuyển động, mà gửi các sự kiện đó ngay khi có sẵn:

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

Việc nên làm và không nên làm khi kết xuất

Việc nên làm

Các phần nhỏ của màn hình, viết tay, vẽ, phác thảo.

Việc không nên làm

Cập nhật ở chế độ toàn màn hình, kéo, thu phóng. Có thể dẫn đến hiện tượng xé hình.

Xé hình

Hiện tượng xé hình xảy ra khi màn hình làm mới trong lúc vùng đệm màn hình đang được sửa đổi. Một phần màn hình hiển thị dữ liệu mới, còn phần khác hiện dữ liệu cũ.

Các phần phía trên và dưới của hình ảnh Android bị lệch do hiện tượng xé hình khi làm mới màn hình.
Hình 11. Hiện tượng xé hình khi màn hình được làm mới từ trên xuống dưới.

Dự đoán chuyển động

Thư viện dự đoán chuyển động của Jetpack giúp giảm độ trễ dự kiến bằng cách ước tính đường vẽ của người dùng và cung cấp các điểm nhân tạo tạm thời cho trình kết xuất.

Thư viện dự đoán chuyển động nhận hoạt động đầu vào thực của người dùng ở dạng đối tượng MotionEvent. Các đối tượng này chứa thông tin về toạ độ x và y, áp lực cũng như thời gian. Công cụ dự đoán chuyển động sẽ tận dụng thông tin đó để dự đoán các đối tượng MotionEvent trong tương lai.

Các đối tượng MotionEvent được dự đoán chỉ là giá trị ước tính. Các sự kiện được dự đoán có thể giúp giảm độ trễ dự kiến, nhưng bạn phải thay thế dữ liệu dự đoán bằng dữ liệu MotionEvent thực tế sau khi nhận được.

Thư viện dự đoán chuyển động có trên Android 4.4 (API cấp 19) trở lên và trên các thiết bị ChromeOS chạy Android 9 (API cấp 28) trở lên.

Độ trễ khiến nét vẽ được kết xuất bị trễ so với vị trí của bút cảm ứng. Khoảng cách giữa nét vẽ và bút cảm ứng được lấp đầy bằng các điểm dự đoán. Khoảng cách còn lại là độ trễ dự kiến.
Hình 12. Độ trễ giảm nhờ thư viện dự đoán chuyển động.

Phần phụ thuộc

Thư viện dự đoán chuyển động cung cấp phương thức triển khai tính năng dự đoán. Thư viện này được thêm làm phần phụ thuộc trong tệp build.gradle của mô-đun ứng dụng:

dependencies {
    implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}

Triển khai

Thư viện dự đoán chuyển động chứa giao diện MotionEventPredictor, xác định các phương thức sau:

  • record(): Lưu trữ các đối tượng MotionEvent ở dạng bản ghi thao tác của người dùng
  • predict(): Trả về MotionEvent được dự đoán
Khai báo một thực thể của MotionEventPredictor

Kotlin

var motionEventPredictor = MotionEventPredictor.newInstance(view)

Java

MotionEventPredictor motionEventPredictor = MotionEventPredictor.newInstance(surfaceView);
Cung cấp dữ liệu cho công cụ dự đoán

Kotlin

motionEventPredictor.record(motionEvent)

Java

motionEventPredictor.record(motionEvent);
Dự đoán

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

Việc nên làm và không nên làm khi dự đoán chuyển động

Việc nên làm

Loại bỏ các điểm dự đoán khi một điểm dự đoán mới được thêm vào.

Việc không nên làm

Không dùng các điểm dự đoán cho hoạt động kết xuất cuối cùng.

Ứng dụng ghi chú

ChromeOS cho phép ứng dụng của bạn khai báo một số thao tác ghi chú.

Để đăng ký một ứng dụng làm ứng dụng ghi chú trên ChromeOS, hãy xem bài viết về Khả năng tương thích đầu vào.

Để đăng ký một ứng dụng làm ứng dụng ghi chú trên Android, hãy xem bài viết về Tạo ứng dụng ghi chú.

Android 14 (API cấp 34), ra mắt ý định ACTION_CREATE_NOTE, ý định này cho phép ứng dụng của bạn bắt đầu một hoạt động ghi chú trên màn hình khoá.

Nhận dạng mực kỹ thuật số bằng Bộ công cụ học máy

Với tính năng nhận dạng mực kỹ thuật số bằng Bộ công cụ học máy, ứng dụng của bạn có thể nhận dạng văn bản viết tay trên một bề mặt kỹ thuật số bằng hàng trăm ngôn ngữ. Bạn cũng có thể phân loại các bản phác thảo.

Bộ công cụ học máy cung cấp lớp Ink.Stroke.Builder để tạo đối tượng Ink mà các mô hình học máy có thể xử lý nhằm chuyển đổi chữ viết tay thành văn bản.

Ngoài tính năng nhận dạng chữ viết tay, mô hình này còn có thể nhận dạng cử chỉ, chẳng hạn như xoá và khoanh tròn.

Xem bài viết Nhận dạng mực kỹ thuật số để tìm hiểu thêm.

Tài nguyên khác

Hướng dẫn cho nhà phát triển

Lớp học lập trình