ملاحظة: تشير هذه الصفحة إلى حزمة camera2. ننصحك باستخدام cameraX ما لم يكن تطبيقك يتطلب ميزات محدّدة ومنخفضة المستوى من Camera2. يتوافق كل من CameraX و Camera2 مع الإصدار Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.
تحتوي العديد من أجهزة Android الحديثة على كاميرتَين أو أكثر في الجهة الأمامية أو الخلفية أو كلا جانبَي الجهاز. يمكن أن يكون لكل عدسة إمكانيات فريدة، مثل التقاط الصور المتسلسلة أو التحكم اليدوي أو تتبع الحركة. قد يستخدم تطبيق إيداع الشيكات أول كاميرا خلفية فقط، بينما قد يستخدم تطبيق وسائل التواصل الاجتماعي تلقائيًا كاميرا أمامية، لكن مع منح المستخدمين خيار التبديل بين جميع العدسات المتاحة. كما يمكنه أيضًا تذكّر خياراتهم.
تتناول هذه الصفحة طريقة إدراج عدسات الكاميرا وإمكاناتها لتتمكّن من اتخاذ قرارات داخل تطبيقك بشأن العدسة التي يجب استخدامها في حالة معيّنة. يسترد مقتطف الرمز التالي قائمة بجميع الكاميرات ثم يتكرر عرضها:
Kotlin
try { val cameraIdList = cameraManager.cameraIdList // may be empty // iterate over available camera devices for (cameraId in cameraIdList) { val characteristics = cameraManager.getCameraCharacteristics(cameraId) val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING) val cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) // check if the selected camera device supports basic features // ensures backward compatibility with the original Camera API val isBackwardCompatible = cameraCapabilities?.contains( CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false ... } } catch (e: CameraAccessException) { e.message?.let { Log.e(TAG, it) } ... }
Java
try { String[] cameraIdList = cameraManager.getCameraIdList(); // may be empty // iterate over available camera devices for (String cameraId : cameraIdList) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); int cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING); int[] cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); // check if the selected camera device supports basic features // ensures backward compatibility with the original Camera API boolean isBackwardCompatible = false; for (int capability : cameraCapabilities) { if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) { isBackwardCompatible = true; break; } } ... } } catch (CameraAccessException e) { Log.e(TAG, e.getMessage()); ... }
يصف المتغيّر cameraLensFacing
الاتجاه الذي تواجهه الكاميرا
بالنسبة إلى شاشة الجهاز، ويحتوي على إحدى القيم التالية:
CameraMetadata.LENS_FACING_FRONT
CameraMetadata.LENS_FACING_BACK
CameraMetadata.LENS_FACING_EXTERNAL
لمزيد من المعلومات عن إعدادات واجهة العدسة، اطّلِع على القسم
CameraCharacteristics.LENS_FACING
.
يحتوي المتغير cameraCapabilities
من عينة الرمز السابقة على
معلومات حول الإمكانات المتنوعة، بما في ذلك ما إذا كانت الكاميرا
قادرة على إنتاج إطارات قياسية كمخرجات (على سبيل المثال،
بيانات أداة استشعار العمق فقط). يمكنك التحقّق ممّا إذا كانت ميزة
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
هي أحد الإمكانات المدرَجة في الكاميرا، ويتم تخزينها كعلم في
isBackwardCompatible
.
اختيار الإعدادات التلقائية المعقولة
في تطبيقك، من المحتمل أنّك تريد فتح كاميرا معيّنة تلقائيًا (إذا كانت متاحة). على سبيل المثال، من المرجح أن يفتح تطبيق الصور الذاتية الكاميرا الأمامية، بينما يبدأ تطبيق الواقع المعزَّز بالكاميرا الخلفية. تعرض الدالة التالية أول كاميرا متجهة إلى اتجاه معين:
Kotlin
fun getFirstCameraIdFacing(cameraManager: CameraManager, facing: Int = CameraMetadata.LENS_FACING_BACK): String? { try { // Get list of all compatible cameras val cameraIds = cameraManager.cameraIdList.filter { val characteristics = cameraManager.getCameraCharacteristics(it) val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) capabilities?.contains( CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false } // Iterate over the list of cameras and return the first one matching desired // lens-facing configuration cameraIds.forEach { val characteristics = cameraManager.getCameraCharacteristics(it) if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) { return it } } // If no camera matched desired orientation, return the first one from the list return cameraIds.firstOrNull() } catch (e: CameraAccessException) { e.message?.let { Log.e(TAG, it) } } }
Java
public String getFirstCameraIdFacing(CameraManager cameraManager, @Nullable Integer facing) { if (facing == null) facing = CameraMetadata.LENS_FACING_BACK; String cameraId = null; try { // Get a list of all compatible cameras String[] cameraIdList = cameraManager.getCameraIdList(); // Iterate over the list of cameras and return the first one matching desired // lens-facing configuration and backward compatibility for (String id : cameraIdList) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); for (int capability : capabilities) { if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE && characteristics.get(CameraCharacteristics.LENS_FACING).equals(facing)) { cameraId = id; break; } } } // If no camera matches the desired orientation, return the first one from the list cameraId = cameraIdList[0]; } catch (CameraAccessException e) { Log.e(TAG, "getFirstCameraIdFacing: " + e.getMessage()); } return cameraId; }
تفعيل تبديل الكاميرات
توفّر العديد من تطبيقات الكاميرات للمستخدمين خيار التبديل بين الكاميرات:
يحتوي العديد من الأجهزة على عدة كاميرات في الاتجاه نفسه. تحتوي بعضها أيضًا على كاميرات USB خارجية. ولتزويد المستخدمين بواجهة مستخدم تتيح لهم التبديل بين الكاميرات المختلفة، اختَر أول كاميرا متاحة لكل إعداد محتمل للعدسات.
على الرغم من عدم وجود منطق عام لاختيار الكاميرا التالية، تعمل التعليمة البرمجية التالية مع معظم حالات الاستخدام:
Kotlin
fun filterCompatibleCameras(cameraIds: Array<String>, cameraManager: CameraManager): List<String> { return cameraIds.filter { val characteristics = cameraManager.getCameraCharacteristics(it) characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains( CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false } } fun filterCameraIdsFacing(cameraIds: List<String>, cameraManager: CameraManager, facing: Int): List<String> { return cameraIds.filter { val characteristics = cameraManager.getCameraCharacteristics(it) characteristics.get(CameraCharacteristics.LENS_FACING) == facing } } fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? { // Get all front, back and external cameras in 3 separate lists val cameraIds = filterCompatibleCameras(cameraManager.cameraIdList, cameraManager) val backCameras = filterCameraIdsFacing( cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK) val frontCameras = filterCameraIdsFacing( cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT) val externalCameras = filterCameraIdsFacing( cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL) // The recommended order of iteration is: all external, first back, first front val allCameras = (externalCameras + listOf( backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull() // Get the index of the currently selected camera in the list val cameraIndex = allCameras.indexOf(currCameraId) // The selected camera may not be in the list, for example it could be an // external camera that has been removed by the user return if (cameraIndex == -1) { // Return the first camera from the list allCameras.getOrNull(0) } else { // Return the next camera from the list, wrap around if necessary allCameras.getOrNull((cameraIndex + 1) % allCameras.size) } }
Java
public List<String> filterCompatibleCameras(CameraManager cameraManager, String[] cameraIds) { final List<String> compatibleCameras = new ArrayList<>(); try { for (String id : cameraIds) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); for (int capability : capabilities) { if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) { compatibleCameras.add(id); } } } } catch (CameraAccessException e) { Log.e(TAG, "filterCompatibleCameras: " + e.getMessage()); } return compatibleCameras; } public List<String> filterCameraIdsFacing(CameraManager cameraManager, List<String> cameraIds, int lensFacing) { final List<String> compatibleCameras = new ArrayList<>(); try { for (String id : cameraIds) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); if (characteristics.get(CameraCharacteristics.LENS_FACING) == lensFacing) { compatibleCameras.add(id); } } } catch (CameraAccessException e) { Log.e(TAG, "filterCameraIdsFacing: " + e.getMessage()); } return compatibleCameras; } public String getNextCameraId(CameraManager cameraManager, @Nullable String currentCameraId) { String nextCameraId = null; try { // Get all front, back, and external cameras in 3 separate lists List<String> compatibleCameraIds = filterCompatibleCameras(cameraManager, cameraManager.getCameraIdList()); List<String> backCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_BACK); List<String> frontCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_FRONT); List<String>externalCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_EXTERNAL); // The recommended order of iteration is: all external, first back, first front List<String> allCameras = new ArrayList<>(externalCameras); if (!backCameras.isEmpty()) allCameras.add(backCameras.get(0)); if (!frontCameras.isEmpty()) allCameras.add(frontCameras.get(0)); // Get the index of the currently selected camera in the list int cameraIndex = allCameras.indexOf(currentCameraId); // The selected camera may not be in the list, for example it could be an // external camera that has been removed by the user if (cameraIndex == -1) { // Return the first camera from the list nextCameraId = !allCameras.isEmpty() ? allCameras.get(0) : null; else { if (!allCameras.isEmpty()) { // Return the next camera from the list, wrap around if necessary nextCameraId = allCameras.get((cameraIndex + 1) % allCameras.size()); } } } catch (CameraAccessException e) { Log.e(TAG, "getNextCameraId: " + e.getMessage()); } return nextCameraId; }
ويتوافق هذا الرمز مع مجموعة كبيرة من الأجهزة ذات عمليات الضبط المختلفة. لمزيد من المعلومات حول احتساب الحالات الحدّية، اطّلِع على CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
.
إنشاء تطبيقات متوافقة
بالنسبة إلى التطبيقات التي لا تزال تستخدم واجهة برمجة تطبيقات الكاميرا التي تم إيقافها نهائيًا، يعتمد عدد الكاميرات التي تعرضها
Camera.getNumberOfCameras()
على مدى تنفيذ المصنّع الأصلي للجهاز. إذا كانت هناك كاميرات متعددة منطقية في النظام، للحفاظ على التوافق مع الأنظمة القديمة للتطبيق، ستعرض هذه الطريقة كاميرا واحدة فقط لكل مجموعة كاميرات منطقية ومجموعة كاميرات فعلية أساسية.
يمكنك استخدام Camera2 API للاطّلاع على جميع الكاميرات.
لمزيد من المعلومات الأساسية حول اتجاهات الكاميرا، يُرجى الاطّلاع على
Camera.CameraInfo.orientation
.
وبشكل عام، استخدِم واجهة برمجة التطبيقات
Camera.getCameraInfo()
لإجراء طلبات بحث في جميع الكاميرات
orientation
،
وعرض كاميرا واحدة فقط لكل اتجاه متاح للمستخدمين
الذين يبدِّلون بين الكاميرات.
استيعاب جميع أنواع الأجهزة
لا تفترض أن تطبيقك يعمل دائمًا على جهاز محمول باليد مزوّد بكاميرا واحدة أو اثنتين. بدلاً من ذلك، اختَر الكاميرات الأكثر ملاءمةً للتطبيق. وإذا لم تكن بحاجة إلى كاميرا معيّنة، اختَر الكاميرا الأولى الموجَّهة إلى الاتجاه المطلوب. في حال توصيل كاميرا خارجية، قد تفترض أنّ المستخدم يفضِّلها ككاميرا تلقائية.