Bản xem trước của camera

Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên sử dụng CameraX, trừ phi ứng dụng yêu cầu các tính năng cụ thể ở cấp thấp trong Camera2. Cả CameraX và Camera2 đều hỗ trợ Android 5.0 (API cấp 21) trở lên.

Máy ảnh và bản xem trước của máy ảnh không phải lúc nào cũng ở cùng một hướng trên thiết bị Android.

Máy ảnh được đặt ở vị trí cố định trên một thiết bị, bất kể thiết bị đó là điện thoại, máy tính bảng hay máy tính. Khi hướng của thiết bị thay đổi, hướng của máy ảnh cũng sẽ thay đổi.

Do đó, các ứng dụng máy ảnh thường giả định một mối quan hệ cố định giữa hướng của thiết bị và tỷ lệ khung hình của bản xem trước của máy ảnh. Khi điện thoại ở hướng dọc, bản xem trước của máy ảnh được giả định là cao hơn chiều rộng. Khi điện thoại (và máy ảnh) xoay sang chế độ ngang, bản xem trước của máy ảnh dự kiến sẽ rộng hơn chiều cao.

Tuy nhiên, những giả định này đang bị thách thức bởi các hệ số hình dạng mới, chẳng hạn như thiết bị có thể gập và các chế độ hiển thị như nhiều cửa sổnhiều màn hình. Thiết bị có thể gập lại thay đổi kích thước màn hình và tỷ lệ khung hình mà không thay đổi hướng. Chế độ nhiều cửa sổ ràng buộc các ứng dụng máy ảnh trong một phần màn hình, điều chỉnh tỷ lệ bản xem trước của máy ảnh bất kể hướng của thiết bị. Chế độ nhiều màn hình cho phép sử dụng màn hình phụ có thể không cùng hướng với màn hình chính.

Hướng máy ảnh

Định nghĩa về khả năng tương thích với Android chỉ định rằng bạn "PHẢI định hướng cảm biến hình ảnh của máy ảnh để chiều dài của máy ảnh phù hợp với chiều dài của màn hình. Tức là khi thiết bị được giữ ở hướng ngang, máy ảnh PHẢI chụp ảnh ở hướng ngang. Thuộc tính này áp dụng bất kể hướng tự nhiên của thiết bị; nghĩa là áp dụng cho thiết bị chính ngang cũng như thiết bị chính dọc."

Việc sắp xếp máy ảnh với màn hình sẽ tối đa hoá vùng hiển thị của kính ngắm máy ảnh trong một ứng dụng máy ảnh. Ngoài ra, cảm biến hình ảnh thường xuất dữ liệu theo tỷ lệ khung hình ngang, tỷ lệ khung hình 4:3 là phổ biến nhất.

Cảm biến của điện thoại và máy ảnh đều ở hướng dọc.
Hình 1. Mối quan hệ thông thường giữa hướng cảm biến của điện thoại và máy ảnh.

Hướng tự nhiên của cảm biến máy ảnh là hướng ngang. Trong hình 1, cảm biến của máy ảnh mặt trước (máy ảnh trỏ theo cùng hướng với màn hình) xoay 270 độ so với điện thoại để tuân thủ Định nghĩa về khả năng tương thích với Android.

Để hiển thị chế độ xoay cảm biến cho các ứng dụng, API camera2 sẽ bao gồm một hằng số SENSOR_ORIENTATION. Đối với hầu hết điện thoại và máy tính bảng, thiết bị báo cáo hướng cảm biến là 270 độ đối với máy ảnh mặt trước và 90 độ (góc nhìn từ mặt sau của thiết bị) đối với máy ảnh mặt sau, điều này sẽ căn chỉnh cạnh dài của cảm biến với cạnh dài của thiết bị. Máy ảnh máy tính xách tay thường báo cáo hướng cảm biến là 0 hoặc 180 độ.

Vì các cảm biến hình ảnh của máy ảnh xuất dữ liệu (vùng đệm hình ảnh) theo hướng tự nhiên của cảm biến (ngang), nên vùng đệm hình ảnh phải xoay số độ do SENSOR_ORIENTATION chỉ định để bản xem trước của máy ảnh xuất hiện thẳng đứng theo hướng tự nhiên của thiết bị. Đối với máy ảnh mặt trước, chế độ xoay theo chiều kim đồng hồ; đối với máy ảnh mặt sau, theo chiều kim đồng hồ.

