ملاحظة: تشير هذه الصفحة إلى حزمة Camera2. ننصحك باستخدام CameraX ما لم يتطلب تطبيقك ميزات معيّنة منخفضة المستوى من Camera2. تتوافق حزمتا CameraX وCamera2 مع الإصدار 5.0 من Android (المستوى 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_FRONTCameraMetadata.LENS_FACING_BACKCameraMetadata.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 API المتوقّفة نهائيًا، يعتمد عدد الكاميرات
التي
Camera.getNumberOfCameras()
تعرضها على تنفيذ الشركة المصنّعة للمعدات الأصلية. إذا كانت هناك كاميرا منطقية متعددة في النظام، وللحفاظ على التوافق مع الإصدارات السابقة من التطبيق، لن تعرض هذه الطريقة سوى كاميرا واحدة لكل كاميرا منطقية ومجموعة كاميرات فعلية أساسية.
استخدِم Camera2 API للاطّلاع على جميع الكاميرات.
لمزيد من المعلومات الأساسية عن اتجاهات الكاميرا، يُرجى الاطّلاع على
Camera.CameraInfo.orientation.
بشكل عام، استخدِم الـ
Camera.getCameraInfo()
API للاستعلام عن جميع
orientation الكاميرا،
ولا تعرض للمستخدمين الذين
ينتقلون بين الكاميرات سوى كاميرا واحدة لكل اتجاه متاح.
استيعاب جميع أنواع الأجهزة
لا تفترض أنّ تطبيقك يعمل دائمًا على جهاز محمول بكاميرا واحدة أو اثنتَين. بدلاً من ذلك، اختَر الكاميرات الأنسب للتطبيق. إذا لم تكن بحاجة إلى كاميرا معيّنة، اختَر الكاميرا الأولى التي تواجه الاتجاه المطلوب. إذا لم تكن الكاميرا التي تواجه اتجاهًا معيّنًا متاحة، ففكِّر في ما إذا كان بإمكان المستخدم إكمال رحلته باستخدام كاميرا مختلفة. لا تقيّد توفّر تطبيقك على أجهزة معيّنة استنادًا إلى أجهزة الكاميرا. إذا كانت كاميرا خارجية متصلة، من المحتمل أنّ المستخدم يفضّلها كإعداد تلقائي.