Chủ đề này trình bày cách thiết lập trường hợp sử dụng CameraX bên trong ứng dụng để nhận được hình ảnh với thông tin xoay chính xác, cho dù đó là từ trường hợp sử dụng ImageAnalysis
hay ImageCapture
. Vì vậy:
Analyzer
của trường hợp sử dụngImageAnalysis
sẽ nhận được khung với chế độ xoay chính xác.- Trường hợp sử dụng
ImageCapture
phải chụp ảnh với chế độ xoay chính xác.
Thuật ngữ
Chủ đề này sử dụng các thuật ngữ sau, do vậy, việc hiểu rõ nghĩa từng thuật ngữ là rất quan trọng:
- Hướng của màn hình
- Cụm từ này muốn chỉ cạnh nào của thiết bị đang ở vị trí hướng lên và có thể là một trong bốn giá trị: dọc, ngang, dọc lộn ngược hoặc ngang lộn ngược.
- Độ xoay màn hình
- Đây là giá trị được
Display.getRotation()
trả về và cho biết số độ mà thiết bị được xoay ngược chiều kim đồng hồ từ hướng tự nhiên của thiết bị. - Độ xoay mục tiêu
- Cụm từ này cho biết số độ cần xoay theo chiều kim đồng hồ để thiết bị trở về hướng tự nhiên.
Cách xác định độ xoay mục tiêu
Các ví dụ dưới đây cho thấy cách để xác định độ xoay mục tiêu cho thiết bị dựa trên hướng tự nhiên của thiết bị.
Ví dụ 1: Hướng tự nhiên theo chiều dọc
Ví dụ về thiết bị: Pixel 3 XL | |
---|---|
Hướng tự nhiên = Chiều dọc Độ xoay màn hình = 0 |
|
Hướng tự nhiên = Chiều dọc Độ xoay màn hình = 90 |
Ví dụ 2: Hướng tự nhiên theo chiều ngang
Ví dụ về thiết bị: Pixel C | |
---|---|
Hướng tự nhiên = Chiều ngang Độ xoay màn hình = 0 |
|
Hướng tự nhiên = Chiều ngang Độ xoay màn hình = 270 |
Độ xoay hình ảnh
Đầu nào đang hướng lên? Hướng cảm biến được xác định trong Android là một giá trị không đổi. Giá trị này biểu thị số độ (0, 90, 180, 270) mà cảm biến xoay tính từ đầu thiết bị khi thiết bị đang ở vị trí tự nhiên. Đối với tất cả các trường hợp trong sơ đồ, độ xoay hình ảnh mô tả cách dữ liệu nên được xoay theo chiều kim đồng hồ để hiển thị theo chiều thẳng đứng.
Các ví dụ sau đây cho thấy độ xoay hình ảnh nên là gì tuỳ thuộc vào hướng cảm biến của camera. Các ví dụ này cũng giả định rằng độ xoay mục tiêu được đặt là độ xoay màn hình.
Ví dụ 1: Cảm biến được xoay 90 độ
Ví dụ về thiết bị: Pixel 3 XL | |
---|---|
Độ xoay màn hình = 0 |
|
Độ xoay màn hình = 90 |
Ví dụ 2: Cảm biến được xoay 270 độ
Ví dụ về thiết bị: Nexus 5X | |
---|---|
Độ xoay màn hình = 0 |
|
Độ xoay màn hình = 90 |
Ví dụ 3: Cảm biến được xoay 0 độ
Ví dụ về thiết bị: Pixel C (Máy tính bảng) | |
---|---|
Độ xoay màn hình = 0 |
|
Độ xoay màn hình = 270 |
Tính toán độ xoay hình ảnh
ImageAnalysis
Analyzer
của ImageAnalysis
nhận hình ảnh từ camera dưới dạng ImageProxy
. Mỗi hình ảnh đều chứa thông tin độ xoay, mà có thể truy cập được qua:
val rotation = imageProxy.imageInfo.rotationDegrees
Giá trị này biểu thị số độ mà hình ảnh cần được xoay theo chiều kim đồng hồ để khớp với độ xoay mục tiêu của ImageAnalysis
. Trong bối cảnh của ứng dụng Android, độ xoay mục tiêu của ImageAnalysis
thường sẽ khớp với hướng của màn hình.
ImageCapture
Lệnh gọi lại được đính kèm vào một thực thể ImageCapture
để gửi tín hiệu khi có kết quả chụp. Kết quả có thể là ảnh đã chụp hoặc lỗi.
Khi chụp ảnh, lệnh gọi lại được cung cấp có thể thuộc một trong những loại sau:
OnImageCapturedCallback
: Nhận hình ảnh với quyền truy cập vào bộ nhớ dưới dạngImageProxy
.OnImageSavedCallback
: Được gọi khi ảnh chụp được lưu trữ thành công tại vị trí đượcImageCapture.OutputFileOptions
chỉ định. Tuỳ chọn có thể chỉ địnhFile
,OutputStream
hoặc một vị trí trongMediaStore
.
Việc xoay ảnh chụp, bất kể định dạng của ảnh là gì (ImageProxy
, File
, OutputStream
, MediaStore Uri
) có nghĩa là số độ mà ảnh đã chụp cần xoay theo chiều kim đồng hồ để khớp với độ xoay mục tiêu của ImageCapture
. Xin nhắc lại, trong ngữ cảnh của một ứng dụng Android độ xoay mục tiêu thường sẽ khớp với hướng của màn hình.
Việc truy xuất độ xoay của ảnh chụp có thể được thực hiện theo một trong các cách sau:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
Xác định hướng xoay của hình ảnh
Các trường hợp sử dụng ImageAnalysis
và ImageCapture
nhận ImageProxy
từ camera sau khi chụp ảnh thành công. ImageProxy
gói ảnh và thông tin về ảnh đó, bao gồm cả độ xoay. Thông tin về độ xoay này thể hiện số độ mà bạn phải xoay hình ảnh để khớp với độ xoay mục tiêu của trường hợp sử dụng.
Nguyên tắc về độ xoay mục tiêu trong ImageCapture/ImageAnalysis
Vì nhiều thiết bị không xoay để đảo chiều dọc hoặc đảo chiều ngang theo mặc định, nên một số ứng dụng Android không hỗ trợ các chiều này. Việc ứng dụng có hỗ trợ việc này hay không sẽ thay đổi cách mà độ xoay mục tiêu của trường hợp sử dụng được cập nhật.
Dưới đây là hai bảng xác định cách đồng bộ hoá độ xoay mục tiêu của trường hợp sử dụng với độ xoay màn hình. Bảng đầu tiên hướng dẫn cách thực hiện việc này trong khi hỗ trợ cả bốn hướng; bảng thứ hai chỉ xử lý hướng thiết bị xoay theo mặc định.
Để chọn thực hiện nguyên tắc nào trong ứng dụng của bạn:
Xác minh liệu camera
Activity
trong ứng dụng của bạn có hướng bị khoá hay không, hướng đã mở khoá chưa hay camera ghi đè chế độ thay đổi cấu hình hướng.Quyết định liệu camera trong ứng dụng
Activity
có nên xử lý cả bốn hướng thiết bị (dọc, dọc lộn ngược, ngang và ngang lộn ngược) hay liệu camera chỉ nên xử lý các hướng được thiết bị, mà ứng dụng chay trên đó, hỗ trợ theo mặc định.
Hỗ trợ cả bốn hướng
Bảng này đề cập đến một số nguyên tắc nhất định cần tuân theo trong trường hợp thiết bị không xoay để đảo chiều dọc. Áp dụng điều tương tự với các thiết bị không xoay để đảo chiều ngang.
Trường hợp | Nguyên tắc | Chế độ một cửa sổ | Chế độ chia đôi màn hình thành nhiều cửa sổ |
---|---|---|---|
Hướng đã được mở khoá |
Thiết lập các trường hợp sử dụng mỗi khi tạo Activity , chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
||
Sử dụng onOrientationChanged() của OrientationEventListener .
Trong lệnh gọi lại, cập nhật độ xoay mục tiêu của trường hợp sử dụng. Thao tác này xử lý các trường hợp tại đó hệ thống không tái tạo Activity ngay cả sau khi một hướng thay đổi, chẳng hạn như khi thiết bị xoay 180 độ.
|
Cũng xử lý các trường hợp khi màn hình nằm theo chiều dọc lộn ngược và thiết bị không xoay để đảo ngược chiều dọc theo mặc định. |
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
|
Tuỳ chọn: Đặt thuộc tính screenOrientation của Activity thành fullSensor trong tệp AndroidManifest .
|
Thao tác này cho phép giao diện người dùng là thẳng đứng khi thiết bị ở chế độ dọc lộn ngược và cho phép hệ thống tái tạo Activity bất cứ khi nào thiết bị xoay 90 độ.
|
Không ảnh hưởng đến các thiết bị không xoay để đảo ngược màn hình dọc theo mặc định. Không hỗ trợ chế độ nhiều cửa sổ khi màn hình hiển thị theo chiều dọc lộn ngược. | |
Hướng bị khoá |
Chỉ thiết lập các trường hợp sử dụng một lần khi Activity được tạo ra lần đầu tiên, chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
||
Sử dụng onOrientationChanged() của OrientationEventListener .
Trong lệnh gọi lại, hãy cập nhật độ xoay mục tiêu của trường hợp sử dụng, ngoại trừ Bản xem trước.
|
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
||
Ghi đè hướng configChanges |
Chỉ thiết lập các trường hợp sử dụng một lần khi Activity được tạo ra lần đầu tiên, chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
||
Sử dụng onOrientationChanged() của OrientationEventListener .
Trong lệnh gọi lại, cập nhật độ xoay mục tiêu của trường hợp sử dụng.
|
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
||
Tuỳ chọn: Đặt thuộc tính screenOrientation của Hoạt động thành fullSensor trong tệp AndroidManifest. | Cho phép giao diện người dùng là thẳng đứng khi thiết bị nằm dọc lộn ngược. | Không ảnh hưởng đến các thiết bị không xoay để đảo ngược màn hình dọc theo mặc định. Không hỗ trợ chế độ nhiều cửa sổ khi màn hình theo chiều dọc lộn ngược. |
Chỉ hỗ trợ các hướng được thiết bị hỗ trợ
Chỉ hỗ trợ hướng mà thiết bị hỗ trợ theo mặc định (có thể bao gồm hoặc không bao gồm dọc lộn ngược/ngang lộn ngược).
Trường hợp | Nguyên tắc | Chế độ chia đôi màn hình thành nhiều cửa sổ |
---|---|---|
Hướng đã được mở khoá |
Thiết lập các trường hợp sử dụng mỗi khi tạo Activity , chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
|
Sử dụng onDisplayChanged() của DisplayListener . Trong lệnh gọi lại, cập nhật độ xoay mục tiêu của trường hợp sử dụng, chẳng hạn như khi xoay thiết bị 180 độ.
|
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
|
Hướng bị khoá |
Chỉ thiết lập các trường hợp sử dụng một lần khi Activity được tạo ra lần đầu tiên, chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
|
Sử dụng onOrientationChanged() của OrientationEventListener .
Trong lệnh gọi lại, cập nhật độ xoay mục tiêu của trường hợp sử dụng.
|
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
|
Ghi đè hướng configChanges |
Chỉ thiết lập các trường hợp sử dụng một lần khi Activity được tạo ra lần đầu tiên, chẳng hạn như trong lệnh gọi lại onCreate() của Activity .
|
|
Sử dụng onDisplayChanged() của DisplayListener . Trong lệnh gọi lại, cập nhật độ xoay mục tiêu của trường hợp sử dụng, chẳng hạn như khi xoay thiết bị 180 độ.
|
Cũng xử lý các trường hợp trong đó Activity không được tái tạo khi thiết bị xoay (90 độ chẳng hạn). Điều này xảy ra trên các thiết bị có kiểu dáng nhỏ khi ứng dụng chiếm một nửa màn hình và trên những thiết bị lớn hơn khi ứng dụng chiếm hai phần ba màn hình.
|
Hướng đã được mở khoá
Một Activity
có hướng được mở khoá khi hướng của màn hình (như dọc hoặc ngang) khớp với hướng thực của thiết bị, ngoại trừ trường hợp dọc/ngang lộn ngược, mà một số thiết bị không hỗ trợ theo mặc định. Để buộc thiết bị xoay sang cả bốn hướng, hãy đặt thuộc tính screenOrientation
của Activity
thành fullSensor
.
Ở chế độ nhiều cửa sổ, theo mặc định một thiết bị không hỗ trợ chế độ dọc/ngang lộn ngược sẽ không xoay thành dọc/ngang lộn ngược, ngay cả khi thuộc tính screenOrientation
của thiết bị được đặt thành fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
Hướng bị khoá
Màn hình có hướng bị khoá khi vẫn ở cùng một hướng hiển thị (chẳng hạn như dọc hoặc ngang) bất kể hướng thực của thiết bị là gì. Bạn có thể thực hiện việc này bằng cách chỉ định thuộc tính screenOrientation
của Activity
bên trong phần khai báo của nó trong tệp AndroidManifest.xml
.
Khi màn hình có hướng bị khoá, hệ thống sẽ không huỷ và tạo lại Activity
khi xoay thiết bị.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Ghi đè thay đổi về cấu hình hướng
Khi Activity
ghi đè thay đổi về cấu hình hướng, hệ thống sẽ không huỷ và tái tạo cấu hình đó khi hướng thực của thiết bị thay đổi.
Tuy nhiên, hệ thống sẽ cập nhật giao diện người dùng để phù hợp với hướng thực của thiết bị.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
Thiết lập các trường hợp sử dụng camera
Trong các trường hợp được mô tả ở trên, các trường hợp sử dụng camera có thể được thiết lập khi Activity
được tạo lần đầu tiên.
Trong trường hợp Activity
có hướng được mở khoá, việc thiết lập này sẽ được thực hiện mỗi khi xoay thiết bị, vì hệ thống sẽ huỷ và tái tạo Activity
khi hướng thay đổi. Kết quả là lần nào trường hợp sử dụng cũng sẽ cài đặt độ xoay mục tiêu cho phù hợp với hướng của màn hình theo mặc định.
Trong trường hợp Activity
có hướng bị hoá hoặc hoạt động ghi đè thay đổi cấu hình hướng, thì việc thiết lập này sẽ được thực hiện một lần khi Activity
được tạo lần đầu tiên.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
Thiết lập OrientationEventListener
Việc sử dụng OrientationEventListener
cho phép bạn liên tục cập nhật độ xoay mục tiêu của các trường hợp sử dụng camera khi hướng thiết bị thay đổi.
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
Thiết lập DisplayListener
Việc sử dụng DisplayListener
cho phép bạn cập nhật độ xoay mục tiêu của trường hợp sử dụng camera trong một số trường hợp, chẳng hạn như khi hệ thống không huỷ và tái tạo Activity
sau khi thiết bị xoay 180 độ.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }