จอแสดงผลขนาดใหญ่ที่กางออกและสถานะการพับที่ไม่เหมือนใครช่วยให้ผู้ใช้ได้รับประสบการณ์ใหม่ๆ บน อุปกรณ์แบบพับได้ หากต้องการให้แอปของคุณรองรับการพับ ให้ใช้ไลบรารี Jetpack WindowManager ซึ่งมีแพลตฟอร์ม API สำหรับฟีเจอร์หน้าต่างของอุปกรณ์พับได้ เช่น รอยพับและบานพับ เมื่อแอปของคุณรับรู้ถึงการพับได้ แอปจะปรับเลย์เอาต์ เพื่อหลีกเลี่ยงการวางเนื้อหาสำคัญในบริเวณรอยพับหรือบานพับ และใช้รอยพับ และบานพับเป็นตัวคั่นตามธรรมชาติ
การทำความเข้าใจว่าอุปกรณ์รองรับการกำหนดค่าต่างๆ เช่น ท่าทางบนโต๊ะหรือท่าทางแบบหนังสือ หรือไม่ จะช่วยในการตัดสินใจเกี่ยวกับการรองรับเลย์เอาต์ต่างๆ หรือการจัดหา ฟีเจอร์ที่เฉพาะเจาะจง
ข้อมูลหน้าต่าง
อินเทอร์เฟซ WindowInfoTracker
ใน Jetpack WindowManager จะแสดงข้อมูลเลย์เอาต์ของหน้าต่าง
เมธอด windowLayoutInfo()
ของอินเทอร์เฟซจะแสดงสตรีมข้อมูล WindowLayoutInfo
ที่แจ้งให้แอปทราบเกี่ยวกับสถานะการพับของอุปกรณ์แบบพับได้
เมธอด WindowInfoTracker#getOrCreate()
จะสร้างอินสแตนซ์ของ WindowInfoTracker
WindowManager รองรับการรวบรวมWindowLayoutInfo
ข้อมูลโดยใช้
โฟลว์ Kotlin และการเรียกกลับของ 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 ที่รวมอยู่ใน Dependency ของ
androidx.window:window-java
ช่วยให้คุณรวบรวมข้อมูลอัปเดตของ
WindowLayoutInfo
ได้โดยไม่ต้องใช้ Kotlin Flow อาร์ติแฟกต์ประกอบด้วยคลาส 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
ได้โดยไม่ต้องใช้ Kotlin Flow
เลเยอร์ความเข้ากันได้ที่จัดทำโดยการขึ้นต่อกันของ 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
: ไม่ว่ารอยพับหรือบานพับจะสร้างพื้นที่แสดงผลเชิงตรรกะ 2 พื้นที่หรือไม่ จริงหรือเท็จ
อุปกรณ์แบบพับได้ที่HALF_OPENED
จะรายงานisSeparating
เป็นจริงเสมอ
เนื่องจากหน้าจอแยกออกเป็น 2 พื้นที่แสดงผล นอกจากนี้ isSeparating
จะเป็น
จริงเสมอในอุปกรณ์แบบ 2 หน้าจอเมื่อแอปพลิเคชันครอบคลุมทั้ง 2
หน้าจอ
พร็อพเพอร์ตี้ 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 ใน UI ของแอปสำหรับเลย์เอาต์แบบวางบนโต๊ะและแบบเต็มหน้าจอได้
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
: ดูวิธีใช้ Media3 ExoPlayer และ WindowManager เพื่อสร้างวิดีโอที่รองรับการพับเพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์พับได้ด้วย Jetpack WindowManager Codelab: ดูวิธีใช้ท่าตั้งโต๊ะสำหรับแอปถ่ายภาพ แสดง ช่องมองภาพที่ครึ่งบนของหน้าจอ (เหนือรอยพับ) และ การควบคุมที่ครึ่งล่าง (ใต้รอยพับ)
ลักษณะการทำงานของอุปกรณ์
อีกหนึ่งฟีเจอร์เฉพาะตัวของอุปกรณ์พับได้คือท่าหนังสือ ซึ่งอุปกรณ์จะกางออกครึ่งหนึ่ง และบานพับจะอยู่ในแนวตั้ง ท่าทางอ่านหนังสือเหมาะสำหรับการอ่าน eBook เมื่อเปิดอุปกรณ์พับที่มีหน้าจอขนาดใหญ่ในเลย์เอาต์แบบ 2 หน้าเหมือนหนังสือที่มีการเย็บเล่ม ท่าทางของหนังสือจะจำลองประสบการณ์การอ่านหนังสือจริง
นอกจากนี้ยังใช้ในการถ่ายภาพได้หากต้องการถ่ายภาพที่มีสัดส่วนภาพแตกต่างกัน ขณะถ่ายภาพแบบแฮนด์ฟรี
ใช้ท่าทางแบบหนังสือด้วยเทคนิคเดียวกับที่ใช้สำหรับท่าทางแบบวางบนโต๊ะ ความแตกต่างเพียงอย่างเดียวคือโค้ดควรตรวจสอบว่าการวางแนวฟีเจอร์การพับเป็น แนวตั้งแทนที่จะเป็นแนวนอน
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);
}
การเปลี่ยนแปลงขนาดหน้าต่าง
พื้นที่แสดงผลของแอปอาจเปลี่ยนแปลงได้อันเป็นผลมาจากการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์ เช่น เมื่ออุปกรณ์พับหรือกางออก หมุน หรือเมื่อมีการปรับขนาดหน้าต่าง ในโหมดหลายหน้าต่าง
คลาส Jetpack WindowManager WindowMetricsCalculator
ช่วยให้คุณ
เรียกข้อมูลเมตริกหน้าต่างปัจจุบันและสูงสุดได้ เช่นเดียวกับแพลตฟอร์ม WindowMetrics
ที่เปิดตัวใน API ระดับ 30 WindowManager
WindowMetrics
จะระบุขอบเขตของหน้าต่าง แต่ API จะเข้ากันได้แบบย้อนหลัง
จนถึง API ระดับ 14
แหล่งข้อมูลเพิ่มเติม
ตัวอย่าง
- Jetpack WindowManager: ตัวอย่างวิธีใช้ไลบรารี Jetpack WindowManager
- Jetcaster : การใช้งานท่าทางบนโต๊ะด้วย Compose
Codelabs
- รองรับอุปกรณ์แบบพับได้และอุปกรณ์ที่มี 2 หน้าจอด้วย Jetpack WindowManager
- เพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์พับได้ด้วย Jetpack WindowManager