Ví dụ: đối với máy ảnh mặt trước trong hình 1, vùng đệm hình ảnh do cảm biến của máy ảnh tạo ra sẽ có dạng như sau:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh
            bị lệch sang một bên, trên cùng bên trái.

Hình ảnh phải được xoay 270 độ ngược chiều kim đồng hồ để hướng của bản xem trước khớp với hướng thiết bị:

Cảm biến của máy ảnh theo hướng dọc với hình ảnh thẳng đứng.

Máy ảnh mặt sau sẽ tạo ra một vùng đệm hình ảnh có cùng hướng với vùng đệm ở trên, nhưng SENSOR_ORIENTATION là 90 độ. Do đó, vùng đệm được xoay 90 độ theo chiều kim đồng hồ.

Xoay thiết bị

Độ xoay thiết bị là số độ mà một thiết bị được xoay so với hướng tự nhiên. Ví dụ: điện thoại theo hướng ngang sẽ xoay thiết bị là 90 hoặc 270 độ, tuỳ thuộc vào hướng xoay.

Vùng đệm hình ảnh cảm biến của máy ảnh phải được xoay cùng số độ với chế độ xoay thiết bị (ngoài độ hướng của cảm biến) để bản xem trước của máy ảnh xuất hiện thẳng đứng.

Tính toán hướng

Hướng phù hợp của bản xem trước của máy ảnh sẽ xét đến hướng cảm biến và hướng xoay thiết bị.

Độ xoay tổng thể của vùng đệm hình ảnh cảm biến có thể được tính bằng công thức sau:

rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360

trong đó sign1 đối với camera mặt trước, -1 đối với camera mặt sau.

Đối với máy ảnh mặt trước, vùng đệm hình ảnh được xoay ngược chiều kim đồng hồ (từ hướng tự nhiên của cảm biến). Đối với máy ảnh mặt sau, vùng đệm hình ảnh cảm biến được xoay theo chiều kim đồng hồ.

Biểu thức deviceOrientationDegrees * sign + 360 chuyển đổi chế độ xoay thiết bị từ ngược chiều kim đồng hồ sang chiều kim đồng hồ đối với máy ảnh mặt sau (ví dụ: chuyển đổi 270 độ ngược chiều kim đồng hồ thành 90 độ theo chiều kim đồng hồ). Phép toán modulo điều chỉnh kết quả thành 360 độ (ví dụ: điều chỉnh tỷ lệ xoay 540 độ thành 180 độ).

Các API khác nhau báo cáo việc xoay thiết bị theo cách khác nhau:

  • Display#getRotation() cung cấp chế độ xoay ngược chiều kim đồng hồ của thiết bị (theo góc nhìn của người dùng). Giá trị này phụ thuộc vào công thức ở trên.
  • OrientationEventListener#onOrientationChanged() trả về chế độ xoay theo chiều kim đồng hồ của thiết bị (từ góc nhìn của người dùng). Phủ định giá trị sẽ sử dụng trong công thức ở trên.

Camera mặt trước

Bản xem trước và cảm biến của máy ảnh đều ở hướng ngang, cảm biến nằm ở bên phải.
Hình 2. Bản xem trước của máy ảnh và cảm biến với điện thoại đã xoay 90 độ sang hướng ngang.

Dưới đây là vùng đệm hình ảnh do cảm biến máy ảnh tạo ra trong hình 2:

Cảm biến của máy ảnh theo hướng ngang với hình ảnh thẳng đứng.

Vùng đệm phải xoay 270 độ ngược chiều kim đồng hồ để điều chỉnh hướng cảm biến (xem phần Hướng của máy ảnh ở trên):

Cảm biến của máy ảnh đã xoay theo hướng dọc với hình ảnh lệch sang một bên,
            trên cùng bên phải.

Sau đó, vùng đệm được xoay thêm 90 độ ngược chiều kim đồng hồ để tính đến việc xoay thiết bị, dẫn đến hướng chính xác của bản xem trước của máy ảnh trong hình 2:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh thẳng đứng.

Dưới đây là máy ảnh đã chuyển sang phải theo hướng ngang:

Bản xem trước và cảm biến của máy ảnh đều ở hướng ngang, nhưng cảm biến bị lộn ngược.
Hình 3. Bản xem trước của máy ảnh và cảm biến khi điện thoại xoay 270 độ (hoặc -90 độ) sang hướng ngang.

