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

相关推荐
软件聚导航23 分钟前
uniapp 实现 ble蓝牙同时连接多台蓝牙设备,支持app、苹果(ios)和安卓手机,以及ios连接蓝牙后的一些坑
android·ios·uni-app
深海呐5 小时前
Android 最新的AndroidStudio引入依赖失败如何解决?如:Failed to resolve:xxxx
android·failed to res·failed to·failed to resol·failed to reso
解压专家6665 小时前
安卓解压软件推荐:高效处理压缩文件的实用工具
android·智能手机·winrar·7-zip
Rverdoser5 小时前
Android 老项目适配 Compose 混合开发
android
️ 邪神7 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
android·flutter·ios·鸿蒙·reatnative
努力遇见美好的生活8 小时前
Mysql每日一题(行程与用户,困难※)
android·数据库·mysql
图王大胜10 小时前
Android Framework AMS(17)APP 异常Crash处理流程解读
android·app·异常处理·ams·crash·binderdied·讣告
ch_s_t10 小时前
电子商务网站之首页设计
android
豆 腐12 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难14 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio