问题
通常在写布局的时候,我们用相对布局、权重比来解决因为屏幕尺寸大小不一带来的控件摆放问题,而大控件的宽高度量使用dp、文字大小使用sp,通常情况下dp和sp是一样的、例如10dp和10sp在屏幕上显示效果一样,只不过当系统修改文字大小时,使用sp标量的控件都跟随系统发生变化。不论是dp还是sp,最终要在页面上渲染出来前都会被转成像素单位px。
1、哪为什么一开始不使用像素作为控件的宽高的单位呢?
2、dp和px换算关系呢?
3、同等dp在不同设备上能保证控件大小一致吗?
解决这些疑问前先来复习一下Android屏幕的几个概念。
概念
屏幕分辨率:1920*1080标识高上有1920个像素点、宽度上有1080个像素点
屏幕尺寸 屏幕对角线的长度(单位inch)
屏幕像素密度dpi 计算公式
dip为160,则刚好 1dp = 1px。
接下来就可以解决上诉提出的三个问题
1、dp和px换算关系
px = dp * (dip / 160)
安卓中定义了一个系数density
density = (dip / 160)
px = density * dp
2、假设屏幕大小相同,若屏幕分别率越高,那么在相同的区域内就得放下更多的像素点、意味着屏幕密度越大,像素点就得越小,反之,像素点就越大,密度越小,像素点就越大;所以同样画一段长度100px的线段在高分辨率下,看上去就比在低分辨率屏幕下短,所以在写布局时不会采取像素来度量控件宽高。
3、最后一个问题 “同等dp在不同设备上能保证控件大小一致吗?”
理想状态状态下是可以基本保持一致。但是安卓手机碎片化严重,有很多奇怪尺寸出现。通过对比,可以明显发现同样的相同的dp华为设备上UI显示比较粗大
由于屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱
我现在手上有两台pad设备,屏幕分辨率1920*1200,一台是华为C5pad,另一台是三星pad。三星是9寸多,华为是8寸多。
今日头条对此给出分析和解决方案,造成这样问题是UI设计假设按宽度360dp这一尺寸设计的,而有的设备实际上宽度比360dp还大,有的比360小。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况。
梳理需求
首先来梳理下我们的需求,一般我们设计图都是以固定的尺寸来设计的。比如以分辨率1920px * 1080px来设计,以density为3来标注,也就是屏幕其实是640dp * 360dp。如果我们想在所有设备上显示完全一致,其实是不现实的,因为屏幕高宽比不是固定的,16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了。但是通常下,我们只需要以宽或高一个维度去适配,比如我们Feed是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致,尤其不能存在某些设备上显示不全的情况。同时考虑到现在基本都是以dp为单位去做的适配,如果新的方案不支持dp,那么迁移成本也非常高。
因此,总结下大致需求如下:
支持以宽或者高一个维度去适配,保持该维度上和设计图一致;
支持dp和sp单位,控制迁移成本到最小。
解决突破口
从dp和px的转换公式 :px = dp * density
可以看出,如果设计图宽为360dp,想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,我们只能修改 density 的值。
通过阅读源码,我们可以得知,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得。
先来熟悉下 DisplayMetrics 中和适配相关的几个变量:
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi
DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
下面给出代码,只有一个方法,对dp进行修改。
//三星pad宽度1280dp (是dp,是不px) ,SCREEN_WIDTH_DP根据不同的设计图修改,手机一般是360
private final static int SCREEN_WIDTH_DP = 1280;
private static float sNoncompatDensity;
private static float sNoncompatScaleDensity;
public static void setCusomDensity(final Activity activity,final Application application){
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sNoncompatDensity == 0){
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaleDensity = appDisplayMetrics.scaledDensity;
//监听系统改变字体的大小
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig !=null &&newConfig.fontScale>0){
sNoncompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
Log.e("tag","sNoncompatScaleDensity:"+sNoncompatScaleDensity);
}
}
@Override
public void onLowMemory() {
}
});
}
//根据参考的适配宽度 计算新的Density、ScaleDensity、DensityDpi
float targetDensity = (float) appDisplayMetrics.widthPixels/SCREEN_WIDTH_DP;
float targetScaleDensity = (float)targetDensity*(sNoncompatScaleDensity/sNoncompatDensity);
int targetDensityDpi = (int) (160*targetDensity);
//修改全局的
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaleDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
//修改当前activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
在要在UI布局渲染前调用即可
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ScreanAdapterUtils.setCusomDensity(this,MyAplication.getApplication());
setContentView(R.layout.activity_main);
}
今日头天适配方案https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
问题
但是这个方法会导致一些比较大的对话框出现暂时问题,对话框不能全名展示。需要从新计算对话宽的宽度和高度,当然一些小对话的的展示不受影响。
直接给出代码
public static void dialogAdapter(WindowManager windowManager, Dialog dialog,float heightScale,float widthScale){
Point point = new Point();
//获得代表当前window属性的对象
Window window = dialog.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
//获取window的宽高信息
Display display = windowManager.getDefaultDisplay();
display.getSize(point);
// 将设置后的大小赋值给window的宽高
if (widthScale!=0){
params.width = (int) (point.x * widthScale);
}
if (heightScale!=0){
params.height = (int) (point.y * heightScale);
}
//设置属性
window.setAttributes(params);
}
在UI渲染之后调用