Dưới đây là vùng đệm hình ảnh:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh bị lộn ngược.

Vùng đệm phải xoay 270 độ ngược chiều kim đồng hồ để điều chỉnh hướng cảm biến:

Cảm biến của máy ảnh được xếp hạng theo hướng dọc với hình ảnh lệch sang một bên,
            trên cùng bên trái.

Sau đó, vùng đệm được xoay thêm 270 độ ngược chiều kim đồng hồ để tính đến việc xoay thiết bị:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh thẳng đứng.

Máy ảnh mặt sau

Máy ảnh mặt sau thường có hướng cảm biến 90 độ (khi xem từ mặt sau thiết bị). Khi định hướng bản xem trước của máy ảnh, vùng đệm hình ảnh cảm biến sẽ được xoay theo chiều kim đồng hồ theo mức xoay cảm biến (thay vì ngược chiều kim đồng hồ như máy ảnh mặt trước), sau đó vùng đệm hình ảnh được xoay ngược chiều kim đồng hồ theo mức xoay thiết bị.

Bản xem trước và cảm biến của máy ảnh đều ở hướng ngang, nhưng cảm biến bị lộn ngược.
Hình 4. Điện thoại có máy ảnh mặt sau theo hướng ngang (quay 270 hoặc -90 độ).

Dưới đây là vùng đệm hình ảnh từ cảm biến của máy ảnh trong hình 4:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh bị lộn ngược.

Vùng đệm phải xoay 90 độ theo chiều kim đồng hồ để điều chỉnh hướng cảm biến:

Cảm biến của máy ảnh được xếp hạng theo hướng dọc với hình ảnh lệch sang một bên,
            trên cùng bên trái.

Sau đó, vùng đệm được xoay 270 độ ngược chiều kim đồng hồ để tính đến chế độ xoay của thiết bị:

Cảm biến của máy ảnh được xoay theo hướng ngang với hình ảnh thẳng đứng.

Tỷ lệ khung hình

Tỷ lệ khung hình hiển thị sẽ thay đổi khi hướng thiết bị thay đổi cũng như khi thiết bị có thể gập lại và mở ra, khi đổi kích thước cửa sổ trong môi trường nhiều cửa sổ và khi ứng dụng mở trên màn hình phụ.

Bạn phải định hướng và điều chỉnh theo tỷ lệ vùng đệm hình ảnh cảm biến của máy ảnh để phù hợp với hướng và tỷ lệ khung hình của phần tử trên giao diện người dùng kính ngắm khi giao diện người dùng tự động thay đổi hướng (dù có hay không) thay đổi hướng của thiết bị.

Trên các kiểu dáng mới hoặc trong môi trường nhiều cửa sổ hay nhiều màn hình, nếu ứng dụng của bạn giả định rằng bản xem trước của máy ảnh có cùng hướng với thiết bị (dọc hoặc ngang), thì bản xem trước của bạn có thể được định hướng không chính xác, được điều chỉnh theo tỷ lệ không chính xác hoặc cả hai.

Thiết bị có thể gập lại đã mở ra với bản xem trước của máy ảnh dọc bị xoay sang một bên.
Hình 5. Thiết bị có thể gập lại chuyển đổi từ tỷ lệ khung hình dọc sang ngang nhưng cảm biến của máy ảnh vẫn ở hướng dọc.

Trong hình 5, ứng dụng giả định nhầm rằng thiết bị đã xoay 90 độ ngược chiều kim đồng hồ; và do đó, ứng dụng đã xoay bản xem trước cùng một mức.

Thiết bị có thể gập lại khi đã mở với bản xem trước của máy ảnh ở vị trí thẳng đứng nhưng bị gấp do tỷ lệ không chính xác.
Hình 6. Thiết bị có thể gập lại chuyển đổi từ tỷ lệ khung hình dọc sang ngang nhưng cảm biến của máy ảnh vẫn ở hướng dọc.

Trong hình 6, ứng dụng không điều chỉnh tỷ lệ khung hình của vùng đệm hình ảnh để cho phép điều chỉnh tỷ lệ phù hợp cho vừa với kích thước mới của phần tử trên giao diện người dùng xem trước máy ảnh.

Các ứng dụng máy ảnh hướng cố định thường gặp sự cố trên thiết bị có thể gập lại và các thiết bị màn hình lớn khác, chẳng hạn như máy tính xách tay:

