市面上折叠屏陆续出了几款手机,我们产品也下发了适配折叠屏的需求,以下对折叠屏是撇工作做一个记录,中间也包含网络搜罗资料整理,供大家参考。
一、目前主流折叠屏机型:
厂商 | 型号 | 折叠分辨率 | 展开分辨率 |
---|---|---|---|
华为 | HUAWEI Mate Xs | 主屏:2480 x 1148 副屏:2480 x 892 | 2480 x 2200 |
华为 | HUAWEI Mate X2 | 外屏:2700 x 1160 | 2480 x 2200 |
小米 | MIX FOLD | 外屏:2520 x 840 | 2480 x 1860 |
三星 | Galaxy Z Fold2 | 外屏:2260 x 816 | 2208 x 1768 |
二、折叠屏的物理姿态:
三、折叠屏的物理切分:
四、针对折叠屏基础的适配方案:
1、屏幕兼容性的支持:
(1)应用 resizeable 能力支持
设置方法:在 manifest 文件的或节点中设置 android:resizeableActivity 的值为 true,可 声明应用支持自适应显示,Activity 将能以分屏和 freeform 模式启动。
示例代码:
android:resizeableActivity=["true" | "false"]
(2)增加申明应用支持的屏幕比范围
申明最大比例:
通过在 < meta-data> 添加 android.max_aspect 声明的方式
示例代码:
<meta - data android: name = "android.max_aspect" android: value = "2.4" / >
申明最小比例
通过在 < meta-data> 添加 android.max_aspect 声明的方式
示例代码:
< meta - data android: name = "android.min_aspect" android: value = "1.0" / >
2、屏幕连续性的支持:
为了保证您的应用程序在展开/折叠过程无缝切换,您需要做应用连续性的设计,以确保您的应用程序任务不中断,
以MateX 为例,最佳的体验为,应用在展开切换过程中,不发生应用的重启,且切换之前的任务和应用相关状态得以保存和延续,
折叠展开的动作,会触发对smallestscreensize、screensize和screenlayout的配置更改。通过注册监听系统configchanges消息,
不重启应用的情况下处理配置更改,您需要向manifest中添加android:configchanges属性,其中至少包含以下值:
< activity
android: name == ".MyActivity"
android: configChanges = "screenSize|smallestScreenSize|screenLayout" / >
需要复写 onConfigurationChanged() 方法,通过该方法的newConfig参数获得屏幕的分辨率等信息,就可以针对不同比例屏幕下的应用界面布局做相应调整,如切换布局、调整控件位置和间距等。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i("test", "newConfig.screenHeightDp:" + newConfig.screenHeightDp +
", newConfig.screenWidthDp" + newConfig.screenWidthDp);
//根据屏幕分辨率信息调整应用的布局
...
}
2.2:我们研究过程中总结的较为通用的方案:
根据华为开发者文档提供的适配需要在每个页面和控件的 onConfigurationChanged方法中对页面进行重新布局和排版,实际测试过程中发现几乎每个页面都需要重新设置,工作量非常大,(我们使用的适配方案为smallestWidth 限定符最小宽度适配方案,在大屏幕进入页面,切换小屏幕时页面元素尺寸变得非常大,字体也非常大,因为大屏幕使用的大尺寸的dp,切换时没有自动切换sw文件夹)。后来发现在基类的attachBaseContext方法中判断是否是折叠屏的展开状态,重新设置Context,使其强制使用小屏幕的sw尺寸,代码如下:
@Override
protected void attachBaseContext(Context newBase) {super.attachBaseContext(FoldingScreenUtils.attachBaseContext(newBase));
}
并在基类Activity的onConfigChanged回调中重新设置Context,避免小屏幕进入页面时不会调用方法,
@Overridepublic void onConfigurationChanged(Configuration newConfig) {FoldingScreenUtils.onConfigurationChanged(this,newConfig);super.onConfigurationChanged(newConfig);}
工具类如下:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.DisplayMetrics;public class FoldingScreenUtils {public static final float MAX_WIDTH_HEIGHT = 3f / 4f;/*** 会创建一个新的Context来替代baseContext用以设置sw* @param context* @return*/public static Context attachBaseContext(Context context) {if (isFoldingScreen(context)) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();DisplayMetrics displayMetrics = resources.getDisplayMetrics();int smallestScreenWidthDp = configuration.smallestScreenWidthDp;// 8.0需要使用createConfigurationContext处理if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {return updateResources(context, smallestScreenWidthDp);} else {configuration.smallestScreenWidthDp = (int) (smallestScreenWidthDp * 0.52);resources.updateConfiguration(configuration, displayMetrics);return context;}} else {return context;}}@TargetApi(Build.VERSION_CODES.N)private static Context updateResources(Context context, int smallestScreenWidthDp) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();configuration.smallestScreenWidthDp = (int) (smallestScreenWidthDp * 0.52);return context.createConfigurationContext(configuration);}/*** 返回当前设备是否是展开状态折叠屏,目前没有提供准确api,暂用屏幕宽高比判断.** @param context 上下文* @return 是否是折叠屏展开状态*/public static boolean isFoldingScreen(Context context) {Resources resources = context.getResources();DisplayMetrics displayMetrics = resources.getDisplayMetrics();float widthPixels = displayMetrics.widthPixels * 1.0f;float heightPixels = displayMetrics.heightPixels * 1.0f;return widthPixels / heightPixels > MAX_WIDTH_HEIGHT;}/**** 根据新屏幕参数判断是否是折叠屏宽屏状态.* @param newConfig context取值不准时拿到onConfigurationChanged中的新屏幕参数* @return 是否是折叠屏的宽屏状态*/@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2)public static boolean isFoldingScreen(Configuration newConfig) {return (float) newConfig.screenWidthDp / (float) newConfig.screenHeightDp > MAX_WIDTH_HEIGHT;}/*** 只用这个方法来更新config* @param context* @param newConfig*/public static void onConfigurationChanged(Context context, Configuration newConfig) {try {if (isFoldingScreen(context)) {newConfig.smallestScreenWidthDp = (int) (newConfig.smallestScreenWidthDp * 0.52);context.getResources().updateConfiguration(newConfig, context.getResources().getDisplayMetrics());}} catch (Exception e) {e.printStackTrace();}}
}
大家有注意到我们工具类中isFoldingScreen方法有两份重写,一个是使用activity类的Context参数取当前屏幕的宽高来判断比例,但在一些popWindow和Dialog中并不能直接实现此方法,需要在类中传入的context监听ComponentCallbacks全局的状态改变回调,
context.registerComponentCallbacks(new ComponentCallbacks() {@Overridepublic void onConfigurationChanged(Configuration newConfig) {}@Overridepublic void onLowMemory() {}});
但是当屏幕改变时注册时的context中取出的屏幕宽高已经不准,所以需要用onConfigurationChanged中返回的newConfig对象取当前准确的屏幕尺寸来判断是否是展开状态的折叠屏,目前当前厂家和google并没有提供具体api来判断是否是折叠屏,所以暂时通过这种方法判断,同时对pad也有一定适配效果。
注意在控件销毁时做解除监听处理:
context.unregisterComponentCallbacks
参考文档:
https://developer.huawei.com/consumer/cn/doc/90101 华为折叠屏应用开发指导
https://www.jianshu.com/p/83efb0eaeee3 Android 折叠屏适配攻略