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

הערה: הדף הזה מתייחס לחבילה 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 לאורך:

האפליקציה סובבה לאורך ולפורמט 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 יוצר תצוגה מקדימה של המצלמה, שמתכוונן באופן אוטומטי לכיוון החיישן סיבוב מכשירים והתאמה לעומס (scaling). PreviewView שומר על יחס הגובה-רוחב של בתמונה מהמצלמה על ידי החלת FILL_CENTER סוג קנה מידה, שמרכז את התמונה, אבל עשוי לחתוך אותה כדי להתאים למידות של PreviewView. כדי לסמן את תמונת המצלמה בפורמט letterbox, יש להגדיר את סוג קנה המידה כ- FIT_CENTER

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

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

עינית המצלמה

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

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

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

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

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

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

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

Kotlin


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))
}

Java


    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));
    }

תצוגת Surface

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

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

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

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

Kotlin

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)
}

Java

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

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

רוטציה יחסית

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

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

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

Kotlin

/**
 * 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
}

Java

/**
 * 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) מחזירה את הגודל של חלון האפליקציה במקום גודל המסך. השיטות של ספריית Jetpack windowManager WindowMetricsCalculator#computeCurrentWindowMetrics() וגם WindowInfoTracker#currentWindowMetrics() מספקים תמיכה דומה עם תאימות לאחור לרמת API 14.

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

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

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

מקורות מידע בלעדיים

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

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

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

מידע נוסף זמין במאמר הבא: ריבוי קורות חיים.

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