如何判断设备是折叠屏,全网最准的方法,没有之一!

2,300 阅读4分钟

折叠屏的设备越来越丰富,对于这块的设备类型非常重要,过去我们使用屏幕尺寸来判断是否是平板设备,有了折叠屏传统的方法就会误判折叠屏为平板,网上查了一圈资料,99%的解法都是错误!最真实的判断还需要看官网呀,代码已经写好,拿走不谢

官方出处

developer.android.com/develop/ui/…

让应用具备折叠感知能力 

bookmark_border

借助展开的大显示屏和独特的折叠状态,我们能够在可折叠设备上打造全新用户体验。如需让应用具备折叠感知能力,请使用 Jetpack WindowManager 库,它为可折叠设备的窗口功能(如折叠边或合页)提供了一个 API surface。如果应用具备折叠感知能力,就能调整其布局,避免将重要内容放在折叠边或合页区域,并将折叠边或合页用作自然分隔符。

了解设备是否支持桌面折叠状态或图书折叠状态等配置,有助于您做出关于支持不同布局或提供特定功能的决策。

窗口信息

Jetpack WindowManager 中的 WindowInfoTracker 接口会公开窗口布局信息。该接口的 windowLayoutInfo() 方法会返回一个 WindowLayoutInfo 数据流,该数据流会将可折叠设备的折叠状态告知您的应用。WindowInfoTracker#getOrCreate() 方法会创建一个 WindowInfoTracker 实例。

WindowManager 支持使用 Kotlin Flow 和 Java 回调收集 WindowLayoutInfo 数据。

Kotlin 数据流

如需开始和停止 WindowLayoutInfo 数据收集,您可以使用可重启的生命周期感知型协程。在这种协程中,当生命周期至少为 STARTED 时,系统会执行 repeatOnLifecycle 代码块,当生命周期为 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.
                    }
            }
        }
    }
}

Java 回调

借助 androidx.window:window-java 依赖项中包含的回调兼容性层,您无需使用 Kotlin Flow 即可收集 WindowLayoutInfo 更新。该工件包含 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()
   }
}

可折叠设备显示屏的功能

Jetpack WindowManager 的 WindowLayoutInfo 类会以 DisplayFeature 元素列表的形式提供显示窗口的功能。

FoldingFeature 是一种 DisplayFeature,它提供了有关可折叠设备显示屏的信息,其中包括:

  • state:设备的折叠状态,即 FLAT 或 HALF_OPENED
  • orientation:折叠边或铰链的方向,即 HORIZONTAL 或 VERTICAL
  • occlusionType:折叠边或铰链是否遮住了显示屏的一部分,即 NONE 或 FULL
  • isSeparating:折叠边或铰链是否创建了两个逻辑显示区域,即 true 或 false

注意 :虽然可折叠设备上的合页允许设备折叠到各种角度,但 FoldingFeature 不会作为 API 的一部分公开该角度。不同的设备有不同的报告范围,传感器的准确度也可能会因设备而异;因此,基于精确合页角度的动画或逻辑必须根据设备进行调整。

HALF_OPENED 的可折叠设备始终将 isSeparating 报告为 true,因为屏幕被分为两个显示区域。此外,当应用跨两块屏幕时,isSeparating 在双屏设备上始终为 true。

FoldingFeature bounds 属性(继承自 DisplayFeature)表示折叠功能(如折叠边或合页)的边界矩形。边界可用于相对于地图项在屏幕上定位元素:

KotlinJava

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

        }
    }
}

总结

官方api不仅支持判断是否是折叠屏,角度等详细信息也是支持的,属性都在displayFeatures内,判断displayFeatures是否为空即可

class MainActivity : FlutterActivity() {
    var isFold = false
    private lateinit var windowInfoTracker: WindowInfoTrackerCallbackAdapter
    private val layoutStateChangeCallback = LayoutStateChangeCallback()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        windowInfoTracker = WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this))
    }

    override fun onStart() {
        super.onStart()
        windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback
        )
    }

    override fun onStop() {
        super.onStop()
        windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback)
    }

    inner class LayoutStateChangeCallback : Consumer<WindowLayoutInfo> {
        override fun accept(newLayoutInfo: WindowLayoutInfo) {
            isFold =  newLayoutInfo.displayFeatures.isNotEmpty();
          Log.d("isPlate","isFold:$isFold displayFeatures:${newLayoutInfo.displayFeatures}")
        }
    }
}