Sổ tay chế biến ứng dụng trên màn hình lớn

Android cung cấp mọi nguyên liệu để tạo nên một ứng dụng 5 sao cho thiết bị màn hình lớn. Các công thức trong cuốn sổ tay này chọn lọc và kết hợp các nguyên liệu được lựa chọn kỹ lưỡng để giải quyết những vấn đề cụ thể khi phát triển ứng dụng. Mỗi công thức đều có các phương pháp hay nhất, đoạn mã ví dụ chất lượng cao và hướng dẫn từng bước, giúp bạn trở thành một đầu bếp hàng đầu chuyên tạo ra các ứng dụng trên màn hình lớn.

Xếp hạng theo sao

Các công thức được xếp hạng sao dựa trên mức độ phù hợp với nguyên tắc về Chất lượng trên ứng dụng màn hình lớn.

Xếp hạng 5 sao Đáp ứng tiêu chí Cấp 1, có sự khác biệt trên màn hình lớn
Xếp hạng 4 sao Đáp ứng tiêu chí Cấp 2, tối ưu hoá cho màn hình lớn
Xếp hạng 3 sao Đáp ứng tiêu chí Cấp 3, sẵn sàng cho màn hình lớn
Xếp hạng 2 sao Cung cấp một số chức năng trên màn hình lớn, nhưng không đáp ứng được các nguyên tắc về chất lượng đối với ứng dụng màn hình lớn
Xếp hạng 1 sao Đáp ứng nhu cầu của một trường hợp sử dụng cụ thể, nhưng không hỗ trợ đúng cách cho màn hình lớn

Hỗ trợ máy ảnh của Chromebook

Xếp hạng 3 sao

Thu hút sự chú ý của người dùng Chromebook trên Google Play.

Nếu ứng dụng máy ảnh của bạn chỉ hoạt động với các tính năng máy ảnh cơ bản, đừng để cửa hàng ứng dụng ngăn người dùng Chromebook cài đặt ứng dụng chỉ vì bạn vô tình chỉ định các tính năng máy ảnh nâng cao trên điện thoại cao cấp.

Chromebook tích hợp sẵn máy ảnh mặt trước (hướng về phía người dùng) phù hợp với ứng dụng hội nghị truyền hình, ảnh chụp nhanh và các ứng dụng khác. Tuy nhiên, không phải Chromebook nào cũng có máy ảnh mặt sau (hướng ra ngoài), đồng thời hầu hết máy ảnh mặt trước của người dùng trên Chromebooks đều không hỗ trợ tính năng tự động lấy nét hoặc đèn flash.

Các phương pháp hay nhất

Các ứng dụng máy ảnh linh hoạt hỗ trợ tất cả các thiết bị, bất kể cấu hình máy ảnh như thiết bị có máy ảnh trước, máy ảnh sau, máy ảnh bên ngoài được kết nối bằng USB.

Để đảm bảo các cửa hàng ứng dụng cung cấp ứng dụng của bạn cho nhiều thiết bị nhất, hãy luôn khai báo tất cả các tính năng của máy ảnh mà ứng dụng dùng và cho biết rõ các tính năng đó có bắt buộc hay không.

Nguyên liệu

  • Quyền CAMERA: Cấp cho ứng dụng của bạn quyền truy cập vào máy ảnh của thiết bị
  • Phần tử tệp kê khai <uses-feature>: Thông báo cho cửa hàng ứng dụng về những tính năng mà ứng dụng sử dụng
  • Thuộc tính required: Cho các cửa hàng ứng dụng biết liệu ứng dụng có thể hoạt động mà không cần tính năng đã chỉ định hay không

Các bước

Tóm tắt

Khai báo quyền sử dụng CAMERA. Khai báo các tính năng của máy ảnh hỗ trợ máy ảnh cơ bản. Chỉ định rõ mỗi tính năng có bắt buộc hay không.

1. Khai báo quyền sử dụng CAMERA

Thêm quyền sau đây vào tệp kê khai ứng dụng:

