مرحبًا مرحبًا بكم من جديد في سلسلتنا حول CameraX وJetpack Compose. في المشاركات السابقة، تناولنا أساسيات إعداد معاينة الكاميرا وأضفنا ميزة النقر للتركيز.
🧱 الجزء 1: إنشاء معاينة أساسية للكاميرا باستخدام العنصر الجديد camera-compose. لقد تناولنا معالجة الأذونات والتكامل الأساسي.
👆 الجزء 2: استخدام نظام الإيماءات والرسومات والروتينات المشتركة في Compose لتنفيذ ميزة النقر للتركيز المرئية
🔦 الجزء 3 (هذه المشاركة): استكشاف كيفية عرض عناصر واجهة مستخدم Compose فوق معاينة الكاميرا للحصول على تجربة مستخدم أكثر ثراءً
📂 الجزء 4: استخدام واجهات برمجة التطبيقات المتكيّفة وإطار عمل الرسوم المتحركة في Compose لإنشاء رسوم متحركة سلسة عند الانتقال إلى وضع "الكمبيوتر المكتبي" ومنه على الهواتف القابلة للطي
في هذه المشاركة، سنتعمّق في موضوع أكثر جاذبية من الناحية المرئية، وهو تطبيق تأثير تسليط الضوء على معاينة الكاميرا، باستخدام ميزة "التعرّف على الوجوه" كأساس للتأثير. لماذا، كما قد تتساءل؟ لستُ متأكّدًا. لكنّها تبدو رائعة 🙂، والأهم من ذلك أنّها توضّح كيف يمكننا بسهولة تحويل إحداثيات المستشعر إلى إحداثيات واجهة المستخدم، ما يتيح لنا استخدامها في Compose.
تفعيل ميزة "التعرّف على الوجوه"
أولاً، لنعدّل CameraPreviewViewModel لتفعيل ميزة "التعرّف على الوجوه". سنستخدم واجهة برمجة التطبيقات Camera2Interop التي تتيح لنا التفاعل مع واجهة برمجة التطبيقات Camera2 الأساسية من 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 تمثّل المربّعات المحيطة بالوجوه التي تم رصدها في إحداثيات المستشعر.
تحويل إحداثيات أداة الاستشعار إلى إحداثيات واجهة المستخدم
تستخدم المربّعات المحيطة بالوجوه التي تم رصدها والتي خزّناها في القسم الأخير إحداثيات في نظام إحداثيات المستشعر. لرسم مربّعات الإحاطة في واجهة المستخدم، علينا تحويل هذه الإحداثيات لتكون صحيحة في نظام إحداثيات 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
نشكر نيك بوتشر وأليكس فانيو وتريفور ماكغواير ودون تيرنر و"لورين وارد" على مراجعة هذه المقالة وتقديم الملاحظات. تم إعداد هذه الميزة بفضل العمل الجاد الذي قام به ياسيث فيدانااراتش.
متابعة القراءة
-
طرق التنفيذ
ستتعرّف في هذه المقالة على كيفية استخدام واجهة برمجة التطبيقات waitUntil لاختبار Compose من أجل انتظار استيفاء شروط معيّنة.
Jose Alcérreca • قراءة لمدة 3 دقائق
-
طرق التنفيذ
سواء كنت تستخدم "Gemini في استوديو Android" أو Gemini CLI أو Antigravity أو وكلاء تابعين لجهات خارجية، مثل Claude Code أو Codex، تتمثّل مهمتنا في ضمان إمكانية تطوير تطبيقات Android عالية الجودة في كل مكان.
Adarsh Fernando, Esteban de la Canal • مدة القراءة: 4 دقائق
-
طرق التنفيذ
إدراكًا منّا أنّ استنزاف البطارية بشكل مفرط هو أوّل ما يخطر في بال مستخدمي Android، اتّخذت Google خطوات مهمة لمساعدة المطوّرين في إنشاء تطبيقات أكثر كفاءة في استهلاك الطاقة.
Alice Yuan • مدة القراءة: 8 دقائق
البقاء على اطّلاع على آخر التحديثات
يمكنك تلقّي أحدث الإحصاءات حول تطوير تطبيقات Android في بريدك الوارد أسبوعيًا.