一开始项目使用的是第三方框架
但是会偶现,断电重启第一次,自适应失败的情况。
复现手顺:
当前为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的时候,最好也设置一下。