Chào bạn! Chào mừng bạn quay lại với loạt bài viết của chúng tôi về CameraX và Jetpack Compose. Trong các bài đăng trước, chúng ta đã tìm hiểu những kiến thức cơ bản về cách thiết lập bản xem trước camera và thêm chức năng nhấn để lấy nét.
🧱 Phần 1: Xây dựng bản xem trước cơ bản của camera bằng cấu phần camera-compose mới. Chúng ta đã đề cập đến việc xử lý quyền và tích hợp cơ bản.
👆 Phần 2: Sử dụng hệ thống cử chỉ, đồ hoạ và các coroutine của Compose để triển khai tính năng nhấn để lấy nét trực quan.
🔦 Phần 3 (bài đăng này): Khám phá cách phủ các phần tử giao diện người dùng Compose lên trên bản xem trước camera để mang lại trải nghiệm phong phú hơn cho người dùng.
📂 Phần 4: Sử dụng các API thích ứng và khung ảnh động Compose để tạo ảnh động mượt mà khi chuyển đổi sang và từ chế độ mặt bàn trên điện thoại có thể gập lại.
Trong bài đăng này, chúng ta sẽ tìm hiểu một điều gì đó hấp dẫn hơn về mặt thị giác: triển khai hiệu ứng làm nổi bật trên bản xem trước camera, sử dụng tính năng nhận diện khuôn mặt làm cơ sở cho hiệu ứng này. Bạn hỏi vì sao ư? Tôi không chắc. Nhưng chắc chắn là nó trông rất thú vị 🙂. Và quan trọng hơn, nó minh hoạ cách chúng ta có thể dễ dàng chuyển đổi toạ độ cảm biến thành toạ độ giao diện người dùng, cho phép chúng ta sử dụng các toạ độ đó trong Compose!
Bật tính năng phát hiện khuôn mặt
Trước tiên, hãy sửa đổi CameraPreviewViewModel để bật tính năng phát hiện khuôn mặt. Chúng ta sẽ sử dụng API Camera2Interop. API này cho phép chúng ta tương tác với Camera2 API cơ bản từ CameraX. Điều này cho phép chúng ta sử dụng các tính năng của camera mà CameraX không trực tiếp hiển thị. Chúng ta cần thực hiện những thay đổi sau:
- Tạo một StateFlow chứa ranh giới khuôn mặt dưới dạng danh sách
Rect. - Đặt lựa chọn yêu cầu chụp
STATISTICS_FACE_DETECT_MODEthành FULL (ĐẦY ĐỦ) để bật tính năng phát hiện khuôn mặt. - Đặt
CaptureCallbackđể lấy thông tin khuôn mặt từ kết quả chụp.
class CameraPreviewViewModel : ViewModel() { ... private val _sensorFaceRects = MutableStateFlow(listOf<Rect>()) val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow() private val cameraPreviewUseCase = Preview.Builder() .apply { Camera2Interop.Extender(this) .setCaptureRequestOption( CaptureRequest.STATISTICS_FACE_DETECT_MODE, CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL ) .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) result.get(CaptureResult.STATISTICS_FACES) ?.map { face -> face.bounds.toComposeRect() } ?.toList() ?.let { faces -> _sensorFaceRects.update { faces } } } }) } .build().apply { ... }
Với những thay đổi này, mô hình chế độ xem của chúng ta hiện phát ra một danh sách các đối tượng Rect đại diện cho các hộp giới hạn của khuôn mặt được phát hiện theo toạ độ cảm biến.
Dịch toạ độ cảm biến sang toạ độ giao diện người dùng
Các hộp giới hạn của những khuôn mặt được phát hiện mà chúng ta đã lưu trữ trong phần trước sử dụng toạ độ trong hệ toạ độ cảm biến. Để vẽ các hộp giới hạn trong giao diện người dùng, chúng ta cần chuyển đổi các toạ độ này sao cho chúng chính xác trong hệ thống toạ độ Compose. Chúng ta cần:
- Chuyển đổi toạ độ cảm biến thành toạ độ vùng đệm xem trước
- Chuyển đổi toạ độ vùng đệm xem trước thành toạ độ giao diện người dùng Compose
Các phép biến đổi này được thực hiện bằng cách sử dụng ma trận biến đổi. Mỗi phép biến đổi có ma trận riêng:
SurfaceRequestcủa chúng ta giữ một thực thểTransformationInfo, chứa một ma trậnsensorToBufferTranform.CameraXViewfindercủa chúng tôi có mộtCoordinateTransformerđược liên kết. Bạn có thể nhớ rằng chúng ta đã sử dụng bộ chuyển đổi này trong bài đăng trên blog trước để chuyển đổi toạ độ nhấn để lấy tiêu điểm.
Chúng ta có thể tạo một phương thức trợ giúp có thể thực hiện việc chuyển đổi cho chúng ta:
private fun List<Rect>.transformToUiCoords( transformationInfo: SurfaceRequest.TransformationInfo?, uiToBufferCoordinateTransformer: MutableCoordinateTransformer ): List<Rect> = this.map { sensorRect -> val bufferToUiTransformMatrix = Matrix().apply { setFrom(uiToBufferCoordinateTransformer.transformMatrix) invert() } val sensorToBufferTransformMatrix = Matrix().apply { transformationInfo?.let { setFrom(it.sensorToBufferTransform) } } val bufferRect = sensorToBufferTransformMatrix.map(sensorRect) val uiRect = bufferToUiTransformMatrix.map(bufferRect) uiRect }
- Chúng ta lặp lại danh sách các khuôn mặt được phát hiện và thực hiện quá trình biến đổi cho từng khuôn mặt.
CoordinateTransformer.transformMatrixmà chúng ta nhận được từCameraXViewfindersẽ chuyển đổi toạ độ từ giao diện người dùng sang toạ độ vùng đệm theo mặc định. Trong trường hợp này, chúng ta muốn ma trận hoạt động theo cách khác, chuyển đổi toạ độ vùng đệm thành toạ độ giao diện người dùng. Do đó, chúng ta sử dụng phương thứcinvert()để đảo ngược ma trận.- Trước tiên, chúng ta biến đổi khuôn mặt từ toạ độ cảm biến sang toạ độ vùng đệm bằng cách dùng
sensorToBufferTransformMatrix, sau đó biến đổi các toạ độ vùng đệm đó sang toạ độ giao diện người dùng bằng cách dùngbufferToUiTransformMatrix.
Triển khai hiệu ứng tiêu điểm
Bây giờ, hãy cập nhật thành phần kết hợp CameraPreviewContent để vẽ hiệu ứng tiêu điểm. Chúng ta sẽ dùng thành phần kết hợp Canvas để vẽ một mặt nạ chuyển màu lên bản xem trước, giúp các khuôn mặt được phát hiện xuất hiện:
@Composable fun CameraPreviewContent( viewModel: CameraPreviewViewModel, modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ) { val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle() val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle() val transformationInfo by produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) { try { surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo -> value = transformationInfo } awaitCancellation() } finally { surfaceRequest?.clearTransformationInfoListener() } } val shouldSpotlightFaces by remember { derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} } val spotlightColor = Color(0xDDE60991) .. surfaceRequest?.let { request -> val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = request, coordinateTransformer = coordinateTransformer, modifier = .. ) AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) { Canvas(Modifier.fillMaxSize()) { val uiFaceRects = sensorFaceRects.transformToUiCoords( transformationInfo = transformationInfo, uiToBufferCoordinateTransformer = coordinateTransformer ) // Fill the whole space with the color drawRect(spotlightColor) // Then extract each face and make it transparent uiFaceRects.forEach { faceRect -> drawRect( Brush.radialGradient( 0.4f to Color.Black, 1f to Color.Transparent, center = faceRect.center, radius = faceRect.minDimension * 2f, ), blendMode = BlendMode.DstOut ) } } } } }
Dưới đây là cách hoạt động:
- Chúng ta thu thập danh sách khuôn mặt từ mô hình chế độ xem.
- Để đảm bảo chúng ta không kết hợp lại toàn bộ màn hình mỗi khi danh sách khuôn mặt được phát hiện thay đổi, chúng ta sẽ dùng
derivedStateOfđể theo dõi xem có khuôn mặt nào được phát hiện hay không. Sau đó, bạn có thể dùngAnimatedVisibilityđể tạo hiệu ứng ảnh động cho lớp phủ có màu xuất hiện và biến mất. surfaceRequestchứa thông tin chúng ta cần để biến đổi toạ độ cảm biến thành toạ độ vùng đệm trongSurfaceRequest.TransformationInfo. Chúng ta dùng hàmproduceStateđể thiết lập một trình nghe trong yêu cầu về vùng hiển thị và xoá trình nghe này khi thành phần kết hợp rời khỏi cây thành phần.- Chúng ta sử dụng
Canvasđể vẽ một hình chữ nhật màu hồng trong suốt bao phủ toàn bộ màn hình. - Chúng ta trì hoãn việc đọc biến
sensorFaceRectscho đến khi ở trong khối vẽCanvas. Sau đó, chúng ta chuyển đổi toạ độ thành toạ độ giao diện người dùng. - Chúng ta lặp lại các khuôn mặt được phát hiện và đối với mỗi khuôn mặt, chúng ta vẽ một chuyển màu xuyên tâm để làm cho bên trong hình chữ nhật khuôn mặt trong suốt.
- Chúng ta sử dụng
BlendMode.DstOutđể đảm bảo rằng chúng ta đang cắt bỏ hiệu ứng chuyển màu khỏi hình chữ nhật màu hồng, tạo hiệu ứng tiêu điểm.
Lưu ý: Khi chuyển camera sang chế độ DEFAULT_FRONT_CAMERA, bạn sẽ nhận thấy đèn chiếu được phản chiếu! Đây là một vấn đề đã biết và được theo dõi trong Trình theo dõi sự cố của Google.
Kết quả
Với mã này, chúng ta có hiệu ứng làm nổi bật hoàn toàn chức năng, giúp làm nổi bật các khuôn mặt được phát hiện. Bạn có thể xem toàn bộ đoạn mã tại đây.
Hiệu ứng này chỉ là bước khởi đầu. Bằng cách sử dụng sức mạnh của Compose, bạn có thể tạo ra vô số trải nghiệm camera bắt mắt. Khả năng chuyển đổi toạ độ cảm biến và vùng đệm thành toạ độ giao diện người dùng Compose và ngược lại có nghĩa là chúng ta có thể tận dụng tất cả các tính năng của giao diện người dùng Compose và tích hợp chúng một cách liền mạch với hệ thống camera cơ bản. Với ảnh động, đồ hoạ giao diện người dùng nâng cao, khả năng quản lý trạng thái giao diện người dùng đơn giản và khả năng kiểm soát hoàn toàn bằng cử chỉ, giới hạn duy nhất là trí tưởng tượng của bạn!
Trong bài đăng cuối cùng của loạt bài này, chúng ta sẽ tìm hiểu cách sử dụng các API thích ứng và khung ảnh động Compose để chuyển đổi liền mạch giữa các giao diện người dùng camera trên thiết bị có thể gập lại. Hãy chú ý theo dõi!
Các đoạn mã trong blog này có giấy phép sau:
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
Xin chân thành cảm ơn Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner và Lauren Ward đã xem xét và đưa ra ý kiến phản hồi. Nhờ nỗ lực làm việc miệt mài của Yasith Vidanaarachch.
Tiếp tục đọc
-
Hướng dẫn
Trong bài viết này, bạn sẽ tìm hiểu cách sử dụng API kiểm thử waitUntil trong Compose để chờ đến khi đáp ứng một số điều kiện nhất định.
Jose Alcérreca • Đọc trong 3 phút
-
Hướng dẫn
Nhận thấy rằng việc tiêu hao pin quá mức là điều đầu tiên trong tâm trí người dùng Android, Google đã thực hiện các bước quan trọng để giúp nhà phát triển tạo ra các ứng dụng tiết kiệm pin hơn.
Alice Yuan • Đọc trong 8 phút
-
Hướng dẫn
Chúng tôi muốn cung cấp cho bạn ví dụ về các tính năng dựa trên AI bằng cách sử dụng cả mô hình trên thiết bị và mô hình trên đám mây, đồng thời truyền cảm hứng để bạn tạo ra trải nghiệm thú vị cho người dùng.
Thomas Ezan, Ivy Knight • Đọc trong 2 phút
Nhận thông tin cập nhật
Nhận thông tin chi tiết mới nhất về hoạt động phát triển trên Android trong hộp thư đến của bạn mỗi tuần.