מסכים גדולים ולא מתקפלים ומצבים מקופלים ייחודיים מאפשרים להפעיל חוויות משתמש חדשות מכשירים מתקפלים. כדי שהאפליקציה תהיה מודעת למצב המתקפל, אפשר להשתמש בספריית Jetpack WindowManager, שמספקת ממשק API לתכונות של חלונות במכשירים מתקפלים, כמו קיפולים ומפרקים. כשהאפליקציה במצב מקופל, היא יכולה לשנות את הפריסה שלה כדי לא למקם תוכן חשוב באזור של קיפולים או צירים ולהשתמש בקפלים וצירים כמפרידים טבעיים.
הבנת התצורות שבהן המכשיר תומך, כמו מצב שולחני או מצב ספר, יכולה לעזור לכם להחליט אם לתמוך בפריסות שונות או לספק תכונות ספציפיות.
פרטי החלון
הממשק WindowInfoTracker
ב-Jetpack WindowManager חושף מידע על הפריסה של החלון. ה-method windowLayoutInfo()
בממשק מחזירה
מקור נתונים של WindowLayoutInfo
שמיידע את האפליקציה לגבי מכשיר מתקפל
במצב הקפל של המכשיר. השיטה WindowInfoTracker#getOrCreate()
יוצרת מופע של WindowInfoTracker
.
WindowManager מספק תמיכה באיסוף נתוני WindowLayoutInfo
באמצעות תהליכים של Kotlin וקריאות חזרה (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.
}
}
}
}
}
קריאה חוזרת (callback) ב-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
),
אפשר להשתמש בפריטי מידע שנוצרו בתהליך פיתוח (Artifact) שמאפשרים להשתמש
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
תמיד יהיה True במכשיר עם שני מסכים כשהאפליקציה נמצאת בשני המסכים.
הנכס 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 Codelab: איך להטמיע יציבה שולחנית באפליקציות צילום. הצגה העינית בחצי העליון של המסך (בחלק העליון והקבוע) שליטה בחצי התחתון (בחלק התחתון לקיפול).
מצב הספר
תכונה ייחודית נוספת של המכשיר המתקפל היא מצב ספר, שבו המכשיר פתוח למחצה והציר אנכי. המעמד של הספר הוא מעולה לקריאת ספרים דיגיטליים. ב- פריסת שני עמודים במסך גדול מתקפל, פתוח כמו ספר ככריכה, ספר שמתארת את החוויה של קריאה ספר אמיתי.
אפשר להשתמש בו גם לצילום תמונות אם רוצים לצלם ביחס גובה-רוחב שונה בלי להשתמש בידיים.
מטמיעים את תנוחת הספר באמצעות אותן שיטות שמשמשות לתנוחת השולחן. ההבדל היחיד הוא שהקוד צריך לבדוק שהכיוון של תכונת הקיפול הוא אנכי במקום אופקי:
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, ה-WindowManager WindowMetrics
מספק את גבולות החלון, אבל ה-API תואם לאחור עד לרמת API 14.
מידע נוסף זמין בקטע שימוש בסיווגים של גדלים של חלונות.
מקורות מידע נוספים
דוגמיות
- WindowManager ב-Jetpack: דוגמה לשימוש בספריית WindowManager של Jetpack
- Jetcaster: הטמעת מצב שולחני באמצעות Compose
שיעורי Lab
- תמיכה במכשירים מתקפלים ובמכשירים עם מסך כפול עם Jetpack windowManager
- אופטימיזציה של אפליקציית המצלמה במכשירים מתקפלים באמצעות Jetpack WindowManager