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

הערה: הדף הזה מתייחס לחבילה 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, מאגר הנתונים הזמני של התמונה שמופק על ידי חיישן המצלמה נראה כך:

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

חובה לסובב את התמונה ב-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() מחזירה את סיבוב המכשיר בכיוון השעון (מנקודת המבט של המשתמש). מבטלים את הערך לשימוש בנוסחה שלמעלה.

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

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

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

חיישן המצלמה בכיוון לרוחב עם תמונה אנכית.

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

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

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

חיישן המצלמה סובב לרוחב עם תמונה
            זקוף.

כאן המצלמה מוטה ימינה לכיוון לרוחב:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

האפליקציה סובבה לאורך ולפורמט letterbox. התמונה מוטה, מצד שמאל למעלה.

תמונת המצלמה מסובבת ב-90 מעלות כדי לכוונן את הכיוון מחדש של app:

התמונה מהחיישן סובבה ב-90 מעלות כדי שהיא תהיה זקופה.

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

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

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

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

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

כדי להציג את האפליקציה ואת התצוגה המקדימה של המצלמה בכיוון הנכון, צריך להציג את האפליקציה בפורמט letterbox בכיוון לאורך במצב 'הוספה בתוך מסגרת לאורך':

אפליקציה בפורמט letterbox לאורך עם תצוגה מקדימה של המצלמה
            במכשיר מתקפל.

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.

מצלמהX

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

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

אפשר לקרוא מידע בסיסי על יצירת תצוגה מקדימה של המצלמה באמצעות PreviewView: מטמיעים תצוגה מקדימה.

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

עינית המצלמה

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

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

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

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

הבקשה הזו כוללת דרישות לגבי רזולוציית המשטח ומכשיר המצלמה מידע מ-CameraCharacteristics.

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

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

KotlinJava
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))
}
    void startCamera(){
        Size previewResolution = new Size(width, height);
        ViewfinderSurfaceRequest viewfinderSurfaceRequest =
                new ViewfinderSurfaceRequest(previewResolution, characteristics);
        ListenableFuture<Surface> surfaceListenableFuture =
                cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest);

        Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() {
            @Override
            public void onSuccess(Surface result) {
                /* create a CaptureSession using this surface as usual */
            }
            @Override public void onFailure(Throwable t) { /* something went wrong */}
        },  ContextCompat.getMainExecutor(context));
    }

SurfaceView

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

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

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

(קוד המקור computeRelativeRotation() הוא סבב יחסי למטה.)

KotlinJava
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)
}
@Override
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees);

    if (previewWidth > 0f && previewHeight > 0f) {

        /* Scale factor required to scale the preview to its original size on the x-axis. */
        float scaleX = (relativeRotation % 180 == 0)
                       ? (float) width / previewWidth
                       : (float) width / previewHeight;

        /* Scale factor required to scale the preview to its original size on the y-axis. */
        float scaleY = (relativeRotation % 180 == 0)
                       ? (float) height / previewHeight
                       : (float) height / previewWidth;

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

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

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

תצוגת מרקם

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

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

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

רוטציה יחסית

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

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

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

KotlinJava
/**
 * 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)!!

    // 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
}
/**
 * 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 int computeRelativeRotation(
    CameraCharacteristics characteristics,
    int surfaceRotationDegrees
){
    Integer sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // Reverse device orientation for back-facing cameras.
    int sign = characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT ? 1 : -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) מחזירה את הגודל של חלון האפליקציה במקום גודל המסך. השיטות של ספריית WindowManager ב-Jetpack‏ WindowMetricsCalculator#computeCurrentWindowMetrics() ו-WindowInfoTracker#currentWindowMetrics() מספקות תמיכה דומה עם תאימות לאחור לגרסה 14 של API.

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

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

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

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

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

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

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

מידע נוסף זמין במאמר הפעלה מחדש בכמה מכשירים.

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