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

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

במכשירי Android, המצלמות והתצוגות המקדימות של המצלמות לא תמיד נמצאות באותו כיוון.

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

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

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

כיוון המצלמה

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

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

הטלפון וחיישן המצלמה נמצאים במצב לאורך.
איור 1. היחס הרגיל בין כיוון הטלפון לכיוון חיישן המצלמה.

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

כדי לחשוף את סיבוב החיישן לאפליקציות, ה-API של camera2 כולל קבוע SENSOR_ORIENTATION. ברוב הטלפונים והטאבלטים, המכשיר מדווח על כיוון חיישן של 270 מעלות למצלמות קדמיות ו-90 מעלות (נקודת מבט מגב המכשיר) למצלמות אחוריות, מה שמיישר את הקצה הארוך של החיישן עם הקצה הארוך של המכשיר. מצלמות של מחשבים ניידים בדרך כלל מדווחות על כיוון חיישן של 0 או 180 מעלות.

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

לדוגמה, במצלמה הקדמית באיור 1, מאגר התמונות שנוצר על ידי חיישן המצלמה נראה כך:

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

צריך לסובב את התמונה ב-270 מעלות נגד כיוון השעון כדי שהכיוון של התצוגה המקדימה יתאים לכיוון של המכשיר:

חיישן המצלמה בכיוון לאורך כשהתמונה אנכית.
איור 3. מאגר התמונות של המצלמה הקדמית סובב ב-270 מעלות נגד כיוון השעון כדי להתאים לכיוון לאורך של המכשיר.

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

סיבוב המכשיר

סיבוב המכשיר הוא מספר המעלות שבהן המכשיר מסובב מהכיוון הטבעי שלו. לדוגמה, טלפון שמוחזק לרוחב מסובב ב-90 או ב-270 מעלות, בהתאם לכיוון הסיבוב.

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

חישוב הכיוון

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

אפשר לחשב את הסיבוב הכולל של מאגר התמונות של החיישן באמצעות הנוסחה הבאה:

rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360

כאשר sign הוא 1 למצלמות קדמיות, -1 למצלמות אחוריות.

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

הביטוי deviceOrientationDegrees * sign + 360 ממיר את סיבוב המכשיר מנגד כיוון השעון לכיוון השעון במצלמות אחוריות (לדוגמה, המרה של 270 מעלות נגד כיוון השעון ל-90 מעלות בכיוון השעון). פעולת המודולו משנה את קנה המידה של התוצאה לפחות מ-360 מעלות (לדוגמה, שינוי קנה המידה של סיבוב ב-540 מעלות ל-180 מעלות).

ממשקי API שונים מדווחים על סיבוב המכשיר בדרכים שונות:

  • הערך Display#getRotation() מספק את הסיבוב נגד כיוון השעון של המכשיר (מנקודת המבט של המשתמש). הערך הזה מוזן בנוסחה הקודמת כמו שהוא.
  • OrientationEventListener#onOrientationChanged() מחזירה את הסיבוב של המכשיר עם כיוון השעון (מנקודת המבט של המשתמש). הופכים את הערך לשלילי כדי להשתמש בו בנוסחה הקודמת.

מצלמות קדמיות

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

הנה מאגר התמונות שנוצר על ידי חיישן המצלמה באיור 4:

חיישן המצלמה בכיוון לרוחב עם תמונה אנכית.
איור 5. מאגר תמונות שנוצר על ידי חיישן המצלמה הקדמית כשהטלפון מסובב ב-90 מעלות נגד כיוון השעון.

צריך לסובב את המאגר ב-270 מעלות נגד כיוון השעון כדי להתאים לכיוון החיישן (ראו כיוון המצלמה למעלה):

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

לאחר מכן, המאגר מסובב ב-90 מעלות נוספות נגד כיוון השעון כדי להתחשב בסיבוב המכשיר, וכך מתקבל הכיוון הנכון של התצוגה המקדימה של המצלמה באיור 4:

חיישן המצלמה מסובב לרוחב כשהתמונה
            אנכית.
איור 7. מאגר תמונות אחרי סיבוב נוסף של 90 מעלות נגד כיוון השעון לכיוון המכשיר.

כך נראית המצלמה כשהיא מסובבת ימינה לתצוגה לרוחב:

התצוגה המקדימה של המצלמה והחיישן נמצאים שניהם בכיוון לרוחב, אבל החיישן הפוך.
איור 8. תצוגה מקדימה של המצלמה והחיישן כשהטלפון מסובב ב-270 מעלות (או ב-90- מעלות) לרוחב.

הנה מאגר התמונות:

חיישן המצלמה מסובב לרוחב והתמונה הפוכה.
איור 9. מאגר תמונות שנוצר על ידי חיישן המצלמה הקדמית כשהטלפון מסובב ב-270 מעלות נגד כיוון השעון.

צריך לסובב את המאגר ב-270 מעלות נגד כיוון השעון כדי להתאים לכיוון החיישן:

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