<uses-permission android:name="android.permission.CAMERA" />
2. Khai báo các tính năng cơ bản của máy ảnh

Thêm các tính năng sau vào tệp kê khai ứng dụng:

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. Chỉ định rõ mỗi tính năng có bắt buộc hay không

Đặt android:required="false" cho tính năng android.hardware.camera.any để cho phép các thiết bị có máy ảnh tích hợp, máy ảnh bên ngoài, hoặc thậm chí thiết bị không có máy ảnh nào cũng đều có thể truy cập vào ứng dụng của bạn.

Đối với các tính năng khác, đoạn mã trên đặt android:required="false" để đảm bảo các thiết bị như Chromebook (không có máy ảnh mặt sau, tính năng tự động lấy nét hoặc đèn flash) có thể truy cập vào ứng dụng của bạn trên các cửa hàng ứng dụng.

Kết quả

Người dùng Chromebook có thể tải và cài đặt ứng dụng của bạn từ Google Play và các cửa hàng ứng dụng khác. Các thiết bị hỗ trợ máy ảnh đầy đủ tính năng như điện thoại sẽ không bị hạn chế về chức năng của máy ảnh.

Bằng cách đặt rõ các tính năng máy ảnh mà ứng dụng của bạn hỗ trợ và chỉ định các tính năng mà ứng dụng yêu cầu, bạn đã cung cấp ứng dụng cho nhiều thiết bị nhất có thể.

Tài nguyên khác

Để biết thêm thông tin, hãy xem nội dung Các tính năng phần cứng của máy ảnh trong tài liệu <uses-feature>.

Hướng ứng dụng bị hạn chế trên điện thoại nhưng không bị hạn chế trên các thiết bị màn hình lớn

Xếp hạng 2 sao

Ứng dụng của bạn hoạt động tốt trên điện thoại ở hướng dọc, vậy nên bạn hạn chế ứng dụng chỉ ở chế độ dọc. Tuy nhiên, bạn có thể làm được nhiều việc hơn trên màn hình lớn theo hướng ngang.

Làm thế nào để bạn có thể sử dụng theo cả hai cách – nghĩa là hạn chế ứng dụng ở hướng dọc trên màn hình nhỏ, nhưng vẫn bật được hướng ngang trên màn hình lớn?

Các phương pháp hay nhất

Một ứng dụng tối ưu luôn tôn trọng các lựa chọn ưu tiên của người dùng, chẳng hạn như hướng thiết bị.

Theo nguyên tắc về chất lượng ứng dụng màn hình lớn, các ứng dụng sẽ hỗ trợ tất cả các cấu hình thiết bị, bao gồm cả hướng dọc và ngang, chế độ nhiều cửa sổ, cũng như các trạng thái gập và mở của thiết bị có thể gập lại. Ứng dụng phải tối ưu hoá bố cục và giao diện người dùng cho các cấu hình khác nhau, đồng thời phải lưu và khôi phục trạng thái trong quá trình thay đổi cấu hình.

Công thức này chỉ là một giải pháp tạm thời — một khoảng nhỏ hỗ trợ màn hình lớn. Sử dụng công thức này cho đến khi bạn có thể cải thiện ứng dụng của mình để cung cấp hỗ trợ đầy đủ cho tất cả các cấu hình thiết bị.

Nguyên liệu

  • screenOrientation: Tuỳ chọn cài đặt tệp kê khai ứng dụng cho phép bạn chỉ định cách ứng dụng của bạn phản hồi các thay đổi về hướng trên thiết bị
  • Jetpack WindowManager: Tập hợp các thư viện cho phép bạn xác định kích thước và tỷ lệ khung hình của cửa sổ ứng dụng; khả năng tương thích ngược với API cấp 14
  • Activity#setRequestedOrientation(): Phương thức mà bạn có thể thay đổi hướng ứng dụng trong thời gian chạy

Các bước

Tóm tắt

