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 phần mềm 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 nội dung hấp dẫn hơn về mặt hình ảnh: 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 tại 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ạ độ này 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 những 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 thức 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 ở bên 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ỏ phần 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 bị 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 các tính năng này 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, tính 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. Công cụ này có thể ra đời là nhờ nỗ lực làm việc miệt mài của Yasith Vidanaarachch.
-
Hướng dẫnTrong 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ờ đáp ứng một số điều kiện nhất định.
Jose Alcérreca • Đọc trong 3 phút -
Hướng dẫnMặc dù hiệu suất của ứng dụng thường được đánh giá bằng giao diện người dùng mượt mà và thời gian khởi động nhanh, nhưng bộ nhớ đóng vai trò là nền tảng thầm lặng để xây dựng các chỉ số hữu hình này. Không có gì bí mật khi chúng ta thấy sự thay đổi trong đó bộ nhớ thiết bị quan trọng hơn bao giờ hết.
Alice Yuan, Ajesh Pai, Fung Lam • Đọc trong 10 phút -
Hướng dẫnHôm nay, chúng tôi rất vui mừng thông báo về một thông tin xác thực mới về email đã xác minh do Google phát hành. Giờ đây, nhà phát triển có thể truy xuất thông tin này trực tiếp từ API Thông tin xác thực kỹ thuật số của Trình quản lý thông tin xác thực trên Android.
Niharika Arora, Jean-Pierre Pralle • Đọc trong 3 phú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.