מסכים גדולים במצב פתוח ומצבים ייחודיים במצב מקופל מאפשרים חוויית משתמש חדשה במכשירים מתקפלים. כדי שהאפליקציה תהיה מודעת למצב המתקפל, אפשר להשתמש בספריית Jetpack WindowManager, שמספקת ממשק API לתכונות של חלונות במכשירים מתקפלים, כמו קיפולים ומפרקים. כשהאפליקציה מותאמת למכשירים מתקפלים, היא יכולה להתאים את הפריסה שלה כדי להימנע מהצגת תוכן חשוב באזורי הקיפול או הצירים, ולהשתמש בקיפול ובצירים כמפרידים טבעיים.
כדי להבין אם המכשיר תומך בהגדרות כמו שולחני או במצב ספר, תוכלו לקבל החלטות בנוגע לתמיכה בפריסות שונות או לספק תכונות ספציפיות.
פרטי החלון
הממשק WindowInfoTracker
ב-Jetpack WindowManager חושף מידע על הפריסה של החלון. ה-method windowLayoutInfo()
בממשק מחזירה זרם של נתונים WindowLayoutInfo
שמיידעים את האפליקציה על מצב קיפול של מכשיר מתקפל. השיטה WindowInfoTracker#getOrCreate()
יוצרת מכונה של WindowInfoTracker
.
windowManager תומך באיסוף נתוני WindowLayoutInfo
באמצעות תהליכי Kootlin ו-callbacks ב-Java.
תהליכים ב-Kotlin
כדי להתחיל ולהפסיק את איסוף הנתונים של WindowLayoutInfo
, אפשר להשתמש בקורוטין בהתאם למחזור החיים שאפשר להפעיל מחדש, שבו מופעל בלוק הקוד repeatOnLifecycle
כשמחזור החיים הוא STARTED
לפחות ומופסק כשמחזור החיים הוא STOPPED
. כשמחזור החיים חוזר להיות STARTED
, הביצוע של בלוק הקוד מתחיל מחדש באופן אוטומטי. בדוגמה הבאה, בלוק הקוד אוסף נתוני WindowLayoutInfo
ומשתמש בהם:
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
.windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}
קריאות חזרה (callbacks) ב-Java
שכבת התאימות לקריאה חוזרת (callback) שכלולה בתלות androidx.window:window-java
מאפשרת לאסוף עדכוני WindowLayoutInfo
בלי להשתמש בתהליך של Kotlin. הארטיפקט כולל את המחלקה WindowInfoTrackerCallbackAdapter
, שמתאימה את WindowInfoTracker
כדי לתמוך ברישום (ובביטול הרישום) של קריאות חזרה (callbacks) לקבלת עדכוני WindowLayoutInfo
, לדוגמה:
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private ActivitySplitLayoutBinding binding;
private final LayoutStateChangeCallback layoutStateChangeCallback =
new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
windowInfoTracker =
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoTracker.addWindowLayoutInfoListener(
this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoTracker
.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
public void accept(WindowLayoutInfo newLayoutInfo) {
SplitLayoutActivity.this.runOnUiThread( () -> {
// Use newLayoutInfo to update the layout.
});
}
}
}
תמיכה ב-RxJava
אם אתם כבר משתמשים ב-RxJava
(גרסה 2
או 3
), תוכלו להשתמש ב-artifacts שמאפשרים להשתמש ב-Observable
או ב-Flowable
כדי לאסוף עדכונים של WindowLayoutInfo
בלי להשתמש בתהליך של Kotlin.
שכבת התאימות שסיפקנו באמצעות יחסי התלות androidx.window:window-rxjava2
ו-androidx.window:window-rxjava3
כוללת את השיטות WindowInfoTracker#windowLayoutInfoFlowable()
ו-WindowInfoTracker#windowLayoutInfoObservable()
, שמאפשרות לאפליקציה לקבל עדכוני WindowLayoutInfo
. לדוגמה:
class RxActivity: AppCompatActivity {
private lateinit var binding: ActivityRxBinding
private var disposable: Disposable? = null
private lateinit var observable: Observable<WindowLayoutInfo>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Create a new observable.
observable = WindowInfoTracker.getOrCreate(this@RxActivity)
.windowLayoutInfoObservable(this@RxActivity)
}
@Override
protected void onStart() {
super.onStart();
// Subscribe to receive WindowLayoutInfo updates.
disposable?.dispose()
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
@Override
protected void onStop() {
super.onStop();
// Dispose of the WindowLayoutInfo observable.
disposable?.dispose()
}
}
תכונות של מסכים מתקפלים
הכיתה WindowLayoutInfo
של Jetpack WindowManager מאפשרת להציג את התכונות של חלון תצוגה כרשימה של רכיבי DisplayFeature
.
FoldingFeature
הוא סוג של DisplayFeature
שמספק מידע על מסכים מתקפלים, כולל הפרטים הבאים:
state
: המצב המקופל של המכשיר,FLAT
אוHALF_OPENED
orientation
: הכיוון של הקיפול או הציר,HORIZONTAL
אוVERTICAL
occlusionType
: אם הקיפול או הציר מסתירים חלק מהמסך,NONE
אוFULL
isSeparating
: אם הצירוף או הציר יוצרים שתי אזורי תצוגה לוגיים, true או false
במכשיר מתקפל שהסטטוס שלו הוא HALF_OPENED
, הערך של isSeparating
תמיד יהיה true, כי המסך מחולק לשני אזורי תצוגה. בנוסף, isSeparating
תמיד מתקיים במכשיר עם שני מסכים כשהאפליקציה מתפרסת על שני המסכים.
הנכס FoldingFeature
bounds
(שעבר בירושה מ-DisplayFeature
) מייצג את המלבן המקיף של תכונת קיפול, כמו קיפול או ציר.
אפשר להשתמש בגבולות כדי למקם רכיבים במסך ביחס לתכונה:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from WindowInfoTracker when the lifecycle is // STARTED and stops collection when the lifecycle is STOPPED. WindowInfoTracker.getOrCreate(this@MainActivity) .windowLayoutInfo(this@MainActivity) .collect { layoutInfo -> // New posture information. val foldingFeature = layoutInfo.displayFeatures .filterIsInstance<FoldingFeature>() .firstOrNull() // Use information from the foldingFeature object. } } } }
Java
private WindowInfoTrackerCallbackAdapter windowInfoTracker; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... windowInfoTracker = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this)); } @Override protected void onStart() { super.onStart(); windowInfoTracker.addWindowLayoutInfoListener( this, Runnable::run, layoutStateChangeCallback); } @Override protected void onStop() { super.onStop(); windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Use newLayoutInfo to update the Layout. List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures(); for (DisplayFeature feature : displayFeatures) { if (feature instanceof FoldingFeature) { // Use information from the feature object. } } } }
מצב שולחני
בעזרת המידע שכלול באובייקט FoldingFeature
, האפליקציה יכולה לתמוך במצבים כמו שולחניות, שבה הטלפון מונח על משטח, הציר נמצא במצב אופקי והמסך המתקפל פתוח למחצה.
המצב השולחני מאפשר למשתמשים להשתמש בטלפון בנוחות בלי להחזיק אותו בידיים. מצב 'על משטח, מסך למעלה' הוא פתרון מעולה לצפייה במדיה, לצילום תמונות ולשיחות וידאו.
משתמשים ב-FoldingFeature.State
וב-FoldingFeature.Orientation
כדי לקבוע אם המכשיר נמצא במצב שולחני:
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Java
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
אחרי שמבינים שהמכשיר נמצא במצב שולחני, מעדכנים את הפריסה של האפליקציה בהתאם. באפליקציות מדיה, בדרך כלל המשמעות היא מיקום ההפעלה מעל הציר, והצבת אמצעי הבקרה והתוכן המשלים מתחתיו, כדי לאפשר צפייה או האזנה ללא ידיים.
ב-Android 15 ואילך (רמת API 35 ואילך), אפשר להפעיל ממשק API סינכרוני כדי לזהות אם המכשיר תומך במצב שולחני, ללא קשר למצב הנוכחי של המכשיר.
ה-API מציג רשימה של מצבים שבהם המכשיר תומך. אם הרשימה מכילה את המיקום 'שולחן', תוכלו לפצל את פריסת האפליקציה כך שתתמוך במיקום הזה ולהריץ בדיקות A/B בממשק המשתמש של האפליקציה לפריסת שולחן ולפריסת מסך מלא.
Kotlin
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(TABLE_TOP)) { // Device supports tabletop posture. } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures(); if (postures.contains(SupportedPosture.TABLETOP)) { // Device supports tabletop posture. } }
דוגמאות
אפליקציית
MediaPlayerActivity
: כאן מוסבר איך משתמשים ב-Media3Exoplayer וב-WindowManager כדי ליצור נגן וידאו שמתאים למכשירים מתקפלים.אופטימיזציה של אפליקציית המצלמה במכשירים מתקפלים באמצעות Jetpack WindowManager: קודינג לאפליקציות צילום: איך מטמיעים מצב שולחני. הציגו את העינית בחצי העליון של המסך (בחלק העליון והקבוע) ואת הפקדים בחלק התחתון (בחלק התחתון לקיפול).
מצב הספר
עוד תכונה מתקפלת ייחודית היא מצב הספר שבו המכשיר פתוח למחצה והציר אנכי. תנוחת הקריאה בספרים מתאימה מאוד לקריאת ספרים דיגיטליים. בפריסת שני דפים במסך גדול שניתן לקפל כמו ספר כרוך, המצב הזה משחזר את חוויית הקריאה בספר אמיתי.
אפשר להשתמש בו גם לצילום אם רוצים לצלם תמונות ביחס גובה-רוחב שונה ללא מגע יד.
מטמיעים את תנוחת הספר באמצעות אותן שיטות שמשמשות לתנוחת השולחן. ההבדל היחיד הוא שהקוד צריך לבדוק שהכיוון של תכונת הקיפול הוא אנכי במקום אופקי:
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
Java
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
שינויים בגודל החלון
אזור התצוגה של האפליקציה יכול להשתנות כתוצאה משינוי של הגדרת המכשיר, לדוגמה, כשהמכשיר מקופל או לא מקופל, כשמסובבים אותו או כשגודל של חלון משתנה במצב של ריבוי חלונות.
בעזרת הכיתה WindowManager של Jetpack WindowMetricsCalculator
אפשר לאחזר את מדדי החלון הנוכחי והמקסימלי. בדומה לפלטפורמה WindowMetrics
שהוצגה ברמת API 30, ה-WindowManagerWindowMetrics
מספק את גבולות החלון, אבל ה-API תואם לאחור עד לרמת API 14.
מידע נוסף זמין בקטע שימוש בסיווגים של גדלים של חלונות.
מקורות מידע נוספים
דוגמיות
- WindowManager ב-Jetpack: דוגמה לשימוש בספריית WindowManager של Jetpack
- Jetcaster : הטמעת מצב 'על משטח, מסך למעלה' באמצעות 'כתיבה'
Codelabs
- תמיכה במכשירים מתקפלים ובמכשירים עם מסך כפול עם Jetpack windowManager
- אופטימיזציה של אפליקציית המצלמה במכשירים מתקפלים עם Jetpack windowManager