בנושא הזה נסביר איך להגדיר תרחישי שימוש של CameraX באפליקציה כדי לקבל תמונות עם פרטי הסיבוב הנכונים, בין שבתרחיש לדוגמה ImageAnalysis
ובין שבתרחיש לדוגמה ImageCapture
. כך:
- השדה
Analyzer
בתרחיש לדוגמהImageAnalysis
אמור לקבל פריימים עם הסיבוב הנכון. - בתרחיש לדוגמה
ImageCapture
, התמונות צריכות להצטלם עם הסיבוב הנכון.
טרמינולוגיה
בנושא הזה נעשה שימוש במונחים הבאים, לכן חשוב להבין את המשמעות של כל מונח:
- כיוון המסך
- הערך הזה מציין איזה צד של המכשיר נמצא במצב שפונה למעלה, והוא יכול להיות אחד מארבעה ערכים: portrait, landscape, reverse portrait או reverse landscape.
- סיבוב מסך
- זהו הערך שמוחזר על ידי
Display.getRotation()
, והוא מייצג את מספר המעלות שבהן המכשיר מסובב נגד כיוון השעון מהכיוון הטבעי שלו. - רוטציה של מודעות יעד
- הערך הזה מייצג את מספר המעלות שצריך לסובב את המכשיר בכיוון השעון כדי להגיע לכיוון הטבעי שלו.
איך קובעים את רוטציית היעדים
בדוגמאות הבאות מוסבר איך לקבוע את סיבוב היעד של מכשיר על סמך הכיוון הטבעי שלו.
דוגמה 1: כיוון טבעי לאורך
דוגמה למכשיר: Pixel 3 XL | |
---|---|
כיוון טבעי = לאורך סיבוב המסך = 0 |
|
כיוון טבעי = לאורך סיבוב המסך = 90 |
דוגמה 2: כיוון טבעי לרוחב
דוגמה למכשיר: Pixel C | |
---|---|
כיוון טבעי = לרוחב סיבוב המסך = 0 |
|
כיוון טבעי = לרוחב סיבוב המסך = 270 |
סיבוב תמונה
איזה קצה למעלה? כיוון החיישן מוגדר ב-Android כערך קבוע שמייצג את המעלות (0, 90, 180, 270) שבהן החיישן מסתובב מחלקו העליון של המכשיר כשהמכשיר נמצא במצב טבעי. בכל המקרים בתרשים, כיוון הסיבוב של התמונה מתאר את האופן שבו צריך לסובב את הנתונים בכיוון השעון כדי שהם יופיעו בצורה זקופה.
בדוגמאות הבאות מוצגת כיוון הסיבוב של התמונה בהתאם לכיוון של חיישן המצלמה. הם גם מניחים שהכיוון של היעד מוגדר לכיוון של המסך.
דוגמה 1: חיישן שהופנה ב-90 מעלות
דוגמה למכשיר: Pixel 3 XL | |
---|---|
סיבוב המסך = 0 |
|
סיבוב המסך = 90 |
דוגמה 2: חיישן שהסתובב ב-270 מעלות
דוגמה למכשיר: Nexus 5X | |
---|---|
סיבוב המסך = 0 |
|
סיבוב המסך = 90 |
דוגמה 3: החיישן סובב ב-0 מעלות
דוגמה למכשיר: Pixel C (טאבלט) | |
---|---|
סיבוב המסך = 0 |
|
סיבוב המסך = 270 |
חישוב סיבוב של תמונה
ImageAnalysis
ה-Analyzer
של ImageAnalysis
מקבל תמונות מהמצלמה בצורת ImageProxy
. כל תמונה מכילה מידע על סיבוב, שאפשר לגשת אליו באמצעות:
val rotation = imageProxy.imageInfo.rotationDegrees
הערך הזה מייצג את מספר המעלות שבהן צריך לסובב את התמונה בכיוון השעון כדי שתתאים לסיבוב היעד של ImageAnalysis
. בהקשר של אפליקציה ל-Android, בדרך כלל כיוון היעד של ImageAnalysis
יתאים לכיוון המסך.
ImageCapture
פונקציית קריאה חוזרת מצורפת למכונה של ImageCapture
כדי לסמן מתי תוצאת הצילום מוכנה. התוצאה יכולה להיות התמונה שצולמה או הודעת שגיאה.
כשמצלמים תמונה, אפשר לספק פונקציית קריאה חוזרת (callback) מאחד מהסוגים הבאים:
OnImageCapturedCallback
: מקבלת תמונה עם גישה בזיכרון בדמותImageProxy
.OnImageSavedCallback
: הפונקציה מופעלת כשהתמונה שצולמה נשמרת בהצלחה במיקום שצוין ב-ImageCapture.OutputFileOptions
. האפשרויות יכולות לצייןFile
,OutputStream
או מיקום ב-MediaStore
.
סיבוב התמונה שצולמה, ללא קשר לפורמט שלה (ImageProxy
, File
, OutputStream
, MediaStore Uri
), מייצג את מעלות הסיבוב שצריך לסובב את התמונה שצולמה בכיוון השעון כדי שתתאים לסיבוב היעד של ImageCapture
. שוב, בהקשר של אפליקציה ל-Android, סיבוב התמונה בדרך כלל תואם לכיוון המסך.
אפשר לאחזר את כיוון התמונה שצולמה באחת מהדרכים הבאות:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
אימות הסיבוב של תמונה
בתרחישי השימוש ImageAnalysis
ו-ImageCapture
, ה-ImageProxy
מתקבל מהמצלמה אחרי בקשת צילום מוצלחת. ה-ImageProxy
עוטף תמונה ומידע עליה, כולל הסיבוב שלה. פרטי הסיבוב מייצגים את מספר המעלות שבהן צריך לסובב את התמונה כדי שתתאים לסיבוב היעד של תרחיש השימוש.
הנחיות לסיבוב היעד ב-ImageCapture/ImageAnalysis
מאחר שבמכשירים רבים אין תמיכה כברירת מחדל בכיוון צילום הפוך (לאורך או לרוחב), חלק מהאפליקציות ל-Android לא תומכות בכיוונים האלה. אם האפליקציה תומכת בכך או לא, המשמעות היא שיהיה שינוי באופן שבו אפשר לעדכן את רוטציית היעדים בתרחישי השימוש.
בהמשך מפורטות שתי טבלאות שמגדירות איך לשמור על סנכרון בין כיוון התצוגה לבין כיוון היעד של התרחישים לדוגמה. בקוד הראשון מוסבר איך לעשות זאת תוך תמיכה בכל ארבעת הכיוונים, ובקוד השני מוסבר איך לעשות זאת רק בכיוונים שהמכשיר מסתובב אליהם כברירת מחדל.
כדי לבחור אילו הנחיות ליישם באפליקציה:
מוודאים אם למצלמה
Activity
של האפליקציה יש כיוון נעילה, כיוון לא נעול או אם היא מבטלת שינויים בהגדרות הכיוון.קובעים אם המצלמה
Activity
של האפליקציה תטפל בכל ארבעת הכיוונים של המכשיר (לאורך, הפוך לרוחב, לרוחב והפוך לרוחב), או רק בכיוונים שהמכשיר שבו היא פועלת תומך בהם כברירת מחדל.
תמיכה בכל ארבעת הכיוונים
בטבלה הזו מפורטות הנחיות מסוימות שצריך לפעול לפיהן במקרים שבהם המכשיר לא מסתובב לכיוון האנכי ההפוך. אפשר לעשות את אותו הדבר במכשירים שלא מסתובבים לפריסה לרוחב.
תרחיש | הנחיות | מצב חלון יחיד | מצב מסך מפוצל עם כמה חלונות |
---|---|---|---|
כיוון פתוח |
מגדירים את תרחישים לדוגמה בכל פעם שנוצר Activity , למשל בקריאה החוזרת (callback) של onCreate() ב-Activity .
|
||
משתמשים ב-onOrientationChanged() של OrientationEventListener .
בתוך קריאת החזרה (callback), מעדכנים את רוטציית היעדים של תרחישים לדוגמה. כך ניתן לטפל במקרים שבהם המערכת לא יוצרת מחדש את Activity גם אחרי שינוי כיוון, למשל כשמסובבים את המכשיר ב-180 מעלות.
|
הקוד הזה מטפל גם במקרים שבהם המסך בכיוון לאורך הפוך והמכשיר לא מסתובב לכיוון לאורך הפוך כברירת מחדל. |
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
אופציונלי: מגדירים את המאפיין screenOrientation של Activity לערך fullSensor בקובץ AndroidManifest .
|
כך ממשק המשתמש יהיה זקוף כשהמכשיר במצב 'פורטרט' הפוך, ואפשר יהיה ליצור מחדש את Activity בכל פעם שהמכשיר מסתובב ב-90 מעלות.
|
ההגדרה הזו לא משפיעה על מכשירים שלא מסתובבים לכיוון הפוך כברירת מחדל. אי אפשר להשתמש במצב 'חלונות מרובים' כשהמסך בכיוון לאורך הפוך. | |
כיוון נעול |
מגדירים את תרחישים לדוגמה רק פעם אחת, כשיוצרים את Activity בפעם הראשונה, למשל בקריאה החוזרת (callback) של onCreate() .Activity
|
||
משתמשים ב-onOrientationChanged() של OrientationEventListener .
בתוך קריאת החזרה (callback), מעדכנים את רוטציית היעד של תרחישים לדוגמה, מלבד 'תצוגה מקדימה'.
|
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
||
Orientation configChanges overridden |
מגדירים את תרחישים לדוגמה רק פעם אחת, כשיוצרים את Activity בפעם הראשונה, למשל בקריאה החוזרת (callback) של onCreate() .Activity
|
||
משתמשים ב-onOrientationChanged() של OrientationEventListener .
בתוך קריאת החזרה (callback), מעדכנים את רוטציית היעדים של תרחישים לדוגמה.
|
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
||
אופציונלי: מגדירים את המאפיין screenOrientation של הפעילות לערך fullSensor בקובץ AndroidManifest. | מאפשרת לממשק המשתמש להיות זקוף כשהמכשיר במצב לאורך הפוך. | ההגדרה הזו לא משפיעה על מכשירים שלא מבצעים סיבוב לכיוון הפוך כברירת מחדל. אי אפשר להשתמש במצב 'חלונות מרובים' כשהמסך בכיוון לאורך הפוך. |
תמיכה רק בכיוונים הנתמכים במכשיר
תמיכה רק בכיוונים שהמכשיר תומך בהם כברירת מחדל (יכול להיות שזה כולל כיוון לאורך הפוך או לרוחב הפוך, ויכול להיות שלא).
תרחיש | הנחיות | מצב מסך מפוצל עם כמה חלונות |
---|---|---|
כיוון פתוח |
מגדירים את תרחישים לדוגמה בכל פעם שנוצר Activity , למשל בקריאה החוזרת (callback) של onCreate() ב-Activity .
|
|
משתמשים ב-onDisplayChanged() של DisplayListener . בתוך הקריאה החוזרת, מעדכנים את סיבוב היעד של תרחישי השימוש, למשל כשהמכשיר מסתובב ב-180 מעלות.
|
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
כיוון נעול |
מגדירים את תרחישים לדוגמה רק פעם אחת, כשיוצרים את Activity בפעם הראשונה, למשל בקריאה החוזרת (callback) של onCreate() .Activity
|
|
משתמשים ב-onOrientationChanged() של OrientationEventListener .
בתוך קריאת החזרה (callback), מעדכנים את רוטציית היעדים של תרחישים לדוגמה.
|
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
Orientation configChanges overridden |
מגדירים את תרחישים לדוגמה רק פעם אחת, כשיוצרים את Activity בפעם הראשונה, למשל בקריאה החוזרת (callback) של onCreate() .Activity
|
|
משתמשים ב-onDisplayChanged() של DisplayListener . בתוך הקריאה החוזרת, מעדכנים את סיבוב היעד של תרחישי השימוש, למשל כשהמכשיר מסתובב ב-180 מעלות.
|
הוא מטפל גם במקרים שבהם ה-Activity לא נוצר מחדש כשהמכשיר מסתובב (למשל, ב-90 מעלות). המצב הזה מתרחש במכשירים בפורמט קטן כשהאפליקציה תופסת חצי מהמסך, ובמכשירים גדולים יותר כשהאפליקציה תופסת שני שלישים מהמסך.
|
כיוון לא נעול
כיוון התצוגה של Activity
לא נעול כשהוא תואם לכיוון הפיזי של המכשיר (למשל, לאורך או לרוחב), מלבד כיוון הפוך לרוחב או לאורך, שלא תומכים בו כברירת מחדל במכשירים מסוימים. כדי לאלץ את המכשיר להתרוטט לכל ארבעת הכיוונים, מגדירים את המאפיין screenOrientation
של Activity
לערך fullSensor
.
במצב'חלונות מרובים', מכשיר שלא תומך בפריסה לרוחב או בפריסה לרוחב הפוכה כברירת מחדל לא יתהפך לפריסה לרוחב או לפריסה לרוחב הפוכה, גם אם המאפיין screenOrientation
מוגדר ל-fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
כיוון נעול
המסך נעול בכיוון מסוים כשהוא נשאר באותו כיוון תצוגה (למשל לאורך או לרוחב) ללא קשר לכיוון הפיזי של המכשיר. כדי לעשות זאת, מציינים את המאפיין screenOrientation
של Activity
בתוך ההצהרה שלו בקובץ AndroidManifest.xml
.
כשהמסך נעול בכיוון מסוים, המערכת לא משמידה את Activity
ויוצרת אותו מחדש כשמסובבים את המכשיר.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
שינויים בהגדרות של כיוון המסך בוטלו
כשהמערכת מעדכנת את Activity
כדי לעקוף שינויים בהגדרות של כיוון המסך, היא לא משמידה את הקוד הזה ויוצרת אותו מחדש כשהכיוון הפיזי של המכשיר משתנה.
עם זאת, המערכת מעדכנת את ממשק המשתמש כך שיתאים לכיוון הפיזי של המכשיר.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
הגדרת תרחישים לדוגמה של שימוש במצלמה
בתרחישים המתוארים למעלה, אפשר להגדיר את התרחישים לדוגמה של שימוש במצלמה כשאתם יוצרים את Activity
בפעם הראשונה.
במקרה של Activity
עם כיוון פתוח, ההגדרה הזו מתבצעת בכל פעם שמסובבים את המכשיר, כי המערכת משמידה את Activity
ויוצרת אותו מחדש כשכיוון התצוגה משתנה. כתוצאה מכך, בכל פעם שתרחישי לדוגמה יתבצעו, כיוון היעד שלהם יוגדר כברירת מחדל בהתאם לכיוון המסך.
במקרה של Activity
עם כיוון נעילה או Activity
שמבטל שינויים בהגדרות הכיוון, ההגדרה הזו מתבצעת פעם אחת, כשיוצרים את ה-Activity
בפעם הראשונה.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
הגדרת OrientationEventListener
שימוש ב-OrientationEventListener
מאפשר לכם לעדכן ברציפות את סיבוב היעד של תרחישי השימוש במצלמה כשכיוון המכשיר משתנה.
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
הגדרת DisplayListener
שימוש ב-DisplayListener
מאפשר לעדכן את סיבוב היעד של תרחישי השימוש במצלמה במצבים מסוימים, למשל כשהמערכת לא משמידה את ה-Activity
ויוצרת אותו מחדש אחרי שהמכשיר מסתובב ב-180 מעלות.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }