עדשות מצלמה ויכולות

הערה: הדף הזה מתייחס לחבילה Camera2. אם האפליקציה לא דורשת תכונות ספציפיות ברמה נמוכה מ-Camera2, מומלץ להשתמש ב-CameraX. גם CameraX וגם Camera2 תומכים ב-Android מגרסה 5.0 (רמת API‏ 21) ואילך.

בהרבה מכשירי Android מודרניים יש שתי מצלמות או יותר בחלק הקדמי, בחלק האחורי או בשני הצדדים של המכשיר. לכל עדשה יכולות להיות תכונות ייחודיות, כמו צילום ברצף, שליטה ידנית או מעקב תנועה. אפליקציה להפקדת צ'קים עשויה להשתמש רק במצלמה האחורית הראשונה, ואפליקציה של רשת חברתית עשויה להשתמש כברירת מחדל במצלמה הקדמית, אבל לתת למשתמשים אפשרות לעבור בין כל העדשות הזמינות. הוא גם יכול לזכור את הבחירות שלהם.

בדף הזה מוסבר איך להציג את עדשות המצלמה ואת היכולות שלהן, כדי שתוכלו להחליט באפליקציה באיזו עדשה להשתמש במצב נתון. קטע הקוד הבא מאחזר רשימה של כל המצלמות וחוזר עליהן:

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) }
    // ...
}

המשתנה cameraLensFacing מתאר את הכיוון שאליו פונה המצלמה ביחס למסך המכשיר, ויש לו אחד מהערכים הבאים:

מידע נוסף על הגדרת כיוון העדשה זמין במאמר CameraCharacteristics.LENS_FACING.

המשתנה cameraCapabilities מדוגמת הקוד הקודמת מכיל מידע על יכולות שונות, כולל אם המצלמה יכולה להפיק פריימים רגילים כפלט (בניגוד, למשל, לנתוני חיישן עומק בלבד). אפשר לבדוק אם CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE היא אחת מהיכולות של המצלמה שמופיעות ברשימה, שמאוחסנת כדגל ב-isBackwardCompatible.

בחירת הגדרות ברירת מחדל הגיוניות

באפליקציה שלכם, סביר להניח שתרצו לפתוח מצלמה ספציפית כברירת מחדל (אם היא זמינה). לדוגמה, אפליקציית סלפי כנראה תפתח את המצלמה הקדמית, ואפליקציית מציאות רבודה תתחיל עם המצלמה האחורית. הפונקציה הבאה מחזירה את המצלמה הראשונה שפונה לכיוון מסוים:

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) }
        return null
    }
}

הפעלת האפשרות לעבור בין מצלמות

אפליקציות מצלמה רבות מאפשרות למשתמשים לעבור בין מצלמות:

ממשק אפליקציית מצלמת Google שמוצג בו הכפתור להחלפת מצלמה.
איור 1. כפתור להחלפת מצלמה באפליקציית מצלמת Google

בהרבה מכשירים יש כמה מצלמות שפונות לאותו כיוון. בחלק מהמכשירים יש אפילו מצלמות USB חיצוניות. כדי לספק למשתמשים ממשק שמאפשר להם לעבור בין מצלמות שונות, צריך לבחור את המצלמה הזמינה הראשונה לכל הגדרה אפשרית של כיוון העדשה.

אין לוגיקה אוניברסלית לבחירת המצלמה הבאה, אבל הקוד הבא מתאים לרוב התרחישים לדוגמה:

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)
    }
}

הקוד הזה פועל במגוון רחב של מכשירים עם הרבה הגדרות שונות. למידע נוסף על טיפול במקרי קצה, ראו CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

יצירת אפליקציות תואמות

באפליקציות שעדיין משתמשות ב-Camera API שהוצא משימוש, מספר המצלמות שמוחזר על ידי Camera.getNumberOfCameras() תלוי בהטמעה של ה-OEM. אם יש במערכת מצלמה מרובת עדשות לוגית, כדי לשמור על תאימות של האפליקציה לדורות קודמים, השיטה הזו תחשוף רק מצלמה אחת לכל מצלמה לוגית וקבוצת מצלמות פיזיות בסיסיות. כדי לראות את כל המצלמות, צריך להשתמש ב-Camera2 API.

למידע נוסף על כיווני המצלמה, ראו Camera.CameraInfo.orientation.

באופן כללי, צריך להשתמש ב-API‏ Camera.getCameraInfo() כדי לשלוח שאילתות לכל ה-orientation של המצלמה, ולחשוף רק מצלמה אחת לכל כיוון זמין למשתמשים שעוברים בין מצלמות.

התאמה לכל סוגי המכשירים

אל תניחו שהאפליקציה תמיד פועלת במכשיר שניתן להחזיק ביד עם מצלמה אחת או שתיים. במקום זאת, בחרו את המצלמות המתאימות ביותר לאפליקציה. אם אתם לא צריכים מצלמה ספציפית, בחרו את המצלמה הראשונה שפונה לכיוון שבחרתם. אם המצלמה שפונה לכיוון מסוים לא זמינה, כדאי לבדוק אם המשתמש יכול להשלים את התהליך באמצעות מצלמה אחרת. אל תגבילו את הזמינות של האפליקציה במכשירים מסוימים על סמך חומרת המצלמה. אם מחוברת מצלמה חיצונית, סביר להניח שהמשתמש מעדיף אותה כברירת מחדל.