Bản xem trước của máy ảnh trên máy tính xách tay ở chế độ thẳng đứng nhưng giao diện người dùng của ứng dụng được lệch sang một bên.
Hình 7. Ứng dụng dọc cố định trên máy tính xách tay.

Trong hình 7, giao diện người dùng của ứng dụng máy ảnh bị lệch sang một bên vì hướng của ứng dụng chỉ bị hạn chế ở hướng dọc. Hình ảnh kính ngắm được định hướng đúng cách so với cảm biến của máy ảnh.

Lồng ghép chế độ dọc

Các ứng dụng máy ảnh không hỗ trợ chế độ nhiều cửa sổ (resizeableActivity="false") và hạn chế hướng của ứng dụng (screenOrientation="portrait" hoặc screenOrientation="landscape") có thể được đặt ở chế độ dọc lồng ghép trên thiết bị màn hình lớn để định hướng đúng cách cho bản xem trước của máy ảnh.

Lồng ghép hộp thư (phần lồng ghép) ở chế độ dọc cho các ứng dụng chỉ ở chế độ dọc theo hướng dọc ngay cả khi tỷ lệ khung hình hiển thị là ngang. Ứng dụng chỉ hiển thị theo hướng ngang được tạo hiệu ứng hòm thư theo hướng ngang mặc dù tỷ lệ khung hình của màn hình là dọc. Hình ảnh máy ảnh được xoay để căn chỉnh với giao diện người dùng của ứng dụng, bị cắt để phù hợp với tỷ lệ khung hình của bản xem trước của máy ảnh, sau đó được điều chỉnh theo tỷ lệ để lấp đầy bản xem trước.

Chế độ dọc lồng ghép được kích hoạt khi tỷ lệ khung hình của cảm biến hình ảnh máy ảnh và tỷ lệ khung hình của hoạt động chính trong ứng dụng không khớp.

Bản xem trước của máy ảnh và giao diện người dùng ứng dụng theo hướng dọc phù hợp trên máy tính xách tay.
            Hình ảnh xem trước rộng được điều chỉnh theo tỷ lệ và cắt để vừa với hướng dọc.
Hình 8. Ứng dụng dọc cố định hướng ở chế độ dọc lồng ghép trên máy tính xách tay.

Trong hình 8, ứng dụng máy ảnh chỉ hiển thị theo hướng dọc đã được xoay để hiển thị giao diện người dùng thẳng đứng trên màn hình máy tính xách tay. Ứng dụng có dạng hòm thư do sự khác biệt về tỷ lệ khung hình giữa ứng dụng dọc và màn hình ngang. Hình ảnh xem trước của máy ảnh đã được xoay để bù cho việc xoay giao diện người dùng của ứng dụng (do chế độ dọc đã lồng ghép), đồng thời, hình ảnh đã được cắt và điều chỉnh theo tỷ lệ để vừa với hướng dọc, giúp giảm trường nhìn.

Xoay, cắt, điều chỉnh theo tỷ lệ

Chế độ lồng ghép dọc được gọi cho ứng dụng máy ảnh chỉ hiển thị theo hướng dọc trên màn hình có tỷ lệ khung hình ngang:

Bản xem trước của máy ảnh trên máy tính xách tay ở chế độ thẳng đứng nhưng giao diện người dùng của ứng dụng được lệch sang một bên.
Hình 9. Ứng dụng dọc cố định trên máy tính xách tay.

Ứng dụng có dạng hòm thư theo hướng dọc:

Ứng dụng được xoay sang hướng dọc và có dạng hòm thư. Hình ảnh lệch sang một bên, ở trên cùng bên phải.

Hình ảnh máy ảnh được xoay 90 độ để điều chỉnh hướng đổi hướng của ứng dụng:

Hình ảnh cảm biến đã xoay 90 độ để làm cho cảm biến thẳng đứng.

Hình ảnh được cắt theo tỷ lệ khung hình của bản xem trước của máy ảnh, sau đó được điều chỉnh theo tỷ lệ để lấp đầy bản xem trước (trường nhìn bị giảm):

Hình ảnh máy ảnh đã cắt được điều chỉnh theo tỷ lệ để lấp đầy bản xem trước của máy ảnh.

Trên các thiết bị có thể gập lại, hướng của cảm biến máy ảnh có thể ở chế độ dọc trong khi tỷ lệ khung hình của màn hình là ngang:

