يوضّح هذا الموضوع كيفية إعداد حالات استخدام CameraX داخل تطبيقك للحصول على
الصور بمعلومات التدوير الصحيحة، سواء من
ImageAnalysis
أو حالة الاستخدام ImageCapture
. لذلك:
- يجب أن تتلقّى حالة استخدام "
Analyzer
" في حالة استخدامImageAnalysis
إطارات تتضمّن الدوران الصحيح. - يجب أن يتم التقاط صور في حالة استخدام
ImageCapture
بطريقة التدوير الصحيحة.
المصطلحات
يستخدم هذا الموضوع المصطلحات التالية، لذلك عليك فهم معنى كل مصطلح هام:
- اتجاه العرض
- يشير هذا إلى جانب الجهاز في الموضع الأعلى، ويمكن إحدى القيم الأربع: عمودي أو أفقي أو عمودي معكوس أو عكسي أفقي.
- تدوير شاشة العرض
- هذه هي القيمة التي تعرضها
Display.getRotation()
، الدرجات التي يتم بها تدوير الجهاز عكس اتجاه عقارب الساعة من باتجاهه الطبيعي. - تغيير الأهداف بالتناوب
- يمثل هذا عدد الدرجات المطلوب التدوير من خلالها. الجهاز في اتجاه عقارب الساعة للوصول إلى اتجاهه الطبيعي.
كيفية تحديد عدد مرات تدوير الهدف
توضِّح الأمثلة التالية كيفية تحديد تدوير الهدف على جهاز. بناءً على اتجاهها الطبيعي.
المثال 1: الاتجاه الطبيعي العمودي
مثال على الجهاز: Pixel 3 XL | |
---|---|
الاتجاه الطبيعي = عمودي تدوير شاشة العرض = 0 |
|
الاتجاه الطبيعي = عمودي تدوير شاشة العرض = 90 |
المثال 2: الاتجاه الطبيعي الأفقي
مثال على الجهاز: Pixel C | |
---|---|
الاتجاه الطبيعي = أفقي تدوير شاشة العرض = 0 |
|
الاتجاه الطبيعي = أفقي تدوير شاشة العرض = 270 |
تدوير الصورة
أي النهاية انتهى؟ يُعرَّف Android اتجاه المستشعر على أنّه ثابت. التي تمثل الدرجات (0، 90، 180، 270) التي يتم تدوير جهاز الاستشعار منها الجزء العلوي من الجهاز عندما يكون في موضع طبيعي. بالنسبة إلى جميع الحالات في المخططات، يصف تدوير الصورة كيف يجب تم تدويره في اتجاه عقارب الساعة لتظهر في وضع مستقيم.
توضح الأمثلة التالية ما ينبغي أن يعتمد عليه تدوير الصورة بناءً على اتجاه أداة استشعار الكاميرا كما يفترض أيضًا أن الدوران المستهدف تم تعيينه على تدوير الشاشة.
المثال 1: تدوير أداة الاستشعار 90 درجة
مثال على الجهاز: Pixel 3 XL | |
---|---|
تدوير شاشة العرض = 0 |
|
تدوير شاشة العرض = 90 |
المثال 2: تدوير أداة الاستشعار 270 درجة
مثال على الجهاز: Nexus 5X | |
---|---|
تدوير شاشة العرض = 0 |
|
تدوير شاشة العرض = 90 |
المثال 3: تدوير أداة الاستشعار 0 درجة
مثال على الجهاز: Pixel C (جهاز لوحي) | |
---|---|
تدوير شاشة العرض = 0 |
|
تدوير شاشة العرض = 270 |
حساب دوران الصورة
تحليل الصور
يتلقّى جهاز "Analyzer
" الخاص بـ "ImageAnalysis
" الصور من الكاميرا على شكل
ImageProxy
تحتوي كل صورة على معلومات حول الدوران، والتي يمكن الوصول إليها
عبر:
val rotation = imageProxy.imageInfo.rotationDegrees
تمثل هذه القيمة الدرجات التي يجب تدوير الصورة بها.
في اتجاه عقارب الساعة لمطابقة دوران "ImageAnalysis
" المستهدَف. في سياق
تطبيق Android، سيتطابق تدوير هدف ImageAnalysis
عادةً مع
اتجاه الشاشة.
التقاط الصور
يتم إرفاق معاودة الاتصال بمثيل ImageCapture
للإشارة إلى وقت الالتقاط
النتيجة جاهزة. يمكن أن تكون النتيجة صورة تم التقاطها أو خطأ.
عند التقاط صورة، يمكن أن يكون رد الاتصال المقدم أيًا مما يلي: الأنواع:
OnImageCapturedCallback
: تتلقى صورة مع إمكانية الوصول إلى الذاكرة في منImageProxy
.OnImageSavedCallback
: تم استدعاؤه عند التقاط الصورة التي تم التقاطها تم تخزينها بنجاح في الموقع المحدد بواسطةImageCapture.OutputFileOptions
يمكن أن تحدد الخيارات السمةFile
أو السمةOutputStream
، أو موقع جغرافي فيMediaStore
تدوير الصورة التي تم التقاطها، بغض النظر عن تنسيقها (ImageProxy
،
File
، OutputStream
، MediaStore Uri
) تمثل درجات التدوير حسب
والذي يجب تدوير الصورة الملتقطة في اتجاه عقارب الساعة لتتطابق مع صورة ImageCapture
نظام تدوير الهدف، والذي بدوره في سياق تطبيق Android،
يتطابق مع اتجاه الشاشة.
يمكن استرداد تدوير الصورة التي تم التقاطها في أي من الإجراءات التالية الطرق:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
التأكّد من دوران الصورة
تتلقّى حالة الاستخدام "ImageAnalysis
" و"ImageCapture
" ImageProxy
من
الكاميرا بعد طلب الالتقاط ناجحًا. يشير ImageProxy
إلى التفاف صورة
ومعلومات حولها، بما في ذلك دورانها. معلومات هذا التناوب
الدرجات التي يجب تدوير الصورة بها لتتوافق مع الاستخدام
التدوير المستهدف للحالة.
إرشادات تدوير الهدف في التقاط الصور/تحليل الصور
بما أنّ العديد من الأجهزة لا تستند إلى الوضع الأفقي العمودي أو العكسي بمقدار افتراضيًا، بعض تطبيقات Android لا تدعم هذه الاتجاهات. ما إذا كان التطبيق تدعمه أو لا يغير طريقة تدوير الهدف في حالات الاستخدام تحديث.
في ما يلي جدولان يحدِّدان كيفية الحفاظ على مزامنة عرض الإعلانات بالتناوب المستهدف في حالات الاستخدام. من خلال دوران الشاشة. يوضح الأول كيفية القيام بذلك مع دعم جميع بأربعة اتجاهات؛ والثاني يعالج فقط اتجاهات تدوير الجهاز إليه افتراضيًا.
لاختيار الإرشادات التي يجب اتّباعها في تطبيقك:
يمكنك التحقّق مما إذا كان اتجاه الكاميرا (
Activity
) في تطبيقك مُقفلاً، الاتجاه الذي تم إلغاء قفله، أو إذا ألغى تغييرات تهيئة الاتجاه.تحديد ما إذا كان يجب التعامل مع الأجهزة الأربعة لكاميرا التطبيق
Activity
الاتجاهات (عمودي، وعمودي عكسي، وأفقي، وأفقي معكوس) أو إذا كان ينبغي أن يتعامل فقط مع الاتجاهات التي يعمل عليها الجهاز تلقائيًا.
دعم جميع الاتجاهات الأربعة
يشير هذا الجدول إلى إرشادات معيّنة يجب اتّباعها في الحالات التي يكون فيها الجهاز لا يتم تدويرها لعكس الوضع العمودي. ويمكن تطبيق الشيء ذاته على الأجهزة التي لا تقم بالتدوير لعكس الوضع الأفقي.
السيناريو | الإرشادات | وضع النافذة الواحدة | وضع تقسيم الشاشة في نوافذ متعددة |
---|---|---|---|
فتح الاتجاه |
إعداد حالات الاستخدام كل
وقت إنشاء Activity ، كما هو الحال في
معاودة الاتصال على onCreate() لـ "Activity "
|
||
استخدام حساب "OrientationEventListener "
onOrientationChanged()
داخل طلب معاودة الاتصال، عدِّل التغيير المستهدف لحالات الاستخدام. يتعامل هذا مع الحالات التي لا يعالج فيها النظام
إعادة إنشاء Activity حتى بعد تغيير الاتجاه، مثل
عند تدوير الجهاز 180 درجة.
|
معالجات أيضًا عندما تكون الشاشة معكوسة الاتجاه العمودي وعدم تدوير الجهاز للوضع العمودي العكسي بمقدار الافتراضي. |
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
|
اختياري: ضبط screenOrientation في Activity
إلى fullSensor في AndroidManifest
الملف.
|
يتيح ذلك أن تكون واجهة المستخدم في وضع عمودي عندما يكون الجهاز معاكسًا.
عمودي، ويسمح بإعادة إنشاء Activity من خلال
عندما يتم تدوير الجهاز بمقدار 90 درجة.
|
ليس له أي تأثير على الأجهزة التي لا تستند إلى الوضع العمودي العكسي بمقدار الافتراضي. لا يمكن استخدام وضع النوافذ المتعددة عندما تكون الشاشة في وضع الاتجاه الرأسي العكسي. | |
قفل الاتجاهات |
عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما
تم إنشاء Activity لأول مرة، كما هو الحال في Activity .
معاودة الاتصال onCreate() .
|
||
استخدام حساب "OrientationEventListener "
onOrientationChanged()
ضِمن قسم معاودة الاتصال، عدِّل معدل التناوب المستهدف لحالات الاستخدام باستثناء "المعاينة".
|
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
||
تم إلغاء تغييرات ضبط الاتجاه |
عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما
تم إنشاء Activity لأول مرة، كما هو الحال في Activity .
معاودة الاتصال onCreate() .
|
||
استخدام حساب "OrientationEventListener "
onOrientationChanged()
داخل طلب معاودة الاتصال، عدِّل التغيير المستهدف لحالات الاستخدام.
|
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
||
اختياري: ضبط خاصية screenOrientation في النشاط على FullSensor في ملف AndroidManifest. | تسمح لواجهة المستخدم بأن تكون في وضع عمودي عندما يكون الجهاز في الوضع العمودي العكسي. | ليس له أي تأثير على الأجهزة التي لا تستند إلى الوضع العمودي العكسي بمقدار الافتراضي. لا يمكن استخدام وضع النوافذ المتعددة عندما تكون الشاشة في وضع الاتجاه الرأسي العكسي. |
إتاحة الاتجاهات المتوافقة مع الجهاز فقط
دعم الاتجاهات التي يدعمها الجهاز فقط بشكل افتراضي (والتي قد أو لا يمكن أن يشتمل على أوضاع رأسية عكسية/أفقية).
السيناريو | الإرشادات | وضع تقسيم الشاشة في نوافذ متعددة |
---|---|---|
فتح الاتجاه |
إعداد حالات الاستخدام كل
وقت إنشاء Activity ، كما هو الحال في
معاودة الاتصال على onCreate() لـ "Activity "
|
|
استخدام حساب "DisplayListener "
onDisplayChanged() داخل
يمكن تحديث التناوب المستهدف لحالات الاستخدام، مثل حالة
يتم تدوير الجهاز 180 درجة.
|
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
|
قفل الاتجاهات |
عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما
تم إنشاء Activity لأول مرة، كما هو الحال في Activity .
معاودة الاتصال onCreate() .
|
|
استخدام حساب "OrientationEventListener "
onOrientationChanged()
داخل طلب معاودة الاتصال، عدِّل التغيير المستهدف لحالات الاستخدام.
|
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
|
تم إلغاء تغييرات ضبط الاتجاه |
عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما
تم إنشاء Activity لأول مرة، كما هو الحال في Activity .
معاودة الاتصال onCreate() .
|
|
استخدام حساب "DisplayListener "
onDisplayChanged() داخل
يمكن تحديث التناوب المستهدف لحالات الاستخدام، مثل حالة
يتم تدوير الجهاز 180 درجة.
|
تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity
تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في
على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر
الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
|
تم فتح قفل الاتجاه
اتجاه شاشة "Activity
" غير مُقفَل عند فتح القفل
(مثل الاتجاه الرأسي أو الأفقي) مع الاتجاه المادي للجهاز، مع
باستثناء الاتجاه العمودي/الأفقي العكسي، والتي لا تتوافق مع بعض الأجهزة
تلقائيًا. لفرض تدوير الجهاز إلى جميع الاتجاهات الأربعة، اضبط
سمة screenOrientation
الخاصة بـ "Activity
" على "fullSensor
"
في وضع النوافذ المتعددة، يكون الجهاز الذي لا يتوافق مع الوضع العمودي/الأفقي العكسي
لن يتم تدوير الصورة تلقائيًا إلى عكس الاتجاه الرأسي/الأفقي، حتى إذا كانت
تم ضبط السمة screenOrientation
على fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
قفل الاتجاه
يكون لشاشة العرض اتجاه مؤمّن عندما تظل في نفس اتجاه العرض
(مثل الاتجاه الرأسي أو الأفقي) بصرف النظر عن الاتجاه الفعلي
الخاص بك. ويمكن إجراء ذلك من خلال تحديد screenOrientation
لـ Activity
.
داخل تعريفها في ملف AndroidManifest.xml
.
عندما يكون اتجاه الشاشة مُقفَلاً، لا يتسبّب النظام في تدمير
إعادة إنشاء Activity
أثناء تدوير الجهاز.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
تم تجاهل تغييرات الإعدادات المتعلّقة بالاتجاه.
عندما تلغي Activity
تغيير ضبط الاتجاه، سيجري النظام
لا يدمرها ويعيد إنشائها عند تغيُّر الاتجاه المادي للجهاز.
يحدِّث النظام واجهة المستخدم لتتوافق مع الاتجاه الفعلي للجهاز.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
إعداد حالات الاستخدام للكاميرا
في السيناريوهات الموضحة أعلاه، يمكن إعداد حالات استخدام الكاميرا عندما
تم إنشاء "Activity
" للمرة الأولى.
يتم إكمال عملية الإعداد هذه في حال استخدام شاشة "Activity
" باتجاه غير مقفل.
في كل مرة يتم فيها تدوير الجهاز، فإن النظام يتلف
Activity
عند تغيير الاتجاه ينتج عن ذلك تحديد حالات الاستخدام
التدوير المستهدف لمطابقة اتجاه الشاشة تلقائيًا في كل مرة.
في حال استخدام Activity
باتجاه مُقفَل أو اتجاه يلغي
تتغير إعدادات الاتجاه، يتم تنفيذ هذا الإعداد مرة واحدة، عندما يتم تشغيل Activity
يتم إنشاؤه لأول مرة.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
إعداد OrientationEventListener
يتيح لك استخدام OrientationEventListener
تعديل الهدف باستمرار.
دوران حالات استخدام الكاميرا مع تغيُّر اتجاه الجهاز
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
إعداد DisplayListener
يتيح لك استخدام DisplayListener
تعديل تدوير الكاميرا الهدف.
حالات استخدام في مواقف معينة، على سبيل المثال عندما لا يتمكن النظام من تدمير
ثم إعادة إنشاء Activity
بعد تدوير الجهاز 180 درجة.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }