Android 自适应

一开始项目使用的是第三方框架

GitHub - JessYanCoding/AndroidAutoSize: 🔥 A low-cost Android screen adaptation solution (今日头条屏幕适配方案终极版,一个极低成本的 Android 屏幕适配方案).

但是会偶现,断电重启第一次,自适应失败的情况。

复现手顺:

当前为A分辨率,杀死进程 =》设置B分辨率=》断电重启,自适应失败。

以下内容基于单位dp。

一 失败原因

第一种

设置DisplayMetrics还未生效,layout已经开始绘制,导致失败。

第二种

DisplayMetrics是全局可用,被其他应用【三方应用或者系统应用\View等】重写了甚至是恢复默认,导致失败。

二 原理

DisplayMetrics 是公用的,谁都有权利进行修改,DisplayMetrics.density一旦进行修改,所有的页面、view、第三方库都会被修改到。

能够达到低成本和低侵入性。

同时如果,其他应用,或者系统进行DisplayMetrics.density的修改,也会影响到我们自身的应用。

除非我们的页面已经创建,没有重建,创建好的页面后,再设置density是不会成功的。

三 解决方案

工具类

复制代码
public class DisplayUtils {

    private String TAG = DisplayUtils.class.getSimpleName();

    //设计宽度
    private static final float DEFAULT_DISPLAY_WIDTH = 1280;


    private static DisplayUtils instance = null;

    public synchronized static DisplayUtils getInstance() {
        if (instance == null) {
            instance = new DisplayUtils();
        }
        return instance;
    }

    //设计高度,本项目以高度为准,横向滑动
    private float DEFAULT_DISPLAY_HEIGHT = 720;
    public void setDisplayDensity(Context context){

        if (context == null) {
            Log.d(TAG, "context is null");
            return;
        }

        DisplayMetrics displayMetrics = getRealDisplayMetrics(context);
        //布局缩放倍数
        float targetDensity = ((float) displayMetrics.heightPixels) / DEFAULT_DISPLAY_HEIGHT;
        //todo 宽度为基准
//        float targetDensity = ((float) displayMetrics.widthPixels) / DEFAULT_DISPLAY_WIDTH;
        //字体缩放倍数
        float targetScaledDensity = targetDensity * (displayMetrics.scaledDensity / displayMetrics.density);
        int targetDensityDpi = (int) (targetDensity * 160);

        Log.d(TAG, "setDisplayDensity: heightPixels = " + displayMetrics.heightPixels +", targetDensity = "
                + targetDensity + ", density = " + displayMetrics.density + ", scaledDensity = " + displayMetrics.scaledDensity
                + ", targetScaledDensity = " + targetScaledDensity + ", targetDensityDpi = " + targetDensityDpi);

        DisplayMetrics activityDisplayMetrics = context.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
    }

    public static DisplayMetrics getRealDisplayMetrics(Context context){
        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        display.getRealMetrics(displayMetrics);
        return displayMetrics;
    }


    /**
     * 每个子fragment创建之前重置density,保证是正确的,因为onConfigurationChanged回调分辨率切换时设置后,
     * 在Fragment创建前density又被系统修改,导致分辨率显示异常(偶现)
     */
    public void setDisplayDensityForFragments(AppCompatActivity context){

        context.getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
            @Override
            public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable Bundle savedInstanceState) {
                super.onFragmentCreated(fm, f, savedInstanceState);
                Log.d(TAG, "onFragmentCreated: " + f);
                setDisplayDensity(context);
            }
        }, true);
    }
}

使用方式

Activity

以下为主Activity调用,必须在setContentView之前。在view绘制之前需要设置好DisplayMetrics.density。

要注意onConfigurationChanged里,分辨率差距过小,页面可能不会进行重建,这个时候需要我们手动重建。

复制代码
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        DisplayUtils.getInstance().setDisplayDensity(this);
        DisplayUtils.getInstance().setDisplayDensityForFragments(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.xxxx);
    }


    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.e(TAG, "onConfigurationChanged screenInfo Width:" + ScreenUtils.getScreenWidth() + " height:" + ScreenUtils.getScreenHeight() + " dpi:" + ScreenUtils.getScreenDensityDpi());
        DisplayUtils.getInstance().setDisplayDensity(this);
        //偶现分辨率不会重绘,手动重绘
        this.recreate();
    }

Fragment

在主Activity添加" DisplayUtils.getInstance().setDisplayDensityForFragments(this);"这句代码后,本身Fragment是不需要单独设置的。

因为我们项目是只有一个activity,剩下的都是Fragment,时序问题导致,偶现首个添加的Fragment自适应失败,所以才需要单独添加代码。

同样也需要在设置view之前,但是不能在onCrate,请参考第二种情况的原因。

为了保证自适应一定成功,要在页面显示到屏幕之前,毫秒级别的差距,设置成你想要的**DisplayMetrics.density。**否则就有可能会失败。

复制代码
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        DisplayUtils.getInstance().setDisplayDensity(getMainActivity());
        binding = DataBindingUtil.inflate(inflater, R.layout.xxxx, container, false);
        return binding.getRoot();
    }

动态添加View的时候,最好也设置一下。

相关推荐
独自破碎E17 小时前
【BISHI9】田忌赛马
android·java·开发语言
代码s贝多芬的音符19 小时前
android 两个人脸对比 mlkit
android
darkb1rd20 小时前
五、PHP类型转换与类型安全
android·安全·php
gjxDaniel21 小时前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
csj5021 小时前
安卓基础之《(22)—高级控件(4)碎片Fragment》
android
峥嵘life1 天前
Android16 【CTS】CtsMediaCodecTestCases等一些列Media测试存在Failed项
android·linux·学习
stevenzqzq1 天前
Compose 中的状态可变性体系
android·compose
似霰1 天前
Linux timerfd 的基本使用
android·linux·c++
darling3311 天前
mysql 自动备份以及远程传输脚本,异地备份
android·数据库·mysql·adb
你刷碗1 天前
基于S32K144 CESc生成随机数
android·java·数据库