Bản xem trước của máy ảnh và giao diện người dùng của ứng dụng bị chuyển sang một bên trong màn hình rộng, ở trạng thái mở.
Hình 10. Thiết bị khi mở ra có ứng dụng máy ảnh chỉ dành cho chế độ dọc cùng màn hình và cảm biến máy ảnh với các tỷ lệ khung hình khác nhau.

Vì bản xem trước của máy ảnh được xoay để điều chỉnh cho hướng cảm biến, nên hình ảnh sẽ được định hướng đúng cách trong kính ngắm, nhưng ứng dụng chỉ hiển thị theo hướng dọc bị lệch sang một bên.

Chế độ dọc lồng ghép chỉ cần tạo hòm thư cho ứng dụng theo hướng dọc để định hướng đúng cách ứng dụng và bản xem trước của máy ảnh:

Ứng dụng dạng hòm thư ở hướng dọc với bản xem trước của máy ảnh thẳng đứng trên thiết bị có thể gập lại.

API

Kể từ Android 12 (API cấp 31), các ứng dụng cũng có thể kiểm soát rõ ràng chế độ dọc lồng ghép bằng thuộc tính SCALER_ROTATE_AND_CROP của lớp CaptureRequest.

Giá trị mặc định là SCALER_ROTATE_AND_CROP_AUTO, cho phép hệ thống gọi chế độ dọc lồng ghép. SCALER_ROTATE_AND_CROP_90 là hành vi của chế độ dọc lồng ghép như mô tả ở trên.

Không phải thiết bị nào cũng hỗ trợ tất cả giá trị SCALER_ROTATE_AND_CROP. Để lấy danh sách các giá trị được hỗ trợ, hãy tham khảo CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.

CameraX

Thư viện Jetpack CameraX giúp việc tạo kính ngắm của máy ảnh phù hợp với hướng cảm biến và xoay thiết bị là một tác vụ đơn giản.

Phần tử bố cục PreviewView sẽ tạo bản xem trước của máy ảnh, tự động điều chỉnh theo hướng cảm biến, xoay thiết bị và điều chỉnh theo tỷ lệ. PreviewView duy trì tỷ lệ khung hình của hình ảnh máy ảnh bằng cách áp dụng loại tỷ lệ FILL_CENTER, căn giữa hình ảnh nhưng có thể cắt hình ảnh cho phù hợp với kích thước của PreviewView. Để tạo hiệu ứng hòm thư cho hình ảnh máy ảnh, hãy đặt loại tỷ lệ thành FIT_CENTER.

Để tìm hiểu những thông tin cơ bản về cách tạo bản xem trước cho máy ảnh bằng PreviewView, hãy xem bài viết Triển khai bản xem trước.

Để biết cách triển khai mẫu đầy đủ, hãy xem kho lưu trữ CameraXBasic trên GitHub.

Kính ngắm của máy ảnh

Tương tự như trường hợp sử dụng Preview (Xem trước), thư viện CameraViewfinder cung cấp một bộ công cụ giúp đơn giản hoá việc tạo bản xem trước của máy ảnh. API này không phụ thuộc vào CameraX Core, vì vậy, bạn có thể tích hợp liền mạch vào cơ sở mã Camera2 hiện có của mình.

Thay vì sử dụng trực tiếp Surface, bạn có thể sử dụng tiện ích CameraViewfinder để hiển thị nguồn cấp dữ liệu máy ảnh cho Camera2.

CameraViewfinder sử dụng nội bộ TextureView hoặc SurfaceView để hiển thị nguồn cấp dữ liệu máy ảnh và áp dụng các phép biến đổi cần thiết để hiển thị kính ngắm chính xác. Việc này bao gồm việc sửa tỷ lệ khung hình, tỷ lệ và chế độ xoay.

Để yêu cầu nền tảng từ đối tượng CameraViewfinder, bạn cần tạo ViewfinderSurfaceRequest.

Yêu cầu này chứa các yêu cầu về độ phân giải bề mặt và thông tin về thiết bị máy ảnh từ CameraCharacteristics.

Việc gọi requestSurfaceAsync() sẽ gửi yêu cầu đến trình cung cấp nền tảng, là TextureView hoặc SurfaceView và sẽ nhận được ListenableFutureSurface.