לאחר מכן, המאגר מסובב ב-270 מעלות נוספות נגד כיוון השעון כדי להתאים לסיבוב המכשיר:

חיישן המצלמה מסובב לרוחב כשהתמונה
            אנכית.
איור 11. מאגר תמונות אחרי סיבוב נוסף של 270 מעלות נגד כיוון השעון לכיוון המכשיר.

מצלמות אחוריות

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

התצוגה המקדימה של המצלמה והחיישן נמצאים שניהם בכיוון לרוחב, אבל החיישן הפוך.
איור 12. טלפון עם מצלמה אחורית לרוחב (מסובב ב-270 או ב-90- מעלות).

הנה מאגר התמונות מחיישן המצלמה באיור 12:

חיישן המצלמה מסובב לרוחב והתמונה הפוכה.
איור 13. מאגר תמונות שנוצר על ידי חיישן המצלמה האחורית כשהטלפון במצב לרוחב.

צריך לסובב את המאגר ב-90 מעלות בכיוון השעון כדי להתאים אותו לכיוון החיישן:

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

לאחר מכן, המאגר מסובב ב-270 מעלות נגד כיוון השעון כדי להתאים לסיבוב המכשיר:

חיישן המצלמה מסובב לרוחב כשהתמונה
            אנכית.
איור 15. מאגר תמונות אחרי סיבוב של 270 מעלות נגד כיוון השעון לכיוון המכשיר.

יחס גובה-רוחב

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

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

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

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

בתמונה 16, האפליקציה הניחה בטעות שהמכשיר סובב ב-90 מעלות נגד כיוון השעון, ולכן האפליקציה סובבה את התצוגה המקדימה באותה מידה.

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

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

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

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

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

מצב הדגשת דיוקן מוקטן

אפליקציות מצלמה שלא תומכות במצב ריבוי חלונות (resizeableActivity="false") ומגבילות את כיוון המסך (screenOrientation="portrait" או screenOrientation="landscape") יכולות להיות מוצגות במצב לאורך עם הוספת שוליים במכשירים עם מסך גדול, כדי שהתצוגה המקדימה של המצלמה תהיה בכיוון הנכון.

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

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

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

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

סיבוב, חיתוך, שינוי קנה מידה

מצב הדגשת דיוקן מוטמע מופעל באפליקציית מצלמה במצב דיוקן בלבד במסך עם יחס גובה-רוחב לרוחב:

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

האפליקציה מוצגת בפורמט letterbox בכיוון לאורך:

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

תמונת המצלמה מסובבת ב-90 מעלות כדי להתאים לשינוי הכיוון של האפליקציה:

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

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

תמונה חתוכה מהמצלמה, שמותאמת למילוי התצוגה המקדימה של המצלמה.
איור 23. תמונת המצלמה נחתכת ומשתנה כדי למלא את התצוגה המקדימה של המצלמה לאורך.

במכשירים מתקפלים, הכיוון של חיישן המצלמה יכול להיות לאורך, בזמן שיחס הגובה-רוחב של המסך הוא לרוחב:

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

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

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

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

API

החל מ-Android 12 (רמת API ‏31), אפליקציות יכולות גם לשלוט באופן מפורש במצב לאורך עם שוליים באמצעות המאפיין SCALER_ROTATE_AND_CROP של המחלקה CaptureRequest.

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

לא כל המכשירים תומכים בכל הערכים של SCALER_ROTATE_AND_CROP. כדי לקבל רשימה של הערכים הנתמכים, עיינו במאמר CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.

CameraX

ספריית Jetpack CameraX מאפשרת ליצור בקלות תצוגת צילום שמתאימה לכיוון החיישן ולסיבוב המכשיר.

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

כאן מוסבר איך יוצרים תצוגה מקדימה של המצלמה באמצעות PreviewView.

הטמעה מלאה לדוגמה מופיעה במאגר CameraXBasic ב-GitHub.

CameraViewfinder

בדומה לתרחיש לדוגמה של תצוגה מקדימה, הספרייה CameraViewfinder מספקת קבוצת כלים שמאפשרים ליצור תצוגה מקדימה של המצלמה בקלות. היא לא תלויה ב-CameraX Core, כך שאפשר לשלב אותה בצורה חלקה בבסיס הקוד הקיים של Camera2.

במקום להשתמש ב-Surface ישירות, אפשר להשתמש בווידג'ט CameraViewfinder כדי להציג את פיד המצלמה של Camera2.

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

כדי לבקש את המשטח מהאובייקט CameraViewfinder, צריך ליצור ViewfinderSurfaceRequest.

הבקשה הזו מכילה דרישות לגבי רזולוציית המסך ופרטי המצלמה של המכשיר מ-CameraCharacteristics.

קריאה ל-requestSurfaceAsync() שולחת את הבקשה לספק המשטח, שהוא TextureView או SurfaceView, ומקבלת ListenableFuture של Surface.

קריאה ל-markSurfaceSafeToRelease() מודיעה לספק המשטח שהמשטח לא נחוץ ושאפשר לשחרר משאבים קשורים.

