สวัสดี ขอต้อนรับกลับมาสู่ซีรีส์เกี่ยวกับ CameraX และ Jetpack Compose ในโพสต์ก่อนหน้านี้ เราได้กล่าวถึงพื้นฐานของการตั้งค่าตัวอย่างกล้องและเพิ่มฟังก์ชันการแตะเพื่อโฟกัส
🧱 ส่วนที่ 1: สร้างตัวอย่างกล้องพื้นฐานโดยใช้ชิ้นงาน 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 { ... }
เมื่อมีการเปลี่ยนแปลงเหล่านี้แล้ว ตอนนี้โมเดลมุมมองจะแสดงรายการออบเจ็กต์ Rect ที่แสดงถึงกรอบล้อมรอบของใบหน้าที่ตรวจพบในพิกัดเซ็นเซอร์
แปลงพิกัดเซ็นเซอร์เป็นพิกัด UI
กรอบล้อมรอบของใบหน้าที่ตรวจพบซึ่งเราจัดเก็บไว้ในส่วนสุดท้ายจะใช้พิกัดในระบบพิกัดของเซ็นเซอร์ หากต้องการวาดกรอบล้อมรอบใน UI เราต้องแปลงพิกัดเหล่านี้เพื่อให้ถูกต้องในระบบพิกัด Compose เราต้องทำสิ่งต่อไปนี้
- แปลงพิกัดเซ็นเซอร์เป็นพิกัดบัฟเฟอร์แสดงตัวอย่าง
- เปลี่ยนรูปแบบพิกัดบัฟเฟอร์ตัวอย่างเป็นพิกัด UI ของ Compose
การแปลงเหล่านี้ทำได้โดยใช้เมทริกซ์การแปลง การเปลี่ยนรูปแบบแต่ละอย่างมีเมทริกซ์ของตัวเอง ดังนี้
-
SurfaceRequestของเราจะเก็บอินสแตนซ์TransformationInfoซึ่งมีเมทริกซ์sensorToBufferTranform -
CameraXViewfinderของเรามีCoordinateTransformerที่เชื่อมโยงอยู่ คุณอาจจำได้ว่าเราเคยใช้ Transformer นี้ในบล็อกโพสต์ก่อนหน้าเพื่อเปลี่ยนพิกัดการแตะเพื่อโฟกัส
เราสามารถสร้างเมธอดตัวช่วยที่จะทำการแปลงให้เราได้
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
ใช้เอฟเฟกต์สปอตไลท์
ตอนนี้เรามาอัปเดต CameraPreviewContent ที่ใช้ร่วมกันได้เพื่อวาดเอฟเฟกต์สปอตไลท์กัน เราจะใช้ Canvas ที่ประกอบได้เพื่อวาดมาสก์การไล่ระดับสีเหนือตัวอย่าง ทำให้ใบหน้าที่ตรวจพบมองเห็นได้
@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 ) } } } } }
โดยมีวิธีการทำงานดังนี้
- เรารวบรวมรายการใบหน้าจาก ViewModel
-
เพื่อไม่ให้เราต้องจัดองค์ประกอบทั้งหน้าจอใหม่ทุกครั้งที่รายการใบหน้าที่ตรวจพบมีการเปลี่ยนแปลง เราจึงใช้
derivedStateOfเพื่อติดตามว่ามีการตรวจพบใบหน้าหรือไม่ จากนั้นจะใช้กับAnimatedVisibilityเพื่อเคลื่อนไหวการซ้อนทับสีเข้าและออกได้ -
surfaceRequestมีข้อมูลที่เราต้องใช้ในการแปลงพิกัดเซ็นเซอร์เป็นพิกัดบัฟเฟอร์ในSurfaceRequest.TransformationInfoเราใช้ฟังก์ชันproduceStateเพื่อตั้งค่า Listener ในคำขอ Surface และล้าง Listener นี้เมื่อ Composable ออกจาก Composition Tree -
เราใช้
Canvasเพื่อวาดสี่เหลี่ยมผืนผ้าสีชมพูโปร่งแสงที่ครอบคลุมทั้งหน้าจอ -
เราจะเลื่อนการอ่านตัวแปร
sensorFaceRectsจนกว่าจะอยู่ในบล็อกการวาดCanvasจากนั้นเราจะแปลงพิกัดเป็นพิกัด UI - เราจะวนซ้ำใบหน้าที่ตรวจพบ และสำหรับแต่ละใบหน้า เราจะวาดการไล่ระดับสีแบบรัศมีที่จะทำให้ด้านในของสี่เหลี่ยมผืนผ้าใบหน้าโปร่งใส
-
เราใช้
BlendMode.DstOutเพื่อให้แน่ใจว่าเราจะตัดการไล่ระดับสีออกจากสี่เหลี่ยมผืนผ้าสีชมพูเพื่อสร้างเอฟเฟกต์สปอตไลท์
หมายเหตุ: เมื่อเปลี่ยนกล้องเป็น DEFAULT_FRONT_CAMERA คุณจะเห็นว่าสปอตไลต์จะกลับด้าน กรณีนี้เป็นปัญหาที่ทราบอยู่แล้ว และมีการติดตามใน Google Issue Tracker
ผลลัพธ์
โค้ดนี้ช่วยให้เรามีเอฟเฟกต์สปอตไลท์ที่ทำงานได้อย่างสมบูรณ์ ซึ่งจะไฮไลต์ใบหน้าที่ตรวจพบ ดูข้อมูลโค้ดทั้งหมดได้ที่นี่
เอฟเฟกต์นี้เป็นเพียงจุดเริ่มต้นเท่านั้น คุณสามารถใช้พลังของ Compose เพื่อสร้างประสบการณ์การใช้งานกล้องที่สวยงามตระการตาได้มากมาย การแปลงพิกัดเซ็นเซอร์และบัฟเฟอร์เป็นพิกัด Compose UI และแปลงกลับได้หมายความว่าเราสามารถใช้ฟีเจอร์ทั้งหมดของ Compose UI และผสานรวมเข้ากับระบบกล้องพื้นฐานได้อย่างราบรื่น ภาพเคลื่อนไหว กราฟิก 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
อ่านต่อ
-
ฮาวทู
ในบทความนี้ คุณจะได้ทราบวิธีใช้ API การทดสอบ waitUntil ใน Compose เพื่อรอให้ตรงตามเงื่อนไขบางอย่าง
Jose Alcérreca • ใช้เวลาอ่าน 3 นาที
-
ฮาวทู
ไม่ว่าคุณจะใช้ Gemini ใน Android Studio, Gemini CLI, Antigravity หรือเอเจนต์ของบุคคลที่สาม เช่น Claude Code หรือ Codex ภารกิจของเราคือการทำให้มั่นใจว่าการพัฒนา Android คุณภาพสูงจะเกิดขึ้นได้ทุกที่
Adarsh Fernando, Esteban de la Canal • ใช้เวลาอ่าน 4 นาที
-
ฮาวทู
การบังคับใช้คุณภาพทางเทคนิคของแบตเตอรี่มาถึงแล้ว: วิธีเพิ่มประสิทธิภาพกรณีการใช้งาน Wake Lock ทั่วไป
Google ทราบดีว่าการใช้แบตเตอรี่หมดเร็วเป็นสิ่งที่ผู้ใช้ Android เป็นที่หนึ่งในใจ จึงได้ดำเนินการอย่างจริงจังเพื่อช่วยให้นักพัฒนาแอปสร้างแอปที่ประหยัดพลังงานมากขึ้น
Alice Yuan • ใช้เวลาอ่าน 8 นาที
รับข่าวสาร
รับข้อมูลเชิงลึกด้านการพัฒนาแอป Android ล่าสุดส่งตรงถึงกล่องจดหมายของคุณทุกสัปดาห์