Việc gọi markSurfaceSafeToRelease() sẽ thông báo cho nhà cung cấp nền tảng rằng nền tảng là không cần thiết và các tài nguyên liên quan có thể được giải phóng.

Kotlin


fun startCamera(){
    val previewResolution = Size(width, height)
    val viewfinderSurfaceRequest =
        ViewfinderSurfaceRequest(previewResolution, characteristics)
    val surfaceListenableFuture =
        cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)

    Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
        override fun onSuccess(surface: Surface) {
            /* create a CaptureSession using this surface as usual */
        }
        override fun onFailure(t: Throwable) { /* something went wrong */}
    }, ContextCompat.getMainExecutor(context))
}

Java


    void startCamera(){
        Size previewResolution = new Size(width, height);
        ViewfinderSurfaceRequest viewfinderSurfaceRequest =
                new ViewfinderSurfaceRequest(previewResolution, characteristics);
        ListenableFuture<Surface> surfaceListenableFuture =
                cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest);

        Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() {
            @Override
            public void onSuccess(Surface result) {
                /* create a CaptureSession using this surface as usual */
            }
            @Override public void onFailure(Throwable t) { /* something went wrong */}
        },  ContextCompat.getMainExecutor(context));
    }

SurfaceView

SurfaceView là một phương pháp đơn giản để tạo bản xem trước của máy ảnh nếu bản xem trước đó không yêu cầu xử lý và không phải là ảnh động.

SurfaceView tự động xoay vùng đệm hình ảnh cảm biến của máy ảnh để phù hợp với hướng hiển thị, tính đến cả hướng cảm biến và hướng xoay thiết bị. Tuy nhiên, vùng đệm hình ảnh được điều chỉnh theo tỷ lệ để vừa với kích thước SurfaceView mà không cần xem xét tỷ lệ khung hình.

Bạn phải đảm bảo tỷ lệ khung hình của vùng đệm hình ảnh khớp với tỷ lệ khung hình của SurfaceView. Bạn có thể thực hiện việc này bằng cách mở rộng nội dung của SurfaceView trong phương thức onMeasure() của thành phần:

(Mã nguồn computeRelativeRotation() nằm trong Xoay vòng tương đối bên dưới.)

Kotlin

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = MeasureSpec.getSize(widthMeasureSpec)
    val height = MeasureSpec.getSize(heightMeasureSpec)

    val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees)

    if (previewWidth > 0f && previewHeight > 0f) {
        /* Scale factor required to scale the preview to its original size on the x-axis. */
        val scaleX =
            if (relativeRotation % 180 == 0) {
                width.toFloat() / previewWidth
            } else {
                width.toFloat() / previewHeight
            }
        /* Scale factor required to scale the preview to its original size on the y-axis. */
        val scaleY =
            if (relativeRotation % 180 == 0) {
                height.toFloat() / previewHeight
            } else {
                height.toFloat() / previewWidth
            }

        /* Scale factor required to fit the preview to the SurfaceView size. */
        val finalScale = min(scaleX, scaleY)

        setScaleX(1 / scaleX * finalScale)
        setScaleY(1 / scaleY * finalScale)
    }
    setMeasuredDimension(width, height)
}

Java

@Override
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees);

    if (previewWidth > 0f && previewHeight > 0f) {

        /* Scale factor required to scale the preview to its original size on the x-axis. */
        float scaleX = (relativeRotation % 180 == 0)
                       ? (float) width / previewWidth
                       : (float) width / previewHeight;

        /* Scale factor required to scale the preview to its original size on the y-axis. */
        float scaleY = (relativeRotation % 180 == 0)
                       ? (float) height / previewHeight
                       : (float) height / previewWidth;

        /* Scale factor required to fit the preview to the SurfaceView size. */
        float finalScale = Math.min(scaleX, scaleY);

        setScaleX(1 / scaleX * finalScale);
        setScaleY(1 / scaleY * finalScale);
    }
    setMeasuredDimension(width, height);
}

Để biết thêm thông tin chi tiết về cách triển khai SurfaceView làm bản xem trước của máy ảnh, hãy xem bài viết Hướng của máy ảnh.

Chế độ xem kết cấu

TextureView kém hiệu quả hơn SurfaceView và tốn nhiều công sức hơn, nhưng TextureView cho phép bạn kiểm soát tối đa bản xem trước của máy ảnh.

