להפעיל או לכבות את הבמצלמה.

בשיעור הזה נסביר איך לשלוט ישירות בחומרת המצלמה באמצעות ממשקי ה-API של framework.

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

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

כדאי לעיין במקורות המידע שקשורים לנושא:

פתיחת אובייקט המצלמה

קבלת מופע של האובייקט Camera היא השלב הראשון ב תהליך של שליטה ישירה במצלמה. כפי שעושה אפליקציית המצלמה של Android, דרך מומלצת לגשת למצלמה היא לפתוח את Camera בשרשור נפרד שהושק מ-onCreate(). כדאי להשתמש בגישה הזאת כי הוא עלול לקחת זמן מה ולפגוע בשרשור של ממשק המשתמש. בהטמעה בסיסית יותר, ניתן לדחות את פתיחת המצלמה לשיטה onResume() כדי לאפשר שימוש חוזר בקוד ולשמור על הזרימה של לשלוט בצורה פשוטה.

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

Kotlin

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

Java

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

מכיוון שרמת ה-API היא 9, ה-framework של המצלמה תומך במספר מצלמות. אם משתמשים API מדור קודם והפעלה של open() ללא מקבלים את המצלמה האחורית הראשונה.

יצירת תצוגה מקדימה של המצלמה

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

תצוגה מקדימה של הכיתה

כדי להתחיל להציג תצוגה מקדימה, צריך תצוגה מקדימה של הכיתה. לתצוגה המקדימה נדרשת הטמעה של הממשק של android.view.SurfaceHolder.Callback, שמשמש להעברת תמונה מחומרת המצלמה אל האפליקציה.

Kotlin

class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

Java

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

צריך להעביר את המחלקה של התצוגה המקדימה לאובייקט Camera לפני הפעלת המחלקה כדי להתחיל תצוגה מקדימה של התמונות, כפי שמוצג בקטע הבא.

קביעה והתחלת התצוגה המקדימה

צריך ליצור מופע מצלמה ואת התצוגה המקדימה שקשורה אליו במיקום ספציפי בסדר, כשאובייקט המצלמה הוא הראשון. בקטע הקוד שלמטה, תהליך אתחול המצלמה מוסתר על ידי הקריאה של Camera.startPreview() setCamera(), בכל פעם שהמשתמש מבצע משהו כדי לשנות מצלמה. צריך להפעיל מחדש את התצוגה המקדימה גם בשיטת הקריאה החוזרת של הכיתה surfaceChanged().

Kotlin

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

שינוי הגדרות המצלמה

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

Kotlin

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

הגדרת כיוון התצוגה של התצוגה המקדימה

רוב אפליקציות המצלמה נועלות את המסך לפריסה לרוחב, כי זה טבעי של חיישן המצלמה. ההגדרה הזו לא מונעת ממך לעבור למצב 'הדגשת דיוקן' תמונות, כי הכיוון של המכשיר מתועד בכותרת ה-EXIF. השיטה setCameraDisplayOrientation() מאפשרת לשנות האופן שבו מוצגת התצוגה המקדימה בלי להשפיע על אופן הצילום של התמונה. אבל, בגרסאות קודמות של Android לרמת API 14, חייבים לעצור את התצוגה המקדימה לפני שינוי הכיוון ואז להפעיל אותו מחדש.

צילום תמונה

שימוש בCamera.takePicture() לצילום תמונה לאחר שהתצוגה המקדימה מתחילה. אפשר ליצור אובייקטים מסוג Camera.PictureCallback ו-Camera.ShutterCallback ולהעביר אותם אל Camera.takePicture().

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

הפעלה מחדש של התצוגה המקדימה

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

Kotlin

fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

Java

@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

הפסקת התצוגה המקדימה ושחרור המצלמה

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

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

Kotlin

override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}

Java

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

מוקדם יותר בשיעור, התהליך הזה היה גם חלק מהשיטה setCamera(), כך שאתחול מצלמה תמיד מתחיל בעצירה של הפקודה תצוגה מקדימה.