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

相关推荐
菜鸟xiaowang23 分钟前
Android 使用ninja加速编译的方法
android
_一条咸鱼_2 小时前
Android大厂面试秘籍: View 相关面试题深入分析
android·面试·android jetpack
_一条咸鱼_2 小时前
Android 大厂面试秘籍:Hilt 框架的测试支持模块(八)
android·面试·kotlin
匹马夕阳3 小时前
(十三)安卓开发中的输入框、复选框、单选框和开关等表单控件详解
android
yangshuo12815 小时前
WSA(Windows Subsystem for Android)安装LSPosed和应用教程
android·windows·模拟器·lsposed·windows安卓子系统
ab_dg_dp5 小时前
Android InstalldNativeService::getAppSize源码分析
android
鸿蒙布道师6 小时前
鸿蒙NEXT开发资源工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
V少年6 小时前
深入浅出安卓字节码插装
android
V少年6 小时前
深入浅出Hook
android
V少年6 小时前
深入浅出讲解安卓PMS
android