TextureView xoay vùng đệm hình ảnh cảm biến dựa trên hướng cảm biến nhưng không xử lý việc xoay thiết bị hoặc điều chỉnh tỷ lệ xem trước.

Việc điều chỉnh tỷ lệ và xoay có thể được mã hoá trong phép biến đổi Matrix (Matrix). Để tìm hiểu cách điều chỉnh tỷ lệ và xoay TextureView sao cho chính xác, hãy xem bài viết Hỗ trợ các nền tảng có thể đổi kích thước trong ứng dụng máy ảnh

Độ xoay tương đối

Độ xoay tương đối của cảm biến máy ảnh là mức xoay cần thiết để điều chỉnh đầu ra của cảm biến máy ảnh với hướng của thiết bị.

Độ xoay tương đối được các thành phần như SurfaceViewTextureView sử dụng để xác định hệ số tỷ lệ x và y cho hình ảnh xem trước. Hàm này cũng được dùng để chỉ định chế độ xoay của vùng đệm hình ảnh cảm biến.

Các lớp CameraCharacteristicsSurface cho phép tính toán độ xoay tương đối của cảm biến máy ảnh:

Kotlin

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public fun computeRelativeRotation(
    characteristics: CameraCharacteristics,
    surfaceRotationDegrees: Int
): Int {
    val sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    // Reverse device orientation for back-facing cameras.
    val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT
    ) 1 else -1

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
}

Java

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public int computeRelativeRotation(
    CameraCharacteristics characteristics,
    int surfaceRotationDegrees
){
    Integer sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // Reverse device orientation for back-facing cameras.
    int sign = characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1;

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360;
}

Chỉ số cửa sổ

Bạn không nên sử dụng kích thước màn hình để xác định kích thước của kính ngắm máy ảnh; ứng dụng máy ảnh có thể đang chạy trên một phần của màn hình, ở chế độ nhiều cửa sổ trên thiết bị di động hoặc chế độ thoát trên ChromeOS.

WindowManager#getCurrentWindowMetrics() (được thêm vào API cấp 30) trả về kích thước của cửa sổ ứng dụng thay vì kích thước màn hình. Các phương thức WindowMetricsCalculator#computeCurrentWindowMetrics()WindowInfoTracker#currentWindowMetrics() trong thư viện Jetpack WindowManager cung cấp sự hỗ trợ tương tự có khả năng tương thích ngược với API cấp 14.

xoay 180 độ

Xoay 180 độ của thiết bị (ví dụ: từ hướng tự nhiên sang hướng tự nhiên lộn ngược) không kích hoạt lệnh gọi lại onConfigurationChanged(). Do đó, bản xem trước của máy ảnh có thể bị lộn ngược.

Để phát hiện chế độ xoay 180 độ, hãy triển khai DisplayListener và kiểm tra chế độ xoay thiết bị bằng lệnh gọi đến Display#getRotation() trong lệnh gọi lại onDisplayChanged().

Tài nguyên dành riêng

Trước Android 10, chỉ hoạt động hiển thị trên cùng trong môi trường nhiều cửa sổ là ở trạng thái RESUMED. Điều này khiến người dùng bối rối vì hệ thống không cung cấp chỉ báo về hoạt động nào đã được tiếp tục.

Android 10 (API cấp 29) đã ra mắt tính năng tiếp tục nhiều lần khi tất cả các hoạt động hiển thị đều ở trạng thái RESUMED. Các hoạt động hiển thị vẫn có thể chuyển sang trạng thái PAUSED, chẳng hạn như một hoạt động rõ nằm ở đầu hoạt động hoặc hoạt động không thể làm tâm điểm, chẳng hạn như ở chế độ hình trong hình (xem phần Hỗ trợ tính năng Hình trong hình).

Ứng dụng dùng máy ảnh, micrô hoặc tài nguyên độc quyền hoặc singleton ở API cấp 29 trở lên phải hỗ trợ tính năng tiếp tục nhiều lần. Ví dụ: nếu 3 hoạt động được tiếp tục muốn sử dụng máy ảnh, thì chỉ một hoạt động có thể truy cập vào tài nguyên độc quyền này. Mỗi hoạt động phải triển khai một lệnh gọi lại onDisconnected() để nhận biết quyền truy cập trước vào máy ảnh bằng một hoạt động có mức độ ưu tiên cao hơn.

Để biết thêm thông tin, hãy xem bài viết Tiếp tục nhiều lần.

Tài nguyên khác