内容简介:android设备各种各样,手机、pad、电视、车载等不一而足。即使是相同分辨率的手机也可能参数不一致,比如1080P的手机 dpi 一般认为是480,但是 Google 的Pixel2(1920*1080)的 dpi 是420。此外,android设备的宽高比更是多种多样。这就导致App适配的工作异常困难。尤其是你的app要适配各种平台,比如手机、pad、车载、电视。在这种情形下,你面临的问题让你无所适从,因为你根本猜不到设备的参数和尺寸,更别提如何适配。android度量计算公式具体的含义自行搜索,d
android设备各种各样,手机、pad、电视、车载等不一而足。即使是相同分辨率的手机也可能参数不一致,比如1080P的手机 dpi 一般认为是480,但是 Google 的Pixel2(1920*1080)的 dpi 是420。此外,android设备的宽高比更是多种多样。这就导致App适配的工作异常困难。尤其是你的app要适配各种平台,比如手机、pad、车载、电视。在这种情形下,你面临的问题让你无所适从,因为你根本猜不到设备的参数和尺寸,更别提如何适配。
相关知识
android度量计算公式
- px = density * dp
- density = dpi / 160
- px = dp * (dpi / 160)
- DisplayMetrics.density
- DisplayMetrics.densityDpi
- DisplayMetrics.scaledDensity
具体的含义自行搜索,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 中的参数计算。
/** * 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; } 复制代码
今日头条方案
原理
- density = px / dp
适配方案
- 给定一个宽高大小固定的标准设计图,支持以宽或高一个维度自适应适配,保持改维度和设计图一致;
- 支持dp和sp单位。
实现
修改application和activity的density,系统修改字体时打开App也能对应修改。scaledDensity计算根据系统原来的比值来获得现在修改后的值。
final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density); 复制代码
在 Activity#onCreate 方法中调用下。代码比较简单,也没有涉及到系统非公开api的调用,因此理论上不会影响app稳定性。
/** * 头条处理多设备的方案 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反馈。
思考一刻
这个方案的解决思路很简洁,参考资料也详细的列举了它的优点,非常吸引人。但是,最终我们公司的项目没有采用这个,而是采用下面的方案。理由很简单:一次修改,全局改变。后期维护无所适从。假如一处UI出问题了,你打算怎么改?你没法改,你怎么改。
后续
参考中《 骚年你的屏幕适配方式该升级了!-今日头条适配方案 》这篇文章进一步升级了这个思路,它最大的贡献是对单个 Activity 或 Fragment 可以取消适配。这个思路可以解决后期维护问题,我觉得这个方案这个时候就值得推荐和使用了。同时,它还能自定义以宽或者高为维度进行适配。
原理
- 基于设计图的宽度值(或高度值)和对应的dpi适配,即根据设备的实际宽度(或高度)相对应的缩放view的尺寸。
- 缩放比率 = value * ((float) actualWidth / (float) designWidth)
适配方案
- 给定一个宽高大小固定的标准设计图,支持以宽或高一个维度自适应适配,保持改维度和设计图一致;
- 支持dp和sp单位。
实现
遍历 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。本质的转化方法是计算缩放因子。
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; } 复制代码
项目实际反馈
- 简单衡量头条和AndroidScreenAdaptation的优缺点后,我们最终选择这个方案。原因:虽然所有布局都需要手动调用 ScreenAdapterTools # getInstance() # loadView(view) 方法,工作量大;但是,优点也是这个。任何 View 的适配都是可以调整和修改的,而且不会影响其它布局。
/** * 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; } 复制代码
- 自定义 View 基本不支持。每个自定义 View 你需要查看源码调用 calcualteValue 重新计算参数。幸运的是项目自定义 View 不是很多和复杂。最致命的是:wrapcontent 不适配,所有的 View 必须给定尺寸;SeekBarProgress 不支持,手动反射调用方法处理适配问题。
@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屏幕适配方案》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- flutter 屏幕尺寸适配 字体大小适配
- Android屏幕适配
- Android屏幕适配方案分析
- androd 今日头条的屏幕适配理解
- Android屏幕适配的两种方式
- Fastlane 2.105.0 发布,已经适配 iPhone XS Max 屏幕
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Host Your Web Site In The Cloud
Jeff Barr / SitePoint / 2010-9-28 / USD 39.95
Host Your Web Site On The Cloud is the OFFICIAL step-by-step guide to this revolutionary approach to hosting and managing your websites and applications, authored by Amazon's very own Jeffrey Barr. "H......一起来看看 《Host Your Web Site In The Cloud》 这本书的介绍吧!