【Android折叠屏适配】基于AutoSize框架适配折叠屏并兼容多窗口模式

【Android折叠屏适配】基于AutoSize框架适配折叠屏并兼容多窗口模式

问题背景

当前最新的Android API 33对大屏设备的支持已较为完善,结合Jetpack Compose等响应式布局可实现一次适配所有屏幕。但考虑到代码往往是不能立即重构的,想要利用存量代码适配层出不穷的大屏及可折叠设备,并兼容多窗口场景,可采用Android界大名鼎鼎的AutoSize框架配合2套设计图尺寸来完成。

AutoSize框架基本原理

AutoSize框架支持5种单位 ,包括dp、sp、pt、in、mm,其中dp及sp为AutoSize框架的主单位。开发过程中可开启或关闭对某一单位的支持,使用主单位+副单位或只使用副单位进行适配都是可以的。选择哪一个副单位,撰写layout时就使用该单位即可。
AutoSize适配屏幕的核心原理为通过WindowManager拿到DisplayMetrics中的屏幕宽高(单位为px),再结合设置的设计图宽高(单位为px)计算并全局设置density(细节可查阅AutoSize框架源码)。

在Application及BaseActivity中进行设置

考虑到折叠屏开合时具有2组不同的屏幕宽高,我们针对其设置2套设计图宽高用以正确计算density。由于具有2套设计图尺寸,在AndroidManifest中设置设计图宽高不可行,需在Application onCreate()中计算当前屏幕宽高比及window宽高比手动设置设计图宽高,代码如下。
此外,在屏幕折叠及旋转场景下,从registerComponentCallbacks的onConfigurationChanged()回调中拿到的屏幕宽高有时不准确,尚未定位到原因(增加时延也并不稳定),故需在封装的BaseActivity的onConfigurationChanged()生命周期中也加入如下处理。

        /*注册屏幕适配监听器,适配前后均会回调此方法*/
        AutoSizeConfig.getInstance().setOnAdaptListener(new onAdaptListener() {
    
    
            @Override
            public void onAdaptBefore(Object target, Activity activity) {
    
    
                if (activity == null) {
    
    
                    return;
                }

                int[] windowSize = new int[2];
                windowSize[0] = activity.getResources().getDisplayMetrics().widthPixels;
                windowSize[1] = activity.getResources().getDisplayMetrics().heightPixels;

                AutoSizeConfig.getInstance()
                        .setCustomFragment(true)
                        .getUnitsManager()
                        .setSupportSP(false)
                        .setSupportDP(false)
                        .setSupportSubunits(Subunits.MM);

                /*判断屏幕宽高比是否为大屏*/
                if (FoldScreenUtil.isLargeScreen(activity)) {
    
    
                    /*判断window宽高比是否为大窗口*/
                    if (FoldScreenUtil.isLargeWindow(activity)) {
    
    
                        Log.i("isLargeScreen", "isLargeWindow");
                        /*判断横竖屏*/
                        if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
    
    
                            /*大屏-大窗口-横屏设计尺寸*/
                            AutoSizeConfig.getInstance()
                                    .getUnitsManager()
                                    .setDesignWidth(windowSize[0])
                                    .setDesignHeight(windowSize[1])
                                    .setDesignSize(1556, 1395);
                        } else {
    
    
                            /*大屏-大窗口-竖屏设计尺寸*/
                            AutoSizeConfig.getInstance()
                                    .getUnitsManager()
                                    .setDesignWidth(windowSize[0])
                                    .setDesignHeight(windowSize[1])
                                    .setDesignSize(1395, 1556);
                        }
                    } else {
    
    
                        if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
    
    
                            /*大屏-小窗口-横屏设计尺寸*/
                            AutoSizeConfig.getInstance()
                                    .getUnitsManager()
                                    .setDesignWidth(windowSize[0])
                                    .setDesignHeight(windowSize[1])
                                    .setDesignSize(1556, 750);
                        } else {
    
    
                            /*大屏-小窗口-竖屏设计尺寸*/
                            AutoSizeConfig.getInstance()
                                    .getUnitsManager()
                                    .setDesignWidth(windowSize[0])
                                    .setDesignHeight(windowSize[1])
                                    .setDesignSize(750, 1556);
                        }
                    }
                } else {
    
    
                    /*小屏通常禁止旋转,仅设置竖屏设计尺寸即可*/
                    /*特殊情况:平行视窗下只能拿到当前activity的显示区域宽高,会被判定为小屏*/
                    AutoSizeConfig.getInstance()
                            .getUnitsManager()
                            .setDesignWidth(windowSize[0])
                            .setDesignHeight(windowSize[1])
                            .setDesignSize(750, 1556);
                }
            }
            @Override
            public void onAdaptAfter(Object target, Activity activity) {
    
    
            }
        });

注意:多窗口场景下Activity window与屏幕不等价

多窗口场景下(如平行视窗、分屏及悬浮窗等),window大小和屏幕大小并不等价,若在多窗⼝模式下仍是以屏幕的宽⾼进⾏适配,则density一定是不正确的值。而AutoSize框架中获取的宽高为屏幕宽高,因此,需利用Activity上下文拿到窗口大小并手动设置。
经测试,平行视窗较为特殊,拿到的屏幕宽高为当前activity显示的区域宽高。
可参考如下方法判断当前window是否为大窗口状态。

    public static boolean isLargeWindow(Activity activity) {
    
    
        if (activity == null) {
    
    
            return false;
        }
        float longSide = 0;
        float shortSide = 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    
    
            WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
            Rect windowRect = windowMetrics.getBounds();
            longSide = Math.max(windowRect.width(), windowRect.height());
            shortSide = Math.min(windowRect.width(), windowRect.height());
        } else {
    
    
            int widthPixels = activity.getResources().getDisplayMetrics().widthPixels;
            int heightPixels = activity.getResources().getDisplayMetrics().heightPixels;
            longSide = Math.max(widthPixels, heightPixels);
            shortSide = Math.min(widthPixels, heightPixels);
        }

        boolean isLargeWindow = longSide / shortSide < MIN_ASPECT;
        Log.i("isLargeWindow", "longSide" + longSide + "shortSide" + shortSide);
        return isLargeWindow;
    }

在item的getView()或onBindViewHolder中进行设置

由于GridView、RecyclerView等组件存在item复用机制,屏幕开合或旋转时,item宽高可能不正确。在getView()中加入AutoSize.autoConvertDensityOfGlobal(activity)手动设置density可解决此问题。

猜你喜欢

转载自blog.csdn.net/weixin_38702864/article/details/127071799