安卓屏幕适配及实验
前言
关于屏幕适配,我看到了很多的笔记和文章,就现在来说,比较流行的有今日头条和sw两种,可能是我的段位太低,在之前的工作中,基本没有处理过屏幕适配的工作,一个dp适配所有,但是最近关注到了这个上面,于是抽时间了解了一下流行的这两种适配方案及其基本原理,写个demo试验并记录。
测试机配置及项目相关配置
- 测试机为谷歌原生机(模拟器):10801920-420、10802160_400、1080*2220_440(分辨率_dpi)
- 开发语言为kotlin
- 代码地址:屏幕适配测试代码
理论知识
理论知识是实践的基础
先来理一理这个db、px、dpi 之间的关系
px = desity * dp
desity = dpi / 160
px = dp * (dpi /160)
dpi ^ 2 = (width ^ 2 + height ^ 2 ) / (屏幕尺寸 ^ 2)
举个栗子:
一部手机,屏幕尺寸为6寸,分辨率为1920*1080,那么
dpi ^ 2 = (1080 ^ 2 + 1920 ^ 2) / 36 = (1166400 + 3686400) / 36 = 134800
dpi = 367
那么,我们计算可以得出宽度的dp为
dp_w = 1080 / (367 / 160) = 470.8
所以,这个手机控件宽度为470.8可以布满。了解以上的计算公式后,我们再来想一个问题。现在的设备基本上dpi是固定的,那么,假如我们现在手机的dpi为420,分辨率为1080*1920,那么计算得到宽度的dp值为
dp_w = (1080 * 160 / 420) = 411.43
那我们现在这样子写
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="411.43dp"
android:text="这是一个按钮"
android:layout_height="50dp"/>
</LinearLayout>
可以看到,刚好布满,但是,如果我们换成一个dpi为440的设备,我们再看看效果
从预览图上可以看出,我们这个控件超出了界线,会导致界面显示不全,那我们换成dpi为400的试试
可以看到,我们同样的布局代码,在不同的dpi的设备上,显示的效果差别还是很大的,假如现在的UI设计图的尺寸就是以411.43dp设计的,那么,在适配的时候就会出现上面的情况。
那么,按照这么说,是不是不能适配呢? 毕竟出厂的手机的dpi不同呀,但是,我们看上面的一个公式:
px = dp * density ==> dp = px / desity
desity = dpi / 160
在dp和dpi之间,靠的是desity来转换的,那么这个desity是什么,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得。(这里请看一种极低成本的Android屏幕适配方式 详细讲解)
那么,我们就可以通过修改density 的值即可适配我们的UI设计图,比如,我们的设计图为420dp,那么我们只需要把density 的值修改为
density = px_w / 420
即可,这里直接借用头条的适配代码
object HalloScreenAdapter {
// 系统的Density
private var sNoncompatDensity: Float = 0.toFloat()
// 系统的ScaledDensity
private var sNoncompatScaledDensity: Float = 0.toFloat()
public fun setCustomDensity(activity: Activity, application: Application) {
val displayMetrics = application.resources.displayMetrics
if (sNoncompatDensity == 0f) {
sNoncompatDensity = displayMetrics.density
sNoncompatScaledDensity = displayMetrics.scaledDensity
// 监听在系统设置中切换字体
application.registerComponentCallbacks(object : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration?) {
if (newConfig != null && newConfig!!.fontScale > 0) {
sNoncompatScaledDensity = application.resources.displayMetrics.scaledDensity
}
}
override fun onLowMemory() {
}
})
}
// 此处以420dp的设计图作为例子
val targetDensity = (displayMetrics.widthPixels / 420).toFloat()
val targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity)
val targetDensityDpi = (160 * targetDensity).toInt()
displayMetrics.density = targetDensity
displayMetrics.scaledDensity = targetScaledDensity
displayMetrics.densityDpi = targetDensityDpi
val activityDisplayMetrics = activity.resources.displayMetrics
activityDisplayMetrics.density = targetDensity
activityDisplayMetrics.scaledDensity = targetScaledDensity
activityDisplayMetrics.densityDpi = targetDensityDpi
}
}
接下来,我们去验证一下,我们的代码的效果。
实践操作
实验是检验真理的唯一标准
我们编辑这样的一个布局,然后在不同dpi的设备上观察一下效果(假设设计图给的宽度dp为411.43)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_screen_adaption_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="今日头条适配方案"
android:textSize="22sp" />
<Button
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_marginTop="30dp"
android:background="@drawable/shape_bt_bg"
android:layout_gravity="center_horizontal"
android:text="Bt1"/>
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/img_test"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<View
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:background="#B32F2F"/>
<View
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:background="#1CB6B1"/>
<View
android:layout_width="30dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:background="#1CB6B1"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="10dp"
android:background="#B32F2F"/>
<View
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:background="#1CB6B1"/>
</LinearLayout>
<TextView
android:layout_width="411.43dp"
android:layout_height="50dp"
android:gravity="right|center_vertical"
android:text="我是一个文本框"
android:background="#80E20E"/>
</LinearLayout>
这里,我们使用不同dpi的设备跑起来看一下效果。
未适配时(从左往右设备分辨率及dpi参数为:10801920-420、10802160_400、1080*2220_440)
显示的效果不尽人意,添加上我们的适配代码,再次运行看看效果
适配之后(从左往右设备分辨率及dpi参数为:10801920-420、10802160_400、1080*2220_440)
现在,看起来,效果好很多了吧,最后,借用大佬说过的一句话
所有的适配方案都不是用来取代match_parent,wrap_content的,而是用来完善他们的
所以,我们能使用match_parent,wrap_content的,还是使用它们比较好。
SW(smallestWidth)
在使用了头条的适配方案之后,我们再来试试smallestWidth的适配方案的效果,它的原理相对来说比较容易理解,就是让系统根据识别到的dp(宽度)值去资源文件中寻找对应限定符的文件夹下的资源文件。
举个栗子,假设我们的设备分辨率为1080*1920,dpi为480,那么,我们的dp_w的值就为 1080 / (480 / 160) = 360 , 那么,系统就会找到如下的资源目录(工具地址:生成资源目录):
未适配时(从左往右设备分辨率及dpi参数为:10801920-420、10802160_400、1080*2220_440)
显示的效果不尽人意,添加上我们的适配代码,再次运行看看效果
适配之后(从左往右设备分辨率及dpi参数为:10801920-420、10802160_400、1080*2220_440)
可以看到,适配效果还是不错的。详细的可以看这里
好了,这篇文章先到这里,后期有新的的再补充(客套话)。