Cho phép ứng dụng xử lý các thay đổi về hướng theo mặc định trong tệp kê khai ứng dụng. Trong thời gian chạy, hãy xác định kích thước cửa sổ ứng dụng. Nếu cửa sổ ứng dụng nhỏ, hãy hạn chế hướng ứng dụng bằng cách ghi đè chế độ cài đặt hướng của tệp kê khai.

1. Chỉ định chế độ cài đặt hướng trong tệp kê khai ứng dụng

Bạn có thể tránh khai báo phần tử screenOrientation của tệp kê khai ứng dụng (trong trường hợp này, hướng được đặt mặc định thành unspecified) hoặc đặt hướng màn hình thành fullUser. Nếu người dùng chưa khoá chế độ xoay dựa trên cảm biến, ứng dụng của bạn sẽ hỗ trợ tất cả các hướng của thiết bị.

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

Sự khác biệt giữa việc sử dụng unspecifiedfullUser là khó thấy nhưng quan trọng. Nếu bạn không khai báo một giá trị screenOrientation, thì hệ thống sẽ chọn hướng và chính sách mà hệ thống sử dụng để xác định hướng có thể khác nhau giữa các thiết bị. Mặt khác, việc chỉ định fullUser sẽ phù hợp hơn với hành vi mà người dùng đã xác định cho thiết bị: nếu người dùng đã khoá chế độ xoay dựa trên cảm biến, thì ứng dụng sẽ tuân theo lựa chọn ưu tiên của người dùng; nếu không, hệ thống sẽ cho phép sử dụng bất kỳ hướng nào trong 4 hướng màn hình có thể có (dọc, ngang, dọc lộn ngược hoặc ngang lộn ngược). Hãy xem android:screenOrientation.

2. Xác định kích thước màn hình

Với tệp kê khai được đặt để hỗ trợ tất cả các hướng được người dùng cho phép, bạn có thể chỉ định hướng ứng dụng theo phương thức lập trình dựa trên kích thước màn hình.

Thêm thư viện Jetpack WindowManager vào tệp build.gradle hoặc build.gradle.kts của mô-đun:

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

Groovy

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

Sử dụng phương thức Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() để lấy kích thước màn hình thiết bị dưới dạng đối tượng WindowMetrics. Bạn có thể so sánh các chỉ số của cửa sổ với lớp kích thước cửa sổ để quyết định thời điểm giới hạn hướng.

Lớp kích thước của Windows cung cấp các điểm ngắt giữa màn hình nhỏ và màn hình lớn.

Sử dụng các điểm ngắt WindowWidthSizeClass#COMPACTWindowHeightSizeClass#COMPACT để xác định kích thước màn hình:

Kotlin

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Java

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    Lưu ý:
  • Những ví dụ trên được triển khai dưới dạng phương thức của một hoạt động; và do đó, hoạt động được tham chiếu như this trong đối số của computeMaximumWindowMetrics().
  • Phương thức computeMaximumWindowMetrics() được dùng thay cho computeCurrentWindowMetrics(), vì bạn có thể chạy ứng dụng ở chế độ nhiều cửa sổ, nghĩa là bỏ qua chế độ cài đặt hướng màn hình. Chẳng có lý do gì phải xác định kích thước cửa sổ ứng dụng và ghi đè tuỳ chọn cài đặt hướng, trừ phi cửa sổ ứng dụng chiếm toàn bộ màn hình thiết bị.

Vui lòng xem WindowManager để biết hướng dẫn về cách khai báo các phần phụ thuộc nhằm cung cấp phương thức computeMaximumWindowMetrics() cho ứng dụng của bạn.

3. Ghi đè chế độ cài đặt trong tệp kê khai ứng dụng

