مرحبًا مرحبًا بكم من جديد في سلسلتنا حول CameraX وJetpack Compose. في المنشورَين السابقَين، تناولنا أساسيات إعداد معاينة الكاميرا وأضفنا وظيفة النقر للتركيز.
🧱 الجزء 1: إنشاء معاينة أساسية للكاميرا باستخدام العنصر الجديد camera-compose تناولنا معالجة الأذونات والتكامل الأساسي.
👆 الجزء 2: استخدام نظام الإيماءات والرسومات والروتينات الفرعية في Compose لتنفيذ النقر المرئي للتركيز
🔦 الجزء 3 (هذا المنشور): استكشاف كيفية تراكب عناصر واجهة مستخدم Compose على معاينة الكاميرا للحصول على تجربة مستخدم أفضل
📂 الجزء 4: استخدام واجهات برمجة التطبيقات التكيُّفية وإطار عمل الرسوم المتحركة في Compose للانتقال بسلاسة من وضع التثبيت على سطح مستوٍ وإليه على الهواتف القابلة للطي
في هذا المنشور، سنتناول موضوعًا أكثر جاذبية من الناحية المرئية، وهو تنفيذ تأثير تسليط الضوء على معاينة الكاميرا، باستخدام ميزة "التعرّف على الوجوه" كأساس للتأثير. قد تتساءلون عن السبب. لست متأكدًا. لكنّ هذا التأثير يبدو رائعًا 🙂. والأهم من ذلك أنّه يوضّح كيف يمكننا بسهولة ترجمة إحداثيات المستشعر إلى إحداثيات واجهة المستخدم، ما يسمح لنا باستخدامها في Compose.
تفعيل ميزة "التعرّف على الوجوه"
أولاً، لنعدّل CameraPreviewViewModel لتفعيل ميزة "التعرّف على الوجوه". سنستخدم واجهة برمجة التطبيقات Camera2Interop التي تسمح لنا بالتفاعل مع واجهة برمجة التطبيقات Camera2 الأساسية من CameraX. يتيح لنا ذلك استخدام ميزات الكاميرا التي لا تعرضها CameraX مباشرةً. علينا إجراء التغييرات التالية:
- إنشاء StateFlow يحتوي على حدود الوجه كقائمة من
Rects. - ضبط خيار طلب الالتقاط
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 التي تمثّل المربّعات المحيطة بالوجوه التي تم رصدها بإحداثيات المستشعر.
ترجمة إحداثيات المستشعر إلى إحداثيات واجهة المستخدم
تستخدم المربّعات المحيطة بالوجوه التي تم رصدها والتي خزّناها في القسم الأخير إحداثيات في نظام إحداثيات المستشعر. لرسم المربّعات المحيطة في واجهة المستخدم، علينا تحويل هذه الإحداثيات لتكون صحيحة في نظام إحداثيات Compose. علينا إجراء ما يلي:
- تحويل إحداثيات المستشعر إلى إحداثيات مخزن المعاينة المؤقت
- تحويل إحداثيات مخزن المعاينة المؤقت إلى إحداثيات واجهة مستخدم Compose
يتم إجراء عمليات التحويل هذه باستخدام مصفوفات التحويل. لكل عملية تحويل مصفوفة خاصة بها:
- يحتوي
SurfaceRequestعلى مثيلTransformationInfoالذي يحتوي على مصفوفةsensorToBufferTranform. - يحتوي
CameraXViewfinderعلىCoordinateTransformerمرتبط. قد تتذكرون أنّنا استخدمنا هذا المحوّل من قبل في منشور المدونة السابق لتحويل إحداثيات النقر للتركيز.
يمكننا إنشاء طريقة مساعِدة يمكنها إجراء عملية التحويل نيابةً عنّا:
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الإحداثيات من واجهة المستخدم إلى إحداثيات المخزن المؤقت تلقائيًا. في حالتنا، نريد أن تعمل المصفوفة بالطريقة المعاكسة، أي تحويل إحداثيات المخزن المؤقت إلى إحداثيات واجهة المستخدم. لذلك، نستخدم الطريقةinvert()لعكس المصفوفة. - نحوّل أولاً الوجه من إحداثيات المستشعر إلى إحداثيات المخزن المؤقت باستخدام
sensorToBufferTransformMatrix، ثم نحوّل إحداثيات المخزن المؤقت هذه إلى إحداثيات واجهة المستخدم باستخدام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 ) } } } } }
إليكم الطريقة:
- نجمع قائمة الوجوه من نموذج العرض.
- للتأكّد من عدم إعادة إنشاء الشاشة بأكملها في كل مرة تتغيّر فيها قائمة الوجوه التي تم رصدها، نستخدم
derivedStateOfلتتبُّع ما إذا تم رصد أي وجوه على الإطلاق. يمكن بعد ذلك استخدام هذا معAnimatedVisibilityلتحريك التراكب الملوّن للداخل والخارج. - يحتوي
surfaceRequestعلى المعلومات التي نحتاج إليها لتحويل إحداثيات المستشعر إلى إحداثيات المخزن المؤقت فيSurfaceRequest.TransformationInfo. نستخدم الدالةproduceStateلإعداد متتبِّع في طلب مساحة العرض، ونمحو هذا المتتبِّع عندما يغادر الدالة المركّبة شجرة التكوين. - نستخدم
Canvasلرسم مستطيل وردي شفّاف يغطي الشاشة بأكملها. - نؤجّل قراءة المتغيّر
sensorFaceRectsإلى أن نكون داخل كتلة الرسمCanvas. ثم نحوّل الإحداثيات إلى إحداثيات واجهة المستخدم. - نكرّر الوجوه التي تم رصدها، ولكل وجه، نرسم تدرّجًا شعاعيًا سيجعل الجزء الداخلي من مستطيل الوجه شفّافًا.
- نستخدم
BlendMode.DstOutللتأكّد من أنّنا نقطع التدرّج من المستطيل الوردي، ما يؤدي إلى إنشاء تأثير تسليط الضوء.
ملاحظة: عند تغيير الكاميرا إلى DEFAULT_FRONT_CAMERA ستلاحظون أنّ تأثير تسليط الضوء معكوس. هذه مشكلة معروفة، ويتم تتبُّعها في أداة تتبُّع المشاكل من Google.
النتيجة
باستخدام هذا الرمز، نحصل على تأثير تسليط ضوء يعمل بكامل وظائفه ويبرز الوجوه التي تم رصدها. يمكنكم العثور على مقتطف الرمز الكامل هنا.
هذا التأثير هو مجرد بداية، فباستخدام إمكانات Compose، يمكنكم إنشاء عدد لا يحصى من تجارب الكاميرا المذهلة من الناحية المرئية. إنّ القدرة على تحويل إحداثيات المستشعر والمخزن المؤقت إلى إحداثيات واجهة مستخدم Compose والعودة إليها تعني أنّه يمكننا استخدام جميع ميزات واجهة مستخدم Compose ودمجها بسلاسة مع نظام الكاميرا الأساسي. باستخدام الرسوم المتحركة والرسومات المتقدّمة لواجهة المستخدم وإدارة حالة واجهة المستخدم البسيطة والتحكّم الكامل بالإيماءات، لا حدود لإبداعكم.
في المنشور الأخير من السلسلة، سنتناول كيفية استخدام واجهات برمجة التطبيقات التكيُّفية وإطار الرسوم المتحركة في Compose للانتقال بسلاسة بين واجهات مستخدم الكاميرا المختلفة على الأجهزة القابلة للطي. يُرجى متابعة أخبارنا باستمرار.
تخضع مقتطفات الرموز البرمجية في هذه المدونة للترخيص التالي:
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
نشكر Nick Butcher وAlex Vanyo وTrevor McGuire وDon Turner وLauren Ward على مراجعة المنشور وتقديم الملاحظات. وقد تم إنجاز هذا المنشور بفضل العمل الجاد الذي قام به Yasith Vidanaarachch.
-
مقالات إرشاديةستتعرّفون في هذه المقالة على كيفية استخدام واجهة برمجة التطبيقات waitUntil للاختبار في Compose للانتظار إلى أن يتم استيفاء شروط معيّنة.
Jose Alcérreca • مدة القراءة: 3 دقائق -
مقالات إرشاديةJetpacker هو تطبيق عرض فني أنشأه فريقنا من البداية من أجل مؤتمر Google I/O لهذا العام (تم إنشاؤه باستخدام Antigravity). يساعد Jetpacker المستخدمين في الأساس على التخطيط لمغامرتهم الكبيرة التالية واستكشافها والاستمتاع بها.
Jolanda Verhoef • مدّة القراءة: 4 دقائق -
مقالات إرشاديةمرحبًا بكم من جديد في سلسلة منشورات المدونة "إنشاء تطبيقات Android ذكية" حيث نأخذ تطبيق Android أساسيًا ونحوّله إلى تجربة مخصّصة وذكية واستباقية.
Thomas Ezan, Jolanda Verhoef, Caren Chang • مدّة القراءة: 8 دقائق
يمكنكم تلقّي أحدث الإحصاءات حول تطوير تطبيقات Android في بريدكم الوارد أسبوعيًا.