בנושא הזה נסביר איך להגדיר תרחישים לדוגמה של CameraX באפליקציה שלך כדי לקבל
עם מידע נכון לגבי הסבב, בין אם
ImageAnalysis
או את התרחיש לדוגמה ImageCapture
. כך:
Analyzer
של התרחיש לדוגמהImageAnalysis
אמור לקבל פריימים עם הסיבוב הנכון.- בתרחיש לדוגמה של
ImageCapture
, צריך לצלם את התמונות עם הסיבוב הנכון.
טרמינולוגיה
נושא זה משתמש במונחים הבאים, לכן הבנה של המשמעות של כל מונח חשוב:
- כיוון המסך
- הוא מציין איזה צד של המכשיר נמצא במיקום כלפי מעלה, והוא יכול להיות אחד מתוך ארבעה ערכים: לאורך, לרוחב, לאורך או הפוך לרוחב.
- סיבוב התצוגה
- זהו הערך שהוחזר על ידי
Display.getRotation()
, וגם מייצג את המעלות שבהן מסובב את המכשיר נגד כיוון השעון הכיוון הטבעי שלו. - סבב יעד
- המשפט הזה מייצג את מספר המעלות שיש לסובב דרכן במכשיר בכיוון השעון כדי להגיע לכיוון הטבעי שלו.
איך קובעים את סבב היעד
הדוגמאות הבאות מראות איך לקבוע את סבב היעד של מכשיר על סמך הכיוון הטבעי שלו.
דוגמה 1: כיוון טבעי לאורך
דוגמה למכשיר: Pixel 3 XL | |
---|---|
כיוון טבעי = לאורך סיבוב התצוגה = |
|
כיוון טבעי = לאורך סיבוב התצוגה = |
דוגמה 2: כיוון טבעי לרוחב
מכשיר לדוגמה: Pixel C | |
---|---|
כיוון טבעי = לרוחב סיבוב התצוגה = |
|
כיוון טבעי = לרוחב סיבוב התצוגה = |
סיבוב התמונה
מה מסתיים? כיוון החיישן מוגדר ב-Android כקבוע ערך, שמייצג את המעלות (0, 90, 180, 270) שמהם מסובב החיישן החלק העליון של המכשיר כאשר המכשיר נמצא במיקום טבעי. לכל במקרים מסוימים בדיאגרמות, סיבוב התמונות מתאר את האופן שבו הנתונים מסובבים בכיוון השעון כדי להופיע בצורה זקופה.
הדוגמאות הבאות מראות מה צריך להיות סיבוב התמונות בהתאם בכיוון של חיישן המצלמה. הם גם מניחים שסבב היעד מוגדר סיבוב המסך.
דוגמה 1: החיישן מסובב ב-90 מעלות
דוגמה למכשיר: Pixel 3 XL | |
---|---|
סיבוב התצוגה = |
|
סיבוב התצוגה = |
דוגמה 2: החיישן מסובב ב-270 מעלות
דוגמה למכשיר: Nexus 5X | |
---|---|
סיבוב התצוגה = |
|
סיבוב התצוגה = |
דוגמה 3: החיישן מסובב ב-0 מעלות
דוגמה למכשיר: Pixel C (טאבלט) | |
---|---|
סיבוב התצוגה = |
|
סיבוב התצוגה = |
חישוב הסיבוב של תמונה
ניתוח תמונה
מכשיר Analyzer
של ImageAnalysis
מקבל תמונות מהמצלמה בצורת
ImageProxy
שנ'. כל תמונה מכילה מידע על הסיבוב, וניתן לגשת אליו
באמצעות:
val rotation = imageProxy.imageInfo.rotationDegrees
הערך הזה מייצג את המעלות שבהן צריך לסובב את התמונה
בכיוון השעון כדי להתאים לסיבוב היעד של ImageAnalysis
. בהקשר של
באפליקציה ל-Android, סבב היעד של ImageAnalysis
בדרך כלל תואם
בכיוון המסך.
צילום תמונה
קריאה חוזרת (callback) מצורפת למופע של 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) של Activity ב-onCreate() .
|
||
להשתמש ב-OrientationEventListener
onOrientationChanged() .
בתוך הקריאה החוזרת, מעדכנים את סבב היעד של התרחישים לדוגמה. כך תטפל במקרים שבהם המערכת לא
ליצור מחדש את Activity גם אחרי שינוי כיוון,
כמו כשהמכשיר מסובב ב-180 מעלות.
|
הכינוי גם יופיע במסך הפוך כיוון לאורך והמכשיר לא מסתובבים לכיוון הפוך על ידי כברירת מחדל. |
טיפול גם במקרים שבהם Activity לא
נוצר מחדש כשהמכשיר מסתובב (לדוגמה, ב-90 מעלות). הפעולה הזו מתבצעת במקרים הבאים
מכשירים קטנים של גורם צורה כשהאפליקציה תופסת חצי מהמסך,
כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
אופציונלי: אפשר להגדיר את screenOrientation של Activity
לנכס fullSensor ב-AndroidManifest
חדש.
|
פעולה זו מאפשרת לממשק המשתמש לפעול בצורה זקופה כשהמכשיר הפוך
לאורך, ומאפשרים ליצור מחדש את Activity
המערכת בכל פעם שמסובבים את המכשיר ב-90 מעלות.
|
אין השפעה על מכשירים שלא מסתובבים כדי להפוך את הדיוקן על ידי כברירת מחדל. אין תמיכה במצב ריבוי חלונות כשהמסך מוצג כיוון הפוך לאורך. | |
כיוון נעול |
מגדירים את התרחישים לדוגמה רק פעם אחת,
Activity נוצר לראשונה, כמוActivity
התקשרות חזרה onCreate() .
|
||
להשתמש ב-OrientationEventListener
onOrientationChanged() .
בתוך הקריאה החוזרת, מעדכנים את סבב היעד של התרחישים לדוגמה, חוץ מ-Preview.
|
טיפול גם במקרים שבהם Activity לא
נוצר מחדש כשהמכשיר מסתובב (לדוגמה, ב-90 מעלות). הפעולה הזו מתבצעת במקרים הבאים
מכשירים קטנים של גורם צורה כשהאפליקציה תופסת חצי מהמסך,
כשהאפליקציה תופסת שני שלישים מהמסך.
|
||
שינויים בהגדרות הכיוון בוטלו |
מגדירים את התרחישים לדוגמה רק פעם אחת,
Activity נוצר לראשונה, כמוActivity
התקשרות חזרה onCreate() .
|
||
להשתמש ב-OrientationEventListener
onOrientationChanged() .
בתוך הקריאה החוזרת, מעדכנים את סבב היעד של התרחישים לדוגמה.
|
טיפול גם במקרים שבהם Activity לא
נוצר מחדש כשהמכשיר מסתובב (לדוגמה, ב-90 מעלות). הפעולה הזו מתבצעת במקרים הבאים
מכשירים קטנים של גורם צורה כשהאפליקציה תופסת חצי מהמסך,
כשהאפליקציה תופסת שני שלישים מהמסך.
|
||
אופציונלי: מגדירים את מאפיין screenOrientation של הפעילות כ- fullSensor הקובץ AndroidManifest. | מאפשרת לממשק המשתמש להופיע בצורה זקופה כשהמכשיר נמצא בפריסה לאורך. | אין השפעה על מכשירים שלא מסתובבים כדי להפוך את הדיוקן על ידי כברירת מחדל. אין תמיכה במצב ריבוי חלונות כשהמסך מוצג כיוון הפוך לאורך. |
תמיכה רק בכיוונים שנתמכים על ידי המכשיר
תמיכה רק בכיוונים שהמכשיר תומך בהם כברירת מחדל (שעשויים או לא יכול לכלול היפוך כיוון הצילום או לרוחב).
תרחיש | הנחיות | מצב מסך מפוצל עם מספר חלונות |
---|---|---|
הכיוון לא נעול |
להגדיר את תרחישי השימוש בכל
זמן היצירה של Activity , כמו
הקריאה החוזרת (callback) של Activity ב-onCreate() .
|
|
להשתמש ב-DisplayListener
onDisplayChanged() . בתוך
קריאה חוזרת, מעדכנים את הרוטציה ביעד של התרחישים לדוגמה, למשל כאשר
המכשיר מסובב ב-180 מעלות.
|
טיפול גם במקרים שבהם Activity לא
נוצר מחדש כשהמכשיר מסתובב (לדוגמה, ב-90 מעלות). הפעולה הזו מתבצעת במקרים הבאים
מכשירים קטנים של גורם צורה כשהאפליקציה תופסת חצי מהמסך,
כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
כיוון נעול |
מגדירים את התרחישים לדוגמה רק פעם אחת,
Activity נוצר לראשונה, כמוActivity
התקשרות חזרה onCreate() .
|
|
להשתמש ב-OrientationEventListener
onOrientationChanged() .
בתוך הקריאה החוזרת, מעדכנים את סבב היעד של התרחישים לדוגמה.
|
טיפול גם במקרים שבהם Activity לא
נוצר מחדש כשהמכשיר מסתובב (לדוגמה, ב-90 מעלות). הפעולה הזו מתבצעת במקרים הבאים
מכשירים קטנים של גורם צורה כשהאפליקציה תופסת חצי מהמסך,
כשהאפליקציה תופסת שני שלישים מהמסך.
|
|
שינויים בהגדרות הכיוון בוטלו |
מגדירים את התרחישים לדוגמה רק פעם אחת,
Activity נוצר לראשונה, כמוActivity
התקשרות חזרה onCreate() .
|
|
להשתמש ב-DisplayListener
onDisplayChanged() . בתוך
קריאה חוזרת, מעדכנים את הרוטציה ביעד של התרחישים לדוגמה, למשל כאשר
המכשיר מסובב ב-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
נוצר לראשונה.
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) } }