fun startCamera() {
    val previewResolution = Size(width, height)
    val viewfinderSurfaceRequest =
        ViewfinderSurfaceRequest(previewResolution, characteristics)
    val surfaceListenableFuture =
        cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)

    Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
        override fun onSuccess(surface: Surface) {
            /* create a CaptureSession using this surface as usual */
        }
        override fun onFailure(t: Throwable) { /* something went wrong */ }
    }, ContextCompat.getMainExecutor(context))
}

SurfaceView

SurfaceView היא גישה פשוטה ליצירת תצוגה מקדימה של המצלמה אם התצוגה המקדימה לא דורשת עיבוד ואינה מונפשת.

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

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

(קוד המקור של computeRelativeRotation() נמצא בקטע סיבוב יחסי בהמשך).

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = MeasureSpec.getSize(widthMeasureSpec)
    val height = MeasureSpec.getSize(heightMeasureSpec)

    val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees)

    if (previewWidth > 0f && previewHeight > 0f) {
        /* Scale factor required to scale the preview to its original size on the x-axis. */
        val scaleX =
            if (relativeRotation % 180 == 0) {
                width.toFloat() / previewWidth
            } else {
                width.toFloat() / previewHeight
            }
        /* Scale factor required to scale the preview to its original size on the y-axis. */
        val scaleY =
            if (relativeRotation % 180 == 0) {
                height.toFloat() / previewHeight
            } else {
                height.toFloat() / previewWidth
            }

        /* Scale factor required to fit the preview to the SurfaceView size. */
        val finalScale = min(scaleX, scaleY)

        setScaleX(1 / scaleX * finalScale)
        setScaleY(1 / scaleY * finalScale)
    }
    setMeasuredDimension(width, height)
}

לפרטים נוספים על הטמעת SurfaceView כתצוגה מקדימה של המצלמה, ראו כיווני מצלמה.

TextureView

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

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

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

סיבוב יחסי

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

רכיבים כמו SurfaceView ו-TextureView משתמשים בסיבוב יחסי כדי לקבוע את הגורמים לקביעת קנה מידה של x ו-y לתמונה בתצוגה המקדימה. הוא משמש גם כדי לציין את הסיבוב של מאגר התמונות של החיישן.

המחלקות CameraCharacteristics ו-Surface מאפשרות לחשב את הסיבוב היחסי של חיישן המצלמה:

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public fun computeRelativeRotation(
    characteristics: CameraCharacteristics,
    surfaceRotationDegrees: Int
): Int {
    val sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0

    // Reverse device orientation for back-facing cameras.
    val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT
    ) 1 else -1

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
}

מדדי חלונות זמן

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

WindowManager#getCurrentWindowMetrics() (נוסף ב-API ברמה 30) מחזירה את הגודל של חלון האפליקציה במקום את גודל המסך. השיטות של ספריית Jetpack WindowManager,‏ WindowMetricsCalculator#computeCurrentWindowMetrics() ו-WindowInfoTracker#currentWindowMetrics(), מספקות תמיכה דומה עם תאימות לדורות קודמים לרמת API‏ 14.

סיבוב של 180 מעלות

סיבוב של 180 מעלות של המכשיר (לדוגמה, מכיוון התצוגה הטבעי לכיוון התצוגה הטבעי כשהוא הפוך) לא מפעיל את הקריאה החוזרת (callback) של onConfigurationChanged(). כתוצאה מכך, יכול להיות שהתצוגה המקדימה של המצלמה תהיה הפוכה.

כדי לזהות סיבוב של 180 מעלות, מטמיעים DisplayListener ובודקים את סיבוב המכשיר באמצעות קריאה ל-Display#getRotation() בקריאה החוזרת (callback) של onDisplayChanged().

משאבים בלעדיים

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

ב-Android 10 (רמת API‏ 29) נוספה פעילות מקבילה, שבה כל הפעילויות שמוצגות נמצאות במצב RESUMED. פעילויות גלויות עדיין יכולות להיכנס למצב PAUSED אם, לדוגמה, פעילות שקופה נמצאת מעל הפעילות או אם לא ניתן להתמקד בפעילות, כמו במצב תמונה בתוך תמונה (ראו תמיכה בתמונה בתוך תמונה).

אפליקציה שמשתמשת במצלמה, במיקרופון או בכל משאב בלעדי או יחיד ברמת API‏ 29 ומעלה חייבת לתמוך בפעילות מקבילה. לדוגמה, אם שלוש פעילויות שהופעלו מחדש רוצות להשתמש במצלמה, רק אחת מהן יכולה לגשת למשאב הבלעדי הזה. כל פעילות צריכה להטמיע קריאה חוזרת (callback) מסוג onDisconnected() כדי להישאר מודעת לגישה מונעת למצלמה על ידי פעילות בעדיפות גבוהה יותר.

מידע נוסף מופיע במאמר פעילות מקבילה.

מקורות מידע נוספים