בשיעור הזה נסביר איך לשלוט בחומרת המצלמה ישירות באמצעות ממשקי ה-API של מסגרת העבודה.
הערה: הדף הזה מתייחס למחלקה Camera, שהוצאה משימוש. מומלץ להשתמש ב-CameraX או, בתרחישי שימוש ספציפיים, ב-Camera2. CameraX ו-Camera2 תומכות ב-Android מגרסה 5.0 (רמת API 21) ומעלה.
שליטה ישירה במצלמה של מכשיר דורשת הרבה יותר קוד מאשר בקשת תמונות או סרטונים מאפליקציות מצלמה קיימות. עם זאת, אם אתם רוצים ליצור אפליקציית מצלמה ייעודית או משהו שמשולב באופן מלא בממשק המשתמש של האפליקציה, בשיעור הזה מוסבר איך לעשות את זה.
כדאי לעיין במקורות המידע הבאים שקשורים לנושא:
פתיחת אובייקט המצלמה
השגת מופע של אובייקט Camera היא השלב הראשון בתהליך של שליטה ישירה במצלמה. כמו באפליקציית המצלמה של Android, הדרך המומלצת לגשת למצלמה היא לפתוח את Camera בשרשור נפרד שמופעל מ-onCreate(). הגישה הזו מומלצת כי היא יכולה להימשך זמן מה ולגרום להאטה בשרשור UI. ביישום בסיסי יותר, אפשר לדחות את פתיחת המצלמה לשיטה 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 ואילך, מסגרת המצלמה תומכת בכמה מצלמות. אם משתמשים ב-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() method כדי לצלם תמונה אחרי שהתצוגה המקדימה מתחילה. אפשר ליצור אובייקטים מסוג 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(), לכן הפעלה של מצלמה תמיד מתחילה בהפסקת התצוגה המקדימה.