Khi đã xác định thiết bị có kích thước màn hình thu gọn, bạn có thể gọi Activity#setRequestedOrientation() để ghi đè chế độ cài đặt screenOrientation của tệp kê khai:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Java

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    ViewGroup container = binding.container;

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(new View(this) {
        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

Bằng cách thêm logic vào phương thức onCreate()View.onConfigurationChanged(), bạn có thể thu được các chỉ số cửa sổ tối đa và ghi đè chế độ cài đặt hướng bất cứ khi nào hoạt động được đổi kích thước hoặc di chuyển giữa các màn hình hiển thị, chẳng hạn như sau khi xoay thiết bị hoặc khi một thiết bị có thể gập lại được gập hoặc mở ra. Để biết thêm thông tin về thời điểm diễn ra các thay đổi về cấu hình và thời điểm các thay đổi đó dẫn đến việc tạo lại hoạt động, hãy tham khảo bài viết Xử lý các thay đổi về cấu hình

Kết quả

Ứng dụng của bạn vẫn nằm ở hướng dọc trên màn hình nhỏ bất kể thiết bị có xoay hướng nào. Trên màn hình lớn, ứng dụng sẽ hỗ trợ cả hướng ngang lẫn hướng dọc.

Tài nguyên khác

Để được trợ giúp về cách nâng cấp ứng dụng nhằm luôn có thể hỗ trợ tất cả cấu hình thiết bị, hãy xem các mục sau:

Tạm dừng và tiếp tục phát nội dung nghe nhìn bằng Phím cách của bàn phím bên ngoài

Xếp hạng 4 sao

Tính năng tối ưu hoá cho màn hình lớn có thể xử lý dữ liệu đầu vào qua bàn phím bên ngoài, chẳng hạn như nhấn Phím cách để tạm dừng hoặc tiếp tục phát các video và nội dung nghe nhìn khác. Tính năng này đặc biệt hữu ích cho máy tính bảng, thường kết nối với bàn phím bên ngoài và Chromebook (thường có bàn phím bên ngoài nhưng có thể sử dụng ở chế độ máy tính bảng).

Khi nội dung nghe nhìn là thành phần duy nhất của cửa sổ (chẳng hạn như phát video toàn màn hình), hãy phản hồi các sự kiện nhấn phím ở cấp hoạt động hoặc ở cấp màn hình (trong Jetpack Compose).

Các phương pháp hay nhất

Bất cứ khi nào ứng dụng của bạn phát một tệp nội dung nghe nhìn, người dùng sẽ có thể tạm dừng và tiếp tục phát bằng cách nhấn Phím cách trên bàn phím vật lý.

Nguyên liệu

Compose

  • onPreviewKeyEvent: Modifier cho phép một thành phần chặn các sự kiện liên quan đến phím phần cứng khi thành phần đó (hoặc một trong các thành phần con) được lấy làm tâm điểm.
  • onKeyEvent: Tương tự như onPreviewKeyEvent, Modifier này cho phép một thành phần chặn các sự kiện liên quan đến phím phần cứng khi thành phần đó (hoặc một trong các thành phần con) được lấy tiêu điểm.

Khung hiển thị

  • onKeyUp(): Được gọi khi một phím được nhả ra và không được khung hiển thị xử lý trong một hoạt động.

Các bước

Tóm tắt

Các ứng dụng dựa trên khung hiển thị và các ứng dụng dựa trên Jetpack Compose phản hồi với thao tác nhấn phím trên bàn phím theo những cách tương tự như sau: ứng dụng phải theo dõi các sự kiện nhấn phím, lọc ra các sự kiện đó và phản hồi một số thao tác nhấn phím, chẳng hạn như thao tác nhấn Phím cách.

1. Theo dõi các sự kiện bàn phím

Khung hiển thị

Trong một hoạt động trên ứng dụng của bạn, hãy ghi đè phương thức onKeyUp():

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

Phương thức này được gọi mỗi khi bạn nhả một phím đã nhấn ra. Do đó, phương thức này sẽ kích hoạt chính xác một lần cho mỗi lần nhấn phím.

Compose

Với Jetpack Compose, bạn có thể tận dụng đối tượng sửa đổi onPreviewKeyEvent hoặc onKeyEvent trong màn hình để quản lý thao tác nhấn phím:

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

hoặc

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. Lọc ra các thao tác nhấn Phím cách

Trong phương thức onKeyUp() hoặc phương thức đối tượng sửa đổi onPreviewKeyEventonKeyEvent trong Compose, hãy lọc KeyEvent.KEYCODE_SPACE để gửi đúng sự kiện đến thành phần nội dung nghe nhìn:

Khung hiển thị

Kotlin

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Java

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

Compose

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

hoặc

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

Kết quả

Ứng dụng của bạn hiện có thể phản hồi khi người dùng nhấn Phím cách để tạm dừng và tiếp tục phát một video hoặc nội dung nghe nhìn khác.

Tài nguyên khác

Để tìm hiểu thêm về các sự kiện bàn phím và cách quản lý những sự kiện đó, hãy xem bài viết Xử lý hoạt động nhập bằng bàn phím.

Tính năng chống tì tay khi dùng bút cảm ứng

Xếp hạng 5 sao

Bút cảm ứng có thể là một công cụ đặc biệt hữu dụng và sáng tạo trên màn hình lớn. Tuy nhiên, khi vẽ, viết hoặc tương tác với ứng dụng bằng bút cảm ứng, đôi lúc người dùng sẽ tì tay lên màn hình. Ứng dụng của bạn có thể sẽ nhận được báo cáo về sự kiện chạm trước khi hệ thống nhận ra và loại bỏ sự kiện đó dưới dạng thao tác vô tình tì tay.

Các phương pháp hay nhất

Ứng dụng của bạn phải xác định các sự kiện chạm dư thừa và bỏ qua những sự kiện đó. Android huỷ thao tác tì tay bằng cách gửi một đối tượng MotionEvent. Hãy kiểm tra đối tượng đó để tìm ACTION_CANCEL hoặc ACTION_POINTER_UPFLAG_CANCELED nhằm xác định xem có nên từ chối cử chỉ do tì tay hay không.

Nguyên liệu

  • MotionEvent: Biểu thị các sự kiện chạm và chuyển động. Chứa thông tin cần thiết để xác định xem có nên bỏ qua một sự kiện hay không.
  • OnTouchListener#onTouch(): Nhận các đối tượng MotionEvent.
  • MotionEvent#getActionMasked(): Trả về thao tác liên quan đến một sự kiện chuyển động.
  • ACTION_CANCEL: Hằng số MotionEvent cho biết cần huỷ một cử chỉ.
  • ACTION_POINTER_UP: Hằng số MotionEvent cho biết rằng con trỏ không phải là con trỏ đầu tiên đã dịch chuyển lên (nghĩa là con trỏ đã ngừng tiếp xúc với màn hình thiết bị).
  • FLAG_CANCELED: Hằng số MotionEvent cho biết rằng việc con trỏ dịch chuyển lên tạo ra một sự kiện chạm ngoài ý muốn. Đã thêm vào các sự kiện ACTION_POINTER_UPACTION_CANCEL trên Android 13 (API cấp 33) trở lên.

Các bước

Tóm tắt

Kiểm tra các đối tượng MotionEvent được gửi đến ứng dụng của bạn. Sử dụng các API MotionEvent để xác định đặc điểm của sự kiện:

  • Sự kiện chạm con trỏ một lần — Kiểm tra để tìm ACTION_CANCEL. Trên Android 13 trở lên, hãy kiểm tra để tìm cả FLAG_CANCELED.
  • Sự kiện chạm con trỏ nhiều lần — Trên Android 13 trở lên, hãy kiểm tra để tìm ACTION_POINTER_UPFLAG_CANCELED.

Phản hồi các sự kiện ACTION_CANCELACTION_POINTER_UP/FLAG_CANCELED.

1. Thu nhận đối tượng sự kiện chuyển động

Thêm một OnTouchListener vào ứng dụng của bạn:

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. Xác định thao tác và cờ cho sự kiện

Kiểm tra để tìm ACTION_CANCEL (cho biết sự kiện chạm con trỏ một lần ở mọi cấp độ API). Trên Android 13 trở lên, hãy kiểm tra ACTION_POINTER_UP để tìm FLAG_CANCELED.

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. Huỷ cử chỉ

Sau khi xác định thấy thao tác tì tay, bạn có thể huỷ các hiệu ứng trên màn hình của cử chỉ.

Ứng dụng phải lưu nhật ký thao tác của người dùng để có thể huỷ thao tác nhập ngoài ý muốn, chẳng hạn như tì tay. Hãy xem ví dụ ở phần Triển khai ứng dụng vẽ cơ bản trong lớp học lập trình Tăng cường hỗ trợ bút cảm ứng trong ứng dụng Android.

Kết quả

Giờ đây, ứng dụng của bạn có thể xác định và từ chối thao tác tì tay đối với các sự kiện chạm con trỏ nhiều lần ở những cấp độ API từ Android 13 trở lên và các sự kiện chạm con trỏ một lần ở mọi cấp độ API.

Tài nguyên khác

Để biết thêm thông tin, hãy xem phần dưới đây:

Quản lý trạng thái với WebView

Xếp hạng 3 sao

WebView là một thành phần thường được sử dụng. Thành phần này cung cấp hệ thống quản lý trạng thái tiên tiến. WebView phải giữ nguyên trạng thái và vị trí cuộn khi có những thay đổi về cấu hình. WebView có thể rời khỏi vị trí cuộn khi người dùng xoay thiết bị hoặc mở gập màn hình điện thoại, buộc người dùng phải cuộn lại từ đầu WebView đến vị trí cuộn trước đó.

Các phương pháp hay nhất

Giảm thiểu số lần tạo lại WebView. WebView thích hợp trong việc quản lý trạng thái, bạn có thể tận dụng tính năng này bằng cách quản lý càng nhiều thay đổi cấu hình càng tốt. Ứng dụng của bạn phải xử lý việc thay đổi cấu hình vì việc tạo lại Activity (cách xử lý các thay đổi về cấu hình của hệ thống) sẽ tạo lại WebView, khiến WebView bị mất trạng thái.

Nguyên liệu

  • android:configChanges: Thuộc tính của phần tử tệp kê khai <activity>. Liệt kê các thay đổi về cấu hình do hoạt động xử lý.
  • View#invalidate(): Phương thức khiến khung hiển thị được vẽ lại. Kế thừa từ WebView.

Các bước

Tóm tắt

Để lưu trạng thái WebView, hãy tránh tạo lại Activity nhiều nhất có thể, sau đó vô hiệu hoá WebView để thành phần này thay đổi về kích thước trong khi vẫn giữ nguyên trạng thái.

1. Thêm thay đổi cấu hình vào tệp AndroidManifest.xml của ứng dụng

Tránh tạo lại hoạt động bằng cách chỉ định thay đổi cấu hình do ứng dụng của bạn xử lý (thay vì để hệ thống xử lý):

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. Vô hiệu hoá WebView bất cứ khi nào ứng dụng của bạn nhận thấy có sự thay đổi về cấu hình

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Java

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

Bước này chỉ áp dụng cho hệ thống khung thiển thị, vì Jetpack Compose không cần vô hiệu hoá bất kỳ thứ gì để đổi kích thước các phần tử Composable một cách chính xác. Tuy nhiên, Compose sẽ thường xuyên tạo lại WebView nếu không được quản lý đúng cách. Sử dụng trình bao bọc Accompanist WebView để lưu và khôi phục trạng thái WebView trong các ứng dụng Compose.

Kết quả

Giờ đây, thành phần WebView của ứng dụng đã giữ nguyên trạng thái và vị trí cuộn khi cấu hình có nhiều sự thay đổi, như thay đổi kích thước, thay đổi hướng hoặc trạng thái gập/mở.

Tài nguyên khác

Để tìm hiểu thêm về thay đổi cấu hình cũng như cách quản lý chúng, hãy xem bài viết Xử lý thay đổi cấu hình.

Quản lý trạng thái với RecyclerView

Xếp hạng 3 sao

RecyclerView có thể hiển thị một lượng lớn dữ liệu bằng cách sử dụng ít tài nguyên đồ họa nhất có thể. Khi RecyclerView cuộn qua danh sách các mục, RecyclerView sẽ sử dụng lại các thực thể View của mục đã cuộn qua để tạo các mục mới khi tiếp tục cuộn trên màn hình. Tuy nhiên, thay đổi cấu hình, chẳng hạn như việc xoay thiết bị, có thể đặt lại trạng thái của RecyclerView, buộc người dùng phải cuộn lại vị trí trước đó trong danh sách các mục RecyclerView.

Các phương pháp hay nhất

RecyclerView phải giữ nguyên trạng thái (cụ thể là vị trí cuộn) và trạng thái của các phần tử trong danh sách tại tất cả thay đổi cấu hình.

Nguyên liệu

Các bước

Tóm tắt

Thiết lập chính sách khôi phục trạng thái của RecyclerView.Adapter để lưu vị trí cuộn RecyclerView. Lưu trạng thái của mục trong danh sách RecyclerView. Thêm trạng thái của mục trong danh sách vào bộ chuyển đổi RecyclerView rồi khôi phục trạng thái của mục trong danh sách khi được liên kết với ViewHolder.

1. Bật chính sách khôi phục trạng thái Adapter

Bật chính sách khôi phục trạng thái trong bộ chuyển đổi RecyclerView để giữ nguyên vị trí cuộn của RecyclerView trên mọi thay đổi về cấu hình. Thêm thông số của chính sách vào hàm khởi tạo bộ chuyển đổi:

Kotlin

class MyAdapter() : RecyclerView.Adapter() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Java

class MyAdapter extends RecyclerView.Adapter {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. Lưu trạng thái của mục trong danh sách có trạng thái

Lưu trạng thái của mục trong danh sách RecyclerView phức tạp, chẳng hạn như các mục chứa phần tử EditText. Ví dụ: để lưu trạng thái của EditText, hãy thêm lệnh gọi lại tương tự như trình xử lý onClick để ghi lại các thay đổi trong văn bản. Trong lệnh gọi lại, hãy xác định dữ liệu cần lưu:

Kotlin

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Java

input.addTextChangedListener(new TextWatcher() {
    
    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

Khai báo lệnh gọi lại trong Activity hoặc Fragment. Sử dụng ViewModel để lưu trữ trạng thái.

3. Thêm trạng thái của mục trong danh sách vào Adapter

Thêm trạng thái của mục trong danh sách vào RecyclerView.Adapter. Truyền trạng thái của mục đến hàm khởi tạo bộ chuyển đổi khi tạo máy chủ lưu trữ Activity hoặc Fragment:

Kotlin

val adapter = MyAdapter(items, viewModel.retrieveState())

Java

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. Khôi phục trạng thái của mục trong danh sách trên ViewHolder của bộ chuyển đổi

Trong RecyclerView.Adapter, khi bạn liên kết ViewHolder với một mục, hãy khôi phục trạng thái của mục đó:

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Java

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

Kết quả

Giờ đây, RecyclerView đã có thể khôi phục lại vị trí cuộn và trạng thái của mọi mục trong danh sách RecyclerView.

Tài nguyên khác

Quản lý bàn phím có thể tháo rời

Xếp hạng 3 sao

Việc hỗ trợ bàn phím có thể tháo rời giúp tối đa hoá năng suất của người dùng trên các thiết bị màn hình lớn. Android kích hoạt thay đổi cấu hình mỗi khi bàn phím được gắn hoặc tách khỏi thiết bị. Việc này có thể làm mất trạng thái giao diện người dùng. Ứng dụng của bạn có thể lưu và khôi phục trạng thái, cho phép hệ thống xử lý việc tạo lại hoạt động hoặc hạn chế việc tạo lại hoạt động đối với các thay đổi về cấu hình bàn phím. Trong mọi trường hợp, mọi dữ liệu liên quan đến bàn phím đều được lưu trữ trong đối tượng Configuration. Các trường keyboardkeyboardHidden của đối tượng cấu hình chứa thông tin về loại bàn phím và khả năng sử dụng bàn phím đó.

Các phương pháp hay nhất

Các ứng dụng được tối ưu hoá cho màn hình lớn hỗ trợ mọi loại thiết bị đầu vào, từ bàn phím phần mềm và phần cứng cho đến bút cảm ứng, chuột, bàn di chuột và các thiết bị ngoại vi khác.

Việc hỗ trợ bàn phím ngoài đòi hỏi bạn phải thay đổi cấu hình mà bạn có thể quản lý theo một trong 2 cách sau:

  1. Cho phép hệ thống tạo lại hoạt động hiện đang chạy và bạn đảm nhận việc quản lý trạng thái ứng dụng của mình.
  2. Tự quản lý thay đổi về cấu hình (hoạt động sẽ không được tạo lại):
    • Khai báo tất cả giá trị cấu hình liên quan đến bàn phím
    • Tạo trình xử lý thay đổi cấu hình

Các ứng dụng cải thiện hiệu suất, thường yêu cầu quyền kiểm soát tốt đối với giao diện người dùng để nhập văn bản và các dữ liệu đầu vào khác, có thể hưởng lợi từ phương pháp tự làm để xử lý các thay đổi về cấu hình.

Trong các trường hợp đặc biệt, bạn nên thay đổi bố cục ứng dụng của mình khi bàn phím phần cứng được đính kèm hoặc tháo rời, chẳng hạn như để có thêm không gian cho các công cụ hoặc cửa sổ chỉnh sửa.

Vì cách đáng tin cậy duy nhất để theo dõi các thay đổi về cấu hình là ghi đè phương thức onConfigurationChanged() của một khung hiển thị, nên bạn có thể thêm một thực thể Khung hiển thị mới vào hoạt động ứng dụng của mình và phản hồi trong trình xử lý onConfigurationChanged() của khung hiển thị đối với những thay đổi về cấu hình do bàn phím được đính kèm hoặc tách rời.

Nguyên liệu

  • android:configChanges: Thuộc tính của phần tử <activity> của tệp kê khai ứng dụng. Thông báo cho hệ thống về những thay đổi về cấu hình mà ứng dụng quản lý.
  • View#onConfigurationChanged(): Phương thức phản ứng với việc truyền một cấu hình ứng dụng mới.

Các bước

Tóm tắt

Khai báo thuộc tính configChanges và thêm các giá trị liên quan đến bàn phím. Thêm View vào hệ phân cấp khung hiển thị của hoạt động và theo dõi các thay đổi về cấu hình.

1. Khai báo thuộc tính configChanges

Cập nhật phần tử <activity> trong tệp kê khai ứng dụng bằng cách thêm các giá trị keyboard|keyboardHidden vào danh sách các thay đổi về cấu hình đã được quản lý:

<activity
      …
      android:configChanges="...|keyboard|keyboardHidden">

2. Thêm khung hiển thị trống vào hệ phân cấp khung hiển thị

Khai báo một thành phần hiển thị mới và thêm mã trình xử lý vào phương thức onConfigurationChanged() của thành phần hiển thị đó:

Kotlin

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Java

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

Kết quả

Bây giờ, ứng dụng của bạn sẽ phản hồi một bàn phím bên ngoài đang được đính kèm hoặc tách ra mà không cần tạo lại hoạt động hiện đang chạy.

Tài nguyên khác

Để tìm hiểu cách lưu trạng thái giao diện người dùng của ứng dụng trong các thay đổi về cấu hình (như đính kèm hoặc tháo rời bàn phím), hãy xem bài viết Lưu trạng thái giao diện người dùng.