屏幕密度dpi
屏幕密度通常指的是手机屏幕的dpi(dots per inch)
计算的方式是

- ldpi:对应的dpi范围为0 ~ 120,也就是说每英寸有0到120个像素点的屏幕的屏幕密度都属于ldpi
- mdpi:dpi范围为120 ~ 160
- hdpi:dpi范围为160 ~ 240
- xhdpi:dpi范围为240~320
- xxhdpi : dpi范围为320~480
通常以dpi值120、160、240、320、480分别指代ldpi、mdpi、hdpi、xhdpi、xxhdpi
屏幕密度越大的手机显示的图像会越细腻
private void getDpi() {
DisplayMetrics dm = getResources().getDisplayMetrics();
Log.i("TAG", "density = " + dm.density);
Log.i("TAG", "densityDpi = " + dm.densityDpi);
}
在一台屏幕密度为320dpi的Android手机上运行以上代码,得到结果
density = 2
densityDpi = 320 <- 这个就是屏幕密度
density = densityDpi/160
android选择dpi值160作为基准屏幕密度,
这个基准屏幕密度人为建立起了dp与px间的关系:在dpi为160的Android设备上,1 dp = 1px
假设x为某UI控件以px为单位的大小,y为同一UI控件以dp为单位的大小,densityDpi表示屏幕密度,
则x与y的关系为:x = y * densityDpi / 160。
dpi | 关系 |
---|---|
mdpi | 1dp=1px |
hdpi | 1dp=1.5px |
xhdpi | 1dp=2px |
xxhdpi | 1dp=3px |
显示大小
同一张图,在不同的文件夹,同一个手机上会有什么表现呢?
以一个144*144px的icon图标(放在drawable-xxxhdpi目录下),在xxhdpi密度的手机为例.
ImageView imageView = (ImageView)findViewById(R.id.img);
imageView.post(new Runnable() {
@Override
public void run() {
Log.i("tag", "img: " + imageView.getWidth() + " " + imageView.getHeight());
}
});
结果是108
计算公式是
最终显示宽度 = 原始宽度 * (显示设备手机的dpi /图片放置的drawable文件夹对应dpi)
也就是如果对应文件夹里面没有图片的时候,显示默认图片大小不一致的原因。
在Bitmap.decodeResource() 源码,系统在加载 res 目录下的资源图片时,会根据图片存放的不同目录做一次分辨率的转换,而转换的规则是:
新图的高度 = 原图高度 * (设备的 dpi / 目录对应的 dpi )
解决屏幕适配的方案
今日头条的解决方案
android中的dp在渲染前会将dp转为px,计算公式:
- px = density * dp;
- density = dpi / 160;
- px = dp * (dpi / 160);
今日头条的解决方案正是基于通过修改density值,把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值。 需要从UI那里知道设计图的尺寸
布局文件中dp的转换,最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换

SP跟scaledDensity有关,dpi跟density有关
用一个1080 * 1920的420dpi的模拟器做实验,假定给的ui图宽是360dp的,现在放置一个宽高为360dp的TextView,此时该控件理应该宽度撑满手机.而实际效果是

显然控件两边有留白,按照x = y * densityDpi / 160公式x = 360*420/160 = 945px,没有到1080px
按照今日头条的方案 在Activity的onCreate中,setContentView之前添加代码

确实得到效果

后来又尝试了在480 * 800的240dpi的设备上运行,又发现效果不对。
在计算targetDensity的方法不对,两个int类型相除,只会有int型的数值。应该是
final float targetDensity = ((float) displayMetrics.widthPixels)/360f;
然后又提到了系统修改字体大小失效的问题,并给出方案
private static float sNoncompatDensity;
private static float sNoncompatScaledDensity;
private static void setCustomDensity(Activity activity, final Application application){
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if(sNoncompatDensity == 0){
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig!=null&&newConfig.fontScale>0){
//监听字体切换
sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDensity = ((float) appDisplayMetrics.widthPixels)/360f;//360 为ui设计图 宽
//我们可以通过计算之前scaledDensity和density的比获得现在的scaledDensity
final float targetScaledDensity = targetDensity * (sNoncompatScaledDensity/sNoncompatDensity);
final int targetDensitydpi = (int)(targetDensity * 160);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensitydpi;
final DisplayMetrics actdisplayMetrics = activity.getResources().getDisplayMetrics();
actdisplayMetrics.density = targetDensity;
actdisplayMetrics.scaledDensity = targetScaledDensity;
actdisplayMetrics.densityDpi = targetDensitydpi;
}
其他方案
百分比布局
Android引入了全新的布局PercentFrameLayout和PercentRelativeLayout
添加依赖
compile 'com.android.support:percent:24:2.1'
<android.support.percent.PercentRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:background="@color/colorAccent"
app:layout_widthPercent="50%"
app:layout_aspectRatio="100%"
android:text="Hello World!"
android:layout_centerInParent="true"
/>
</android.support.percent.PercentRelativeLayout>
使用 app:app:layout_widthPercent 或者 app:layout_heightPercent来设置百分比
另外可以用 layout_aspectRatio来设置宽高比
smallestWidth适配
指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件
Android 目前最稳定和高效的UI适配方案
网友评论