نمایشگرهای بزرگ باز نشده و حالتهای تاشدگی منحصر به فرد، تجربیات کاربری جدیدی را در دستگاههای تاشو فراهم میکنند. برای اینکه برنامه شما از قابلیت تا شدن آگاه باشد، از کتابخانه Jetpack WindowManager استفاده کنید که یک سطح API برای ویژگیهای پنجره دستگاههای تاشو مانند تاها و لولاها فراهم میکند. وقتی برنامه شما از قابلیت تا شدن آگاه است، میتواند طرحبندی خود را طوری تنظیم کند که از قرار دادن محتوای مهم در ناحیه تاها یا لولاها جلوگیری کند و از تاها و لولاها به عنوان جداکنندههای طبیعی استفاده کند.
درک اینکه آیا یک دستگاه از پیکربندیهایی مانند حالت رومیزی یا کتابی پشتیبانی میکند یا خیر، میتواند به تصمیمگیری در مورد پشتیبانی از چیدمانهای مختلف یا ارائه ویژگیهای خاص کمک کند.
اطلاعات پنجره
رابط WindowInfoTracker
در Jetpack WindowManager اطلاعات طرحبندی پنجره را نمایش میدهد. متد windowLayoutInfo()
رابط، جریانی از دادههای WindowLayoutInfo
را برمیگرداند که برنامه شما را در مورد وضعیت تا شدن یک دستگاه تاشو مطلع میکند. متد WindowInfoTracker#getOrCreate()
یک نمونه از WindowInfoTracker
ایجاد میکند.
WindowManager از جمعآوری دادههای WindowLayoutInfo
با استفاده از جریانهای کاتلین و فراخوانیهای جاوا پشتیبانی میکند.
جریانهای کاتلین
برای شروع و توقف جمعآوری دادههای 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.
}
}
}
}
}
فراخوانیهای جاوا
لایه سازگاری فراخوانی برگشتی که در وابستگی androidx.window:window-java
قرار دارد، شما را قادر میسازد تا بهروزرسانیهای WindowLayoutInfo
را بدون استفاده از جریان Kotlin جمعآوری کنید. این مصنوع شامل کلاس WindowInfoTrackerCallbackAdapter
است که یک WindowInfoTracker
برای پشتیبانی از ثبت (و لغو ثبت) فراخوانیهای برگشتی برای دریافت بهروزرسانیهای 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
) استفاده میکنید، میتوانید از مزایایی بهرهمند شوید که به شما امکان میدهند بدون استفاده از جریان کاتلین، از Observable
یا Flowable
برای جمعآوری بهروزرسانیهای WindowLayoutInfo
استفاده کنید.
لایه سازگاری ارائه شده توسط وابستگیهای 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
: آیا تاخوردگی یا لولا دو ناحیه نمایش منطقی ایجاد میکند، درست یا غلط
یک دستگاه تاشو که HALF_OPENED
است، همیشه isSeparating
درست گزارش میدهد زیرا صفحه نمایش به دو قسمت نمایش تقسیم شده است. همچنین، isSeparating
همیشه در یک دستگاه دو صفحهای درست است، زمانی که برنامه هر دو صفحه را پوشش میدهد.
ویژگی FoldingFeature
bounds
(که از DisplayFeature
به ارث رسیده است) مستطیل مرزی یک ویژگی تاشو مانند تا یا لولا را نشان میدهد. از این bounds میتوان برای قرار دادن عناصر روی صفحه نسبت به آن ویژگی استفاده کرد:
کاتلین
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.
}
}
}
}
جاوا
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
برای تعیین اینکه آیا دستگاه در حالت رومیزی قرار دارد یا خیر، استفاده کنید:
کاتلین
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
contract { returns(true) implies (foldFeature != null) }
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}
جاوا
boolean isTableTopPosture(FoldingFeature foldFeature) {
return (foldFeature != null) &&
(foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
(foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}
وقتی فهمیدید که دستگاه در حالت رومیزی قرار دارد، طرحبندی برنامه خود را متناسب با آن بهروزرسانی کنید. برای برنامههای رسانهای، این معمولاً به معنای قرار دادن پخشکننده در بالای صفحه تا شده و قرار دادن کنترلها و محتوای تکمیلی درست بعد از آن برای تجربه مشاهده یا گوش دادن بدون دخالت دست است.
در اندروید ۱۵ (سطح API 35) و بالاتر، میتوانید یک API همگام را فراخوانی کنید تا تشخیص دهد که آیا یک دستگاه از حالت قرارگیری روی میز پشتیبانی میکند یا خیر، صرف نظر از وضعیت فعلی دستگاه.
این API فهرستی از حالتهای بدنی پشتیبانیشده توسط دستگاه را ارائه میدهد. اگر این فهرست شامل حالتهای بدنی روی میز باشد، میتوانید طرحبندی برنامه خود را برای پشتیبانی از این حالتها تقسیم کنید و تستهای A/B را روی رابط کاربری برنامه خود برای حالتهای بدنی روی میز و تمام صفحه اجرا کنید.
کاتلین
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
if (postures.contains(TABLE_TOP)) {
// Device supports tabletop posture.
}
}
جاوا
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
if (postures.contains(SupportedPosture.TABLETOP)) {
// Device supports tabletop posture.
}
}
مثالها
برنامه
MediaPlayerActivity
: نحوه استفاده از Media3 Exoplayer و WindowManager را برای ایجاد یک پخش کننده ویدیوی fold-aware ببینید.بهینهسازی برنامه دوربین در دستگاههای تاشو با Jetpack WindowManager codelab: یاد بگیرید که چگونه حالت رومیزی را برای برنامههای عکاسی پیادهسازی کنید. منظرهیاب را در نیمه بالایی صفحه (بالای تاشدگی) و کنترلها را در نیمه پایینی (پایین تاشدگی) نشان دهید.
حالت کتاب
یکی دیگر از ویژگیهای منحصر به فرد تاشو، حالت کتاب است که در آن دستگاه نیمه باز است و لولا عمودی است. حالت کتاب برای خواندن کتابهای الکترونیکی عالی است. با طرحبندی دو صفحهای روی یک صفحه نمایش بزرگ که مانند یک کتاب صحافی شده باز میشود، حالت کتاب تجربه خواندن یک کتاب واقعی را به تصویر میکشد.
همچنین اگر میخواهید هنگام عکاسی بدون دخالت دست، نسبت ابعاد متفاوتی را ثبت کنید، میتوانید از آن برای عکاسی استفاده کنید.
حالت کتاب را با همان تکنیکهایی که برای حالت رومیزی استفاده شده است، پیادهسازی کنید. تنها تفاوت این است که کد باید بررسی کند که جهت ویژگی تاشو عمودی باشد نه افقی:
کاتلین
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
contract { returns(true) implies (foldFeature != null) }
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}
جاوا
boolean isBookPosture(FoldingFeature foldFeature) {
return (foldFeature != null) &&
(foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
(foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}
تغییر اندازه پنجره
ناحیه نمایش یک برنامه میتواند در نتیجه تغییر پیکربندی دستگاه تغییر کند، برای مثال، وقتی دستگاه تا یا باز میشود، میچرخد یا اندازه یک پنجره در حالت چند پنجرهای تغییر میکند.
کلاس Jetpack WindowManager WindowMetricsCalculator
شما را قادر میسازد تا معیارهای فعلی و حداکثری پنجره را بازیابی کنید. مانند پلتفرم WindowMetrics
که در API سطح 30 معرفی شد، WindowManager WindowMetrics
محدودههای پنجره را ارائه میدهد، اما این API تا سطح API سطح 14 با نسخههای قبلی سازگار است.
به بخش «استفاده از کلاسهای اندازه پنجره» مراجعه کنید.
منابع اضافی
نمونهها
- Jetpack WindowManager : مثالی از نحوه استفاده از کتابخانه Jetpack WindowManager
- Jetcaster : پیادهسازی حالت قرارگیری میز با Compose
کدلبز
- پشتیبانی از دستگاههای تاشو و دو صفحه نمایش با Jetpack WindowManager
- با Jetpack WindowManager برنامه دوربین خود را در دستگاههای تاشو بهینه کنید