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的时候,最好也设置一下。

相关推荐
inmK111 分钟前
蓝奏云官方版不好用?蓝云最后一版实测:轻量化 + 不限速(避更新坑) 蓝云、蓝奏云第三方安卓版、蓝云最后一版、蓝奏云无广告管理工具、安卓网盘轻量化 APP
android·工具·网盘工具
giaoho14 分钟前
Android 热点开发的相关api总结
android
咖啡の猫2 小时前
Android开发-常用布局
android·gitee
程序员老刘2 小时前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans52 小时前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白3 小时前
实现双向滑动的 ScalableImageView(下)
android
峥嵘life3 小时前
Android Studio新版本编译release版本apk实现
android·ide·android studio
studyForMokey5 小时前
【Android 消息机制】Handler
android
敲代码的鱼哇5 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
翻滚丷大头鱼5 小时前
android View详解—动画
android