Android 折叠屏型号判断(包含pad)

新增判断折叠屏方法

参考相关:

手机是否是折叠屏设备

通过手机型号判断

    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",//支持左右大小拖拽
        }
      }
    }
  ]
}

参数说明:

参数限制描述
easyGoVersion1协议版本,固定值为“1.0”
client1应用包名
logicEntities. head. function1调用组件名,固定值“magicwindow”
logicEntities. head. required1预留字段,固定值“true”
logicEntities.body.mode1基础分屏模式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.mainPages1主页面 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.name1Activity 组件名
logicEntities.body.Activities.defaultFullScreen?Activity 是否支持默认以全屏启动true:支持false:不支持默认为 false
logicEntities.body.Activities.lockSideActivity 锁定方式,当前仅支持锁定在 primary 侧primary:锁定在主界面那一侧,锁定后,另一侧启动新的 Activity 时不会轻易平推窗口过来,除非推过来的窗口也是 primary 锁定窗口(典型场景:直播购物场景,将直播 Activity 配置成锁定)。
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Android 折叠动画可以使用属性动画来实现。以下是一个简单的实现方式: 1. 定义布局文件 在布局文件中,需要定义两个 View,一个是可见的 View,一个是不可见的 View。可见的 View 用来展示内容,不可见的 View 用来作为展开或折叠的目标。 2. 定义属性动画 使用属性动画来实现折叠效果,需要针对可见的 View 的高度进行动画。根据需要展开或折叠的高度,设置动画的起始值和结束值。 3. 设置动画监听器 在动画开始和结束时,需要对不可见的 View 进行显示或隐藏。 以下是一个具体的实现例子: 1. 定义布局文件 ```xml <RelativeLayout android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:padding="16dp" android:text="Hello World!"/> <TextView android:id="@+id/target" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone"/> </RelativeLayout> ``` 2. 定义属性动画 ```java private void animateView(final View view, final int startHeight, final int endHeight) { ValueAnimator valueAnimator = ValueAnimator.ofInt(startHeight, endHeight); valueAnimator.setDuration(500); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = value; view.setLayoutParams(layoutParams); } }); valueAnimator.start(); } ``` 3. 设置动画监听器 ```java private void toggle() { final TextView content = findViewById(R.id.content); final TextView target = findViewById(R.id.target); final int targetHeight = content.getLineHeight() * 3; if (target.getVisibility() == View.GONE) { target.setVisibility(View.VISIBLE); animateView(content, content.getHeight(), targetHeight); } else { animateView(content, targetHeight, content.getHeight()); target.setVisibility(View.GONE); } } ``` 以上是一个简单的实现方式,可以根据实际需求进行调整。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值