1. WindowManager
这并非系统 WMS 获取的那个 WindowManager,它是 Jetpack 的新成员,当前刚刚迈入 1.1.0。
implementation "androidx.window:window:1.1.0-alpha02"
它可以帮助我们适配日益增多的可折叠设备,满足多窗口环境下的开发需求。 可折叠设备通常分为两类:单屏可折叠设备(一个整体的柔性屏幕)和双屏可折叠设备(两个屏幕由合页相连)。
目前单屏可折叠设备正逐渐成为主流,但无论哪种设备都可以通过 WindowManager 感知当前的屏幕显示特性,例如当前折叠的状态和姿势等。
获取折叠状态
多屏设备下,一个窗口可能会跨越物理屏幕显示,这样窗口中会出现铰链等不连续部分,FoldingFeature (DisplayFeature 的子类)对铰链这类的物理部件进行抽象,从中可以获取铰链在窗口中的准确位置,帮助我们避免将关键交互按钮布局在其中。另外 FoldingFeature 还提供了可以感知感知当前折叠状态的 API,我们可以根据这些状态改变应用的布局:
//铰链处于半开状态且位置水平,适合切换到平板模式 fun isTableTopMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL //铰链处于半开状态且位置垂直,适合切换到阅读模式 fun isBookMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
WindowManager 允许我们通过 Flow 持续观察显示特性的变化。
lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { WindowInfoTracker.getOrCreate(this@SampleActivity) .windowLayoutInfo(this@SampleActivity) .collect { newLayoutInfo -> // Use newLayoutInfo to update the layout. } } }
如上,当显示特性变化时,我们能获取 newLayoutInfo ,它是一个 WindowLayoutInfo 类型,内部持有了 FoldingFeature 信息。
感知窗口大小变化
应用窗口可能跟随设备配置变化时(例如折叠屏的展开、旋转,或窗口在多窗口模式下调整大小)发生变化,我们可以通过 WIndowManger 的 WindowMetrics 获取窗口大小,我们有两种获取当前 WindowMetrics 的方式,同步获取和异步监听:
//异步监听 lifecycleScope.launch(Dispatchers.Main) { windowInfoRepository().currentWindowMetrics.flowWithLifecycle(lifecycle) .collect { windowMetrics: WindowMetrics -> val currentBounds = windowMetrics.bounds val width = currentBounds.width() val height = currentBounds.height() } } //同步获取 val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity) val currentBounds = windowMetrics.bounds val width = currentBounds.width() val height = currentBounds.height()
2. DragAndDrop
Jetpack DragAndDrop 是专门处理拖放手势的库,它除了服务于普通手机设备上的开发,更重要的意义是可以实现折叠设备跨屏幕的拖放
implementation 'androidx.draganddrop:draganddrop:1.0.0-alpha02'
DragStartHelper 和 DropHelper 是其最核心的 API,可以配置拖防过程中的数据传递、显示效果等,还可以监听手势回调。
拖动 DragStartHelper
DragStartHelper 负责监测拖动手势的开始时机,包括长按拖动、单击并用鼠标拖动等。我们可以将需要拖动的视图对象包装进来并开启监听,当监听到拖动手势触发时,完成一些简单配置即可。
// 使用 DragStartHelper 包装 draggableView 对象 DragStartHelper(draggableView) { view, _ -> // 将需要传递的数据封装到 ClipData 中 val dragClipData = ClipData.newUri(contentResolver, "File", fileUri) // 创建目标拖动时的展示图片,可自定义也可以根据 draggableView 创建默认样式 val dragShadow = View.DragShadowBuilder(view) // 基于数据、拖动效果启动拖动 view.startDragAndDrop( dragClipData, dragShadow, null, // Optional extra local state information // 添加 flag 启动全局拖动 DRAG_FLAG_GLOBAL or DRAG_FLAG_GLOBAL_URI_READ) ) }.attach()
如上,准备好需要拖动数据和样式等,调用 View#startDragAndDrop 启动拖动。例子中拖动的目标是 content: 这类 URI,因此我们可以通过设置 DRAG_FLAG_GLOBAL 实现跨进程的拖动。
放置 DropHelper
DropHelper 是另一个核心 API,关心拖动数据放下的时机和目标视图。
//针对可拖放视图调用 configureView DropHelper.configureView( this,// 当前Activity outerDropTarget, //接收拖放的对象,会根据情况高亮显示 arrayOf(MIMETYPE_TEXT_PLAIN, "image/*"), // 支持的 MIME 类型 DropHelper.Options.Builder() //一些参数配置,例如放下时高亮的颜色,视图范围等 .addInnerEditTexts(innerEditText) .build() ) { _, payload -> // 监听到目标的放下,可以从 ClipData 中取得数据, // 执行上传、显示等处理,当然还可以处理非法拖放时的警告或视图提醒等 ... }
构建 DropHelper.Options 实例的时候,需要调用 addInnerEditTexts(),这样可以确保嵌套的 EditText 控件不会抢夺视图焦点。