สวัสดี ยินดีต้อนรับกลับสู่ซีรีส์เกี่ยวกับ CameraX และ Jetpack Compose ในโพสต์ก่อนหน้านี้ เราได้พูดถึงพื้นฐานของการตั้งค่าการแสดงตัวอย่างกล้องและเพิ่มฟังก์ชันการทำงานแบบแตะเพื่อโฟกัส
🧱 ส่วนที่ 1: สร้างการแสดงตัวอย่างกล้องพื้นฐานโดยใช้ Artifact ใหม่ของ camera-compose เราได้พูดถึงการจัดการสิทธิ์และการผสานรวมพื้นฐาน
👆 ส่วนที่ 2: ใช้ระบบท่าทางสัมผัส กราฟิก และโครูทีนของ Compose เพื่อใช้ฟังก์ชันการทำงานแบบแตะเพื่อโฟกัสแบบภาพ
🔦 ส่วนที่ 3 (โพสต์นี้): สำรวจวิธีซ้อนทับองค์ประกอบ UI ของ Compose ไว้เหนือการแสดงตัวอย่างกล้องเพื่อประสบการณ์การใช้งานที่ดียิ่งขึ้น
📂 ส่วนที่ 4: ใช้ API แบบปรับอัตโนมัติและเฟรมเวิร์กภาพเคลื่อนไหวของ Compose เพื่อสร้างภาพเคลื่อนไหวที่ราบรื่นในการเปลี่ยนไปมาระหว่างโหมดตั้งโต๊ะในโทรศัพท์แบบพับได้
ในโพสต์นี้ เราจะมาเจาะลึกเรื่องที่น่าสนใจมากขึ้นในเชิงภาพ นั่นคือการใช้เอฟเฟกต์สปอตไลท์ไว้เหนือการแสดงตัวอย่างกล้อง โดยใช้การตรวจจับใบหน้าเป็นพื้นฐานของเอฟเฟกต์ คุณอาจสงสัยว่าทำไม ฉันไม่แน่ใจ แต่ดูเท่ดี 🙂 และที่สำคัญกว่านั้นคือแสดงให้เห็นว่าเราสามารถแปลงพิกัดเซ็นเซอร์เป็นพิกัด UI ได้อย่างง่ายดาย ซึ่งช่วยให้เราใช้พิกัดเหล่านั้นใน Compose ได้
เปิดใช้การตรวจจับใบหน้า
ก่อนอื่น เรามาแก้ไข CameraPreviewViewModel เพื่อเปิดใช้การตรวจจับใบหน้ากัน เราจะใช้ Camera2Interop API ซึ่งช่วยให้เราโต้ตอบกับ Camera2 API ที่อยู่เบื้องหลังได้จาก CameraX ซึ่งจะช่วยให้เราใช้ฟีเจอร์กล้องที่ CameraX ไม่ได้แสดงโดยตรงได้ เราต้องทำการเปลี่ยนแปลงต่อไปนี้
- สร้าง StateFlow ที่มีขอบเขตใบหน้าเป็นรายการ
Rect - ตั้งค่าตัวเลือกคำขอการจับภาพ
STATISTICS_FACE_DETECT_MODEเป็น FULL ซึ่งจะเปิดใช้การตรวจจับใบหน้า - ตั้งค่า
CaptureCallbackเพื่อรับข้อมูลใบหน้าจากผลลัพธ์การจับภาพ
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 { ... }
เมื่อทำการเปลี่ยนแปลงเหล่านี้แล้ว ตอนนี้ View Model จะแสดงรายการออบเจ็กต์ Rect ที่แสดงถึงกรอบล้อมรอบของใบหน้าที่ตรวจพบในพิกัดเซ็นเซอร์
แปลงพิกัดเซ็นเซอร์เป็นพิกัด UI
กรอบล้อมรอบของใบหน้าที่ตรวจพบซึ่งเราจัดเก็บไว้ในส่วนที่แล้วใช้พิกัดในระบบพิกัดเซ็นเซอร์ หากต้องการวาดกรอบล้อมรอบใน UI เราต้องแปลงพิกัดเหล่านี้เพื่อให้ถูกต้องในระบบพิกัดของ Compose โดยต้องทำดังนี้
- แปลงพิกัดเซ็นเซอร์ เป็นพิกัดบัฟเฟอร์การแสดงตัวอย่าง
- แปลงพิกัดบัฟเฟอร์การแสดงตัวอย่าง เป็นพิกัด UI ของ Compose
การแปลงเหล่านี้ทำได้โดยใช้เมทริกซ์การแปลง การแปลงแต่ละรายการจะมีเมทริกซ์ของตัวเอง ดังนี้
SurfaceRequestของเราเก็บอินสแตนซ์TransformationInfoซึ่งมีเมทริกซ์sensorToBufferTranformCameraXViewfinderของเรามีCoordinateTransformerที่เชื่อมโยงอยู่ คุณอาจจำได้ว่าเราได้ใช้ Transformer นี้ในโพสต์บล็อกก่อนหน้านี้เพื่อแปลงพิกัดแบบแตะเพื่อโฟกัส
เราสามารถสร้างเมธอด Helper ที่ทำการแปลงให้เราได้ ดังนี้
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 }
- เราจะวนซ้ำรายการใบหน้าที่ตรวจพบ และทำการแปลงสำหรับแต่ละใบหน้า
CoordinateTransformer.transformMatrixที่เราได้รับจากCameraXViewfinderจะแปลงพิกัดจาก UI เป็นพิกัดบัฟเฟอร์โดยค่าเริ่มต้น ในกรณีของเรา เราต้องการให้เมทริกซ์ทำงานในทางกลับกัน นั่นคือแปลงพิกัดบัฟเฟอร์เป็นพิกัด UI ดังนั้น เราจึงใช้เมธอดinvert()เพื่อกลับเมทริกซ์- ก่อนอื่น เราจะแปลงใบหน้าจากพิกัดเซ็นเซอร์เป็นพิกัดบัฟเฟอร์โดยใช้
sensorToBufferTransformMatrixแล้วแปลงพิกัดบัฟเฟอร์เหล่านั้นเป็นพิกัด UI โดยใช้bufferToUiTransformMatrix
ใช้เอฟเฟกต์สปอตไลท์
ตอนนี้ เรามาอัปเดต Composible CameraPreviewContent เพื่อวาดเอฟเฟกต์สปอตไลท์กัน เราจะใช้ Canvas Composible เพื่อวาดมาสก์การไล่ระดับสีไว้เหนือการแสดงตัวอย่าง ซึ่งจะทำให้ใบหน้าที่ตรวจพบมองเห็นได้
@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 ) } } } } }
วิธีการทำงานมีดังนี้
- เรารวบรวมรายการใบหน้าจาก View Model
- เพื่อให้แน่ใจว่าเราจะไม่ทำการคอมโพสใหม่ทั้งหน้าจอทุกครั้งที่รายการใบหน้าที่ตรวจพบมีการเปลี่ยนแปลง เราจะใช้
derivedStateOfเพื่อติดตามว่ามีการตรวจพบใบหน้าหรือไม่ จากนั้นเราจะใช้ฟังก์ชันนี้กับAnimatedVisibilityเพื่อสร้างภาพเคลื่อนไหวให้การซ้อนทับสีเข้าและออก surfaceRequestมีข้อมูลที่เราต้องใช้ในการแปลงพิกัดเซ็นเซอร์เป็นพิกัดบัฟเฟอร์ในSurfaceRequest.TransformationInfoเราใช้ฟังก์ชันproduceStateเพื่อตั้งค่า Listener ในคำขอ Surface และล้าง Listener นี้เมื่อ Composible ออกจากแผนผังการคอมโพส- เราใช้
Canvasเพื่อวาดสี่เหลี่ยมผืนผ้าสีชมพูโปร่งแสงที่ครอบคลุมทั้งหน้าจอ - เราจะเลื่อนการอ่านตัวแปร
sensorFaceRectsออกไปจนกว่าจะอยู่ในบล็อกการวาดCanvasจากนั้นเราจะแปลงพิกัดเป็นพิกัด UI - เราจะวนซ้ำใบหน้าที่ตรวจพบ และสำหรับแต่ละใบหน้า เราจะวาดการไล่ระดับสีแบบรัศมีที่จะทำให้ด้านในของสี่เหลี่ยมผืนผ้าใบหน้าโปร่งใส
- เราใช้
BlendMode.DstOutเพื่อให้แน่ใจว่าเราจะตัดการไล่ระดับสีออกจากสี่เหลี่ยมผืนผ้าสีชมพู ซึ่งจะสร้างเอฟเฟกต์สปอตไลท์
หมายเหตุ: เมื่อเปลี่ยนกล้องเป็น DEFAULT_FRONT_CAMERA คุณจะสังเกตเห็นว่าสปอตไลท์กลับด้าน ซึ่งเป็นปัญหาที่ทราบอยู่แล้วและมีการติดตามใน Google Issue Tracker.
ผลลัพธ์
โค้ดนี้จะสร้างเอฟเฟกต์สปอตไลท์ที่ทำงานได้อย่างสมบูรณ์ ซึ่งจะไฮไลต์ใบหน้าที่ตรวจพบ ดูข้อมูลโค้ดทั้งหมดได้ที่นี่ ที่นี่
เอฟเฟกต์นี้เป็นเพียงจุดเริ่มต้นเท่านั้น คุณสามารถสร้างประสบการณ์การใช้งานกล้องที่น่าทึ่งมากมายได้โดยใช้ขุมพลังของ Compose ความสามารถในการแปลงพิกัดเซ็นเซอร์และบัฟเฟอร์เป็นพิกัด UI ของ Compose และแปลงกลับหมายความว่าเราสามารถใช้ฟีเจอร์ UI ทั้งหมดของ Compose และผสานรวมฟีเจอร์เหล่านั้นเข้ากับระบบกล้องที่อยู่เบื้องหลังได้อย่างราบรื่น ด้วยภาพเคลื่อนไหว กราฟิก UI ขั้นสูง การจัดการสถานะ UI อย่างง่าย และการควบคุมท่าทางสัมผัสอย่างเต็มรูปแบบ คุณจะสร้างสรรค์ได้ไม่สิ้นสุด
ในโพสต์สุดท้ายของซีรีส์นี้ เราจะมาเจาะลึกวิธีใช้ API แบบปรับอัตโนมัติและเฟรมเวิร์กภาพเคลื่อนไหวของ Compose เพื่อเปลี่ยน UI กล้องต่างๆ ในอุปกรณ์แบบพับได้อย่างราบรื่น โปรดอดใจรอ
ข้อมูลโค้ดในบล็อกนี้มีใบอนุญาตดังนี้
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
ขอขอบคุณ Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner และ Lauren Ward ที่ช่วยตรวจสอบและให้ความคิดเห็น และขอขอบคุณ Yasith Vidanaarachch ที่ทุ่มเททำงานจนเกิดเป็นบล็อกนี้
-
วิธีการในบทความนี้ คุณจะได้ทราบวิธีใช้ waitUntil API การทดสอบใน Compose เพื่อรอให้ตรงตามเงื่อนไขบางอย่าง
Jose Alcérreca • ใช้เวลาอ่าน 3 นาที -
วิธีการJetpacker เป็นแอปสาธิตทางเทคนิคที่ทีมของเราสร้างขึ้นตั้งแต่ต้นสำหรับงาน Google I/O ปีนี้ (สร้างโดยใช้ Antigravity) โดยหลักๆ แล้ว Jetpacker จะช่วยให้ผู้ใช้วางแผน สำรวจ และสนุกกับการผจญภัยครั้งต่อไป
Jolanda Verhoef • ใช้เวลาอ่าน 4 นาที -
วิธีการยินดีต้อนรับกลับสู่ซีรีส์โพสต์บล็อก "สร้างแอป Android อัจฉริยะ" ซึ่งเราจะนำแอป Android พื้นฐานมาเปลี่ยนให้เป็นประสบการณ์การใช้งานที่ปรับเปลี่ยนตามผู้ใช้ เป็นอัจฉริยะ และเป็นตัวแทน
Thomas Ezan, Jolanda Verhoef, Caren Chang • ใช้เวลาอ่าน 8 นาที
รับข้อมูลเชิงลึกล่าสุดเกี่ยวกับการพัฒนา Android ส่งตรงถึงกล่องจดหมายของคุณ ทุกสัปดาห์