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

相关推荐
后端码匠4 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白6 小时前
Android清单文件
android
董可伦8 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空9 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭9 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot10 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai10 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢11 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^11 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区11 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版