android设备各类各样,手机、pad、电视、车载等不一而足。即便是相同分辨率的手机也可能参数不一致,好比1080P的手机 dpi 通常认为是480,可是 Google 的Pixel2(1920*1080)的 dpi 是420。此外,android设备的宽高比更是多种多样。这就致使App适配的工做异常困难。尤为是你的app要适配各类平台,好比手机、pad、车载、电视。在这种情形下,你面临的问题让你无所适从,由于你根本猜不到设备的参数和尺寸,更别提如何适配。android
android度量计算公式git
具体的含义自行搜索,density 的差别致使适配困难;scaledDensity 是字体的缩放因子,scaledDensity 正常状况下和 density 相等,可是调节系统字体大小后会改变这个值。
查看源码,能够得知:DisplayMetrics 实例经过 Resources#getDisplayMetrics能够得到,而Resouces经过 Activity 或者 Application 的 Context 得到。
dp 和 px 的转换是经过 DisplayMetrics 中相关的值来计算的,view、bitmap 等元素在计算中的dp转换也是如此。
布局文件中 dp 的转换,最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换。相似的,BitmapFactory#decodeResourceStream 方法也会应用 DisplayMetrics 中的参数计算。github
/**
* Converts an unpacked complex data value holding a dimension to its final floating
* point value. The two parameters <var>unit</var> and <var>value</var>
* are as in {@link #TYPE_DIMENSION}.
*
* @param unit The unit to convert from.
* @param value The value to apply the unit to.
* @param metrics Current display metrics to use in the conversion --
* supplies display density and scaling information.
*
* @return The complex floating point value multiplied by the appropriate
* metrics depending on its unit.
*/
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
复制代码
修改application和activity的density,系统修改字体时打开App也能对应修改。scaledDensity计算根据系统原来的比值来得到如今修改后的值。api
final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density);
复制代码
在 Activity#onCreate 方法中调用下。代码比较简单,也没有涉及到系统非公开api的调用,所以理论上不会影响app稳定性。bash
/**
* 头条处理多设备的方案 setCustomDensity(this, getApplication());
*
* @param activity
* @param application
*/
private void setCustomDensity(Activity activity, final Application application) {
//application
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sRoncompatDennsity == 0) {
sRoncompatDennsity = appDisplayMetrics.density;
sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sRoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//计算宽为360dp 同理能够设置高为640dp的根据实际状况
final float targetDensity = appDisplayMetrics.widthPixels / 360;
final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity / sRoncompatDennsity);
final int targetDensityDpi = (int) (targetDensity * 160);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
appDisplayMetrics.scaledDensity = targetScaledDensity;
//activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
}
复制代码
具体适配效果能够见参考资料连接,今日头条团队在各类手机上的适配效果和后期bug反馈。app
这个方案的解决思路很简洁,参考资料也详细的列举了它的优势,很是吸引人。可是,最终咱们公司的项目没有采用这个,而是采用下面的方案。理由很简单:一次修改,全局改变。后期维护无所适从。假如一处UI出问题了,你打算怎么改?你无法改,你怎么改。ide
参考中《骚年你的屏幕适配方式该升级了!-今日头条适配方案》这篇文章进一步升级了这个思路,它最大的贡献是对单个 Activity 或 Fragment 能够取消适配。这个思路能够解决后期维护问题,我以为这个方案这个时候就值得推荐和使用了。同时,它还能自定义以宽或者高为维度进行适配。布局
遍历 ViewGroup 获取全部子 View 的尺寸参数,从新计算 View 的WidthHeightFont、Padding、LayoutMargin。字体
/**
* Only adapter width/height/padding/margin
* Created by zhangyuwan0 on 2018/3/21.
*/
public class SimpleConversion implements IConversion {
@Override
public void transform(View view, AbsLoadViewHelper loadViewHelper) {
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
}
}
复制代码
Activity、Fragment、自定义 View 等加载view后,手动调用LoadViewHelper#loadView 方法重计算一遍全部view。本质的转化方法是计算缩放因子。ui
private float calculateValue(float value) {
if ("px".equals(unit)) {
return value * ((float) actualWidth / (float) designWidth);
} else if ("dp".equals(unit)) {
int dip = dp2pxUtils.px2dip(actualDensity, value);
value = ((float) designDpi / 160) * dip;
return value * ((float) actualWidth / (float) designWidth);
}
return 0;
}
复制代码
/**
* Created by guokun on 2018/7/21.
* Description: 标准宽高640x360(16:9) density = 1.0 dpi = 160
* 1. 高度低于设计高度,以高度做标准缩放;
* 2. 高度高于设计高度,可是高度:宽度 < 9:16,以高度做标准缩放;
* 3. 其他以宽度做标准缩放;
* @param
* @return
*/
public float calculateValue(float value) {
if ("px".equals(unit)) {
return value * ((float) actualWidth / (float) designWidth);
} else if ("dp".equals(unit)) {
int dip = dp2pxUtils.px2dip(actualDensity, value);
value = ((float) designDpi / 160) * dip;
if (actualHeight < designHeight || actualWidth * designHeight / designWidth > actualHeight) {
return value * ((float) actualHeight / (float) designHeight);
}
return value * ((float) actualWidth / (float) designWidth);
}
return 0;
}
复制代码
@Override
public void transform(View view, AbsLoadViewHelper loadViewHelper) {
/**Created by guokun on 2018/7/28.
* Description: MyLinearLayout_h381特殊处理
* 1. MyLinearLayout键盘的高度大于标准高度360;
* 2. 这里UI标准图设计bug,未加上20dp 键盘top;
* */
if (view.getTag() != null && (Integer)view.getTag() == MyLinearLayout_h381.getCustomHeight(view.getContext())) {
int defaultDesign = loadViewHelper.getDesignHeight();
loadViewHelper.setDesignHeight((Integer) view.getTag());
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
loadViewHelper.setDesignHeight(defaultDesign);
}else {
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
}
}
复制代码
放弃这个项目吧,不值得。光是缺点,你都改不过来。
android 适配一直是个悬而未决的大难题。Google 提供的思路对于国内复杂的设备环境和小团队而言,代价很高。综合项目实际场景再权衡各类方案才是解决之道,由于这些方案自己并非很大的工程。
推荐Android两种屏幕适配方案
Android 目前稳定高效的UI适配方案
骚年你的屏幕适配方式该升级了!-今日头条适配方案