新增判断折叠屏方法
参考相关:
手机是否是折叠屏设备
通过手机型号判断
private static boolean isHuaweiFold(Context context) {
if (!DeviceTypeUtils.INSTANCE.isHuaweiPhone()) {
return false;
}
return context.getPackageManager().hasSystemFeature("com.huawei.hardware.sensor.posture");
}
private static boolean isOppoFoldPhone(Context context) throws Throwable {
boolean isFold = false;
boolean isVerticalFold = false;
Class<?> cls = Class.forName("com.oplus.content.OplusFeatureConfigManager");
Method instance = cls.getMethod("getInstance");
Object configManager = instance.invoke(null);
Method hasFeature = cls.getDeclaredMethod("hasFeature", String.class);
Object object = hasFeature.invoke(configManager, FEATURE_FOLD);
if (object instanceof Boolean) {
isFold = (boolean) object;
}
Object object2 = hasFeature.invoke(configManager, FEATURE_VERTICAL_FOLD);
if (object2 instanceof Boolean) {
isVerticalFold = (boolean) object2;
}
// 是横向折叠屏的同时,不是竖向折叠屏
return isFold && !isVerticalFold;
}
通过设备属性判断
这里的判断,首次是不准的,请做存储
WindowManagerConfig.Companion.getInstance().isUnFoldStatus()
具体代码:
package com.haohua.demo
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.yunzhijia.logsdk.YZJLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class WindowManagerConfig private constructor(){
var uiStatus: MutableLiveData<FoldState> = MutableLiveData<FoldState>()
private object SingletonHolder {
val instance = WindowManagerConfig()
}
companion object {
val instance = SingletonHolder.instance
}
// 是否折叠状态,正常手机状态
fun isNormalPhoneStatus(): Boolean {
return uiStatus.value == FoldState.NORMAL_PHONE
}
fun isUnFoldStatus(): Boolean {
return !isNormalPhoneStatus()
// return uiStatus.value == FoldState.FLAT || uiStatus.value == FoldState.HALF_OPENED
}
fun register(activity: AppCompatActivity) {
activity.lifecycleScope.launch(Dispatchers.Main) {
activity.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED)
{
WindowInfoTracker.getOrCreate(activity)
.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
uiStatus.value = getCurrentFoldState(newLayoutInfo)
YZJLog.d("im-fold", "折叠屏状态变化 = ${uiStatus.value}, act = ${activity.javaClass.simpleName}")
}
}
}
}
private fun getCurrentFoldState(layoutInfo: WindowLayoutInfo): FoldState {
for (displayFeature in layoutInfo.displayFeatures) {
val foldFeature = displayFeature as? FoldingFeature
foldFeature?.let {
return when (it.state) {
FoldingFeature.State.FLAT -> {
FoldState.FLAT
}
FoldingFeature.State.HALF_OPENED -> {
FoldState.HALF_OPENED
}
else -> {
FoldState.NORMAL_PHONE
}
}
}
}
return FoldState.NORMAL_PHONE
}
class FoldState private constructor(private val description: String) {
override fun toString(): String {
return description
}
public companion object {
@JvmField
val FLAT: FoldState = FoldState("FLAT")
@JvmField
val HALF_OPENED: FoldState = FoldState("HALF_OPENED")
@JvmField
val NORMAL_PHONE: FoldState = FoldState("NORMAL_PHONE")
}
}
}
参考文案:
折叠屏资料
target api 升级到29的官方改动
https://developer.android.com/about/versions/10/behavior-changes-10?hl=zh-cn#foldables
折叠屏适配介绍文档
https://developer.android.com/guide/topics/ui/foldables?hl=zh-cn
集成文档 WindowManager
https://developer.android.com/jetpack/androidx/releases/window?hl=zh-cn#groovy
识别折叠屏状态的参数说明
https://developer.android.com/reference/kotlin/androidx/window/layout/FoldingFeature
官方提供的折叠屏的demo
https://github.com/android/user-interface-samples/tree/main/WindowManager
继承折叠屏的步骤说明
https://developer.android.com/codelabs/android-window-manager-dual-screen-foldables?hl=zh-cn#6
三星适配指南:
http://samsung.smarterapps.cn/index.php?app=home&mod=Public&act=help&type=11&id=92
Android 折叠屏技术发展与适配
https://blog.51cto.com/u_15375308/5071743
以下为老办法,不建议使用
获取手机是否是折叠屏
1、通过系统参数进行判断,是否是折叠屏手机
public static boolean isFoldDisplay(Context context) {
final String KEY = “config_lidControlsDisplayFold”;
int id = context.getResources().getIdentifier(KEY, “bool”, “android”);
if (id > 0) {
return context.getResources().getBoolean(id);
}
return false;
}
2、系统数据,进行判断
/**
- 系统方法判断,做兼容处理
- @param context
- @return
*/
public static boolean isFoldUIConfig(Context context) {
try {
// 系统方法不能兼容所有折叠屏手机,补偿,通过status进行判断
UIConfig.Status status = ResponsiveUIConfig.getDefault(context).getUiStatus().getValue();
// 由于正常手机和折叠状态,status值都是fold,所以,我们得以是否是折叠屏作为判断标准
return status == UIConfig.Status.UNFOLD || status == UIConfig.Status.UNFOLDING;
// 如果为true,存储起来,但是这个地方,无法调用到存储代码,心累,存储代码写在上层
} catch (Exception e) {
YZJLog.e(e.getMessage());
return false;
}
}
3、如果是折叠屏,存到sp里面,下次获取该数据
代码略。
判断代码(仅参考):
public static boolean isRealFoldPhone(Context context) {
// foldPhone 如果有值,直接返回,每次只读取一次配置,不需要反复读取
if (isRealFoldPhone != null && isRealFoldPhone) {
return isRealFoldPhone;
}
if (context == null) {
return false;
}
// 1、通过系统参数进行判断,是否是折叠屏手机
isRealFoldPhone = isFoldDisplay(context);
if (!isRealFoldPhone) {
boolean isFoldUiConfig = isFoldUIConfig(context);
if (isFoldUiConfig) {
isRealFoldPhone = true;
} else {
return false;
}
}
YZJLog.d("im-fold", "是否是折叠屏:" + isRealFoldPhone);
// 3、status补偿也不能完全确定,在折叠的情况下,获取到的值是异常的,最后在sp里面,看是否有记录
if (isRealFoldPhone) {
// 是折叠屏手机,先记录下来,后续使用,每次进记录一次
if (FoldUIConfigUtils.isRealFoldPhone == null) {
RouterManager.AppFold.getAppFoldService().setFoldPhone(true);
}
FoldUIConfigUtils.isFoldPhone = true;
} else {
// 非折叠屏手机,并且未赋值,从sp里面读值,可以直接拦截,不调用底层方法,暂时需要考虑默认配置,不处理
if (FoldUIConfigUtils.isFoldPhone == null) {
FoldUIConfigUtils.isFoldPhone = RouterManager.AppFold.getAppFoldService().isFoldPhone(context instanceof Activity ? (Activity) context : null);
}
}
return FoldUIConfigUtils.isFoldPhone;
}
判断是否是pad手机
1、区分出平板设备
ps:折叠屏默认是平板,也就是说,设备里面,是没有平板和折叠屏区分的,折叠屏是在平板的基础上,衍生而来的。
public static void initPadParam(Context context) {
if (padFlag == 1 || padFlag == 2 || context == null) {
return;
}
boolean ret = (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
padFlag = ret ? 1 : 2;
}
2、oppo手机专门判断
// 平板 FEATURE_TABLET oplus.hardware.type.tablet
// 折叠 FEATURE_FOLD oplus.hardware.type.fold
public static boolean isTablet(Context context) {
if (BuildConfig.DEBUG) {
YZJLog.d("im-device", "is pad = " + (context.getPackageManager().hasSystemFeature(FEATURE_TABLET)));
}
return context.getPackageManager().hasSystemFeature(FEATURE_TABLET);
}
public static boolean isFold(Context context) {
if (BuildConfig.DEBUG) {
YZJLog.d("im-device", "is pad = " + (context.getPackageManager().hasSystemFeature(FEATURE_FOLD)));
}
return context.getPackageManager().hasSystemFeature(FEATURE_FOLD);
}
3、其他手机,通过设备宽度判断
//方式2:smallestDeviceWidth
private static boolean isTableBySmallWidth(Context context) {
Context applicationContext = context.getApplicationContext();
if (applicationContext != null && applicationContext.getResources().getConfiguration().smallestScreenWidthDp >= 600) {
return true;
}
return false;
}
4、其他未验证方法,仅供参考
public static class HuaweiPad {
// 方式1
// public static boolean isTablet() {
// return "tablet".equals(SystemPropertiesEx.get("ro.build.characteristics", ""));
// }
//方式2
public static boolean isTablet(Context context) {
return context != null && (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
public static Boolean isTablet3() {
// return SystemProperties.getBoolean("lockscreen.rot_override", false);
// return SystemProperties.get("lockscreen.rot_override", false);
Boolean getBoolean = (Boolean) SystemProperties.get("getBoolean", Boolean.class, "lockscreen.rot_override", Boolean.class, false);
return getBoolean;
}
// 折叠屏判断
// IS_FOLDABLE = (!SystemProperties.get("ro.config.hw_fold_disp").isEmpty() || !SystemProperties.get(HwFoldScreenState.DEBUG_HW_FOLD_DISP_PROP).isEmpty());
}
public static class SemPad {
// 平板判断:
//方式1:screenLayout
public static boolean isTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout & 15) >= 3;
}
//方式2:smallestDeviceWidth
public static boolean isTablet2(Context context) {
Context applicationContext = context.getApplicationContext();
if (applicationContext != null && applicationContext.getResources().getConfiguration().smallestScreenWidthDp >= 600) {
return true;
}
return false;
}
// //方式3:ro.build.characteristics
// private static boolean isTablet() {
// String str = SemSystemProperties.get("ro.build.characteristics");
// return str != null && str.contains("tablet");
// }
}
手机屏幕状态
多窗口模式
/**
* 是否是多窗口模式
* @param activity
* @return
*/
public static boolean isInMultiWindow(Activity activity) {
boolean isInMultiWindowMode = false;
if (activity != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
isInMultiWindowMode = activity.isInMultiWindowMode();
}
return isInMultiWindowMode;
}
屏幕方向
/**
* 获取屏幕方向
*
* @param context
* @return true: 竖屏 false: 横屏
*/
public static boolean getScreenDirection(Context context) {
DisplayMetrics d = context.getResources().getDisplayMetrics();
if (d.heightPixels > d.widthPixels) {
return true;
}
return false;
}
平行视窗模式判断
String config = context.getResources().getConfiguration().toString();
boolean isInMagicWindow = config.contains("oplus-magic-windows");
context为Activity的context
说明:
部分Activity在平行视界下即使显示状态为全屏,其状态仍处于平行视界状态。
折叠屏适配
方案一:
activity + fragment 方式
方案二:
系统提供的方式
修改AndroidManifest文件
在“AndroidManifest.xml”文件的“application”中新增“meta-data”:
<meta-data android:name="EasyGoClient" android:value="true" />
新增Easygo.json配置文件
在“assets”目录下新建配置文件“easygo.json”,“easygo.json”文件模板和字段详细说明:
{
"easyGoVersion": "1.0",
"client": "com.teamtalk.ims",
"logicEntities": [
{
"head": {
"function": "magicwindow",
"required": "true"
},
"body": {
"mode": "0",
"defaultDualActivities": {
//主页面Activity,可以有多个,分号隔开
展开态时冷启动应用打开此页面时,系统在右屏自动启动relatedPage页面
"mainPages": "com.kdweibo.android.ui.fragment.HomeMainFragmentActivity",
"relatedPage": "com.yunzhijia.im.chat.ui.ChatActivity"
},
"Activities": [
{
"name": "com.kdweibo.android.ui.activity.StartActivity",
"defaultFullScreen": "true"
},
{
"name": "com.kdweibo.android.ui.fragment.HomeMainFragmentActivity",
"lockSide": "primary"//Activity锁定方式,当前仅支持锁定在primary侧
primary:锁定在主界面那一侧,锁定后,另一侧启动新的Activity时不会轻易平推窗口过来,除非推过来的窗口也是primary锁定窗口(典型场景:直播购物场景,将直播Activity配置成锁定)。
}
],
"UX": {
"supportDraggingToFullScreen": "true",
"isDraggable": "true",//支持左右大小拖拽
}
}
}
]
}
参数说明:
参数 | 限制 | 描述 |
easyGoVersion | 1 | 协议版本,固定值为“1.0” |
client | 1 | 应用包名 |
logicEntities. head. function | 1 | 调用组件名,固定值“magicwindow” |
logicEntities. head. required | 1 | 预留字段,固定值“true” |
logicEntities.body.mode | 1 | 基础分屏模式0:购物模式,activityPairs 节点不生效1:自定义模式(包括导航栏模式) |
logicEntities.body. activityPairs | ? | 自定义模式参数,配置从 from 页面到 to 页面的分屏展示 |
logicEntities.body. activityPairs.from | * | 触发分屏的源 Activity |
logicEntities.body. activityPairs.to | * | 触发分屏的目标 Activity,“”表示任意 Activity自定义模式:[{”from” :”com.xxx. ActivityA”, “to” :”com.xxx. ActivityB”}] 表示 A 上启动 B,触发分屏(A 左 B 右)导航栏模式:[{”from” :”com.xxx. ActivityA”, “to” :””}] |
logicEntities.body. defaultDualActivities | ? | 应用冷启动默认打开首页双屏配置 |
logicEntities.body. defaultDualActivities.mainPages | 1 | 主页面 Activity,可以有多个,分号隔开展开态时冷启动应用打开此页面时,系统在右屏自动启动 relatedPage 页面 |
logicEntities.body.defaultDualActivities.relatedPage | ? | 右屏默认展示页面 ActivitymainPages 和 relatedPage 只能配置 1 对,需要具体的 Activity 名,不支持通配符如: [{“mainPages”:“com.xxx.MainActivity”,“relatedPage”:“com.xxx.EmptyActivity”}] |
logicEntities.body. transActivities | * | 过渡页面列表如[ “com.xxx.ActivityD”,“com.xxx.ActivityE”,“com.xxx.ActivityF”] |
logicEntities.body.Activities | ? | 应用关键 Activity 属性列表 |
logicEntities.body.Activities.name | 1 | Activity 组件名 |
logicEntities.body.Activities.defaultFullScreen | ? | Activity 是否支持默认以全屏启动true:支持false:不支持默认为 false |
logicEntities.body.Activities.lockSide | ? | Activity 锁定方式,当前仅支持锁定在 primary 侧primary:锁定在主界面那一侧,锁定后,另一侧启动新的 Activity 时不会轻易平推窗口过来,除非推过来的窗口也是 primary 锁定窗口(典型场景:直播购物场景,将直播 Activity 配置成锁定)。 |