基本定义
屏幕尺寸L
例子:华为P10(VTR-AL00/全网通)这台手机的尺寸为5.1英寸
含义:手机对角线的物理尺寸
单位:英寸(inch),1英寸=2.54cm
屏幕分辨率W*H
例子:华为P10(VTR-AL00/全网通)的屏幕分辨率为1920x1080像素,即宽度方向上均匀填充1080个像素点,在高度方向上均匀填充1920个像素
含义:手机在横向、纵向上的像素点数总和
一般描述成屏幕的"宽x高”=AxB,屏幕在横向方向(宽度)上有A个像素点,在纵向方向(高)有B个像素点
单位:px(pixel),UI设计师的设计图会以px作为统一的计量单位
屏幕像素密度 dpi
含义:每英寸的像素点数
单位:dpi(dots per ich)
假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi
密度无关像素dp
含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上 应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
dp与px的转换 px = density * dp ,density = dpi / 160
在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px = 1px/density = 1px * 160 / dpi
屏幕尺寸、分辨率、像素密度三者关系
dpi = sqrt(W的平方+H的平方) / L = 对角线像素个数 / 对角线物理尺寸 = 一英寸的屏幕长度里填充了多少个像素点
density = dpi / 160 = 一英寸的屏幕长度里填充了多少个像素点 乘以 一个固定常数
所以对于每台手机来说,density值是有固定的物理意义的
探索方案
理解了上述定义,我们再往下看:
约定设计规则
在屏幕适配的问题上,首先要确保设计稿是按照一个恒定标准输出的
比如按照一台屏幕宽高比为16:9,屏幕分辨率为1920*1080,density = 3的手机去设计UI
px = density * dp ,由于规则恒定,我们可以根据设计稿给定的尺寸(像素)转化为(密度无关像素dp)
我们可得知设计稿期望的是一个360dp(DESIGN_WIDTH)*640dp(DESIGN_HEIGHT)的画布(屏幕分辨率除以density即为像素与dp的换算结果)
具体适配工作
故适配工作就变成了我们要尽可能的将屏幕在宽度上的像素点分为360组,在高度上的像素点分为640组,每组计为一个dp,则适配者需要的做的就是改变系统的density值使px 与 dp 的换算比例发生变化,最终实现按照期望分组的愿望,density对于宽度与高度的像素转化关系是共用的,故对于16:9的机型下述两种方式都能得到期望的density值
density = W/ DESIGN_WIDTH
density = H / DESIGN_HEIGHT
不同屏幕宽高比所面临的问题
但这个愿望能实现的基础是屏幕的像素宽高比是16:9(640:360),对于大于或小于16:9的机型会出现什么问题呢?
当我们处理一个屏幕分辨率比值大于16:9的机型时,
如18:9(2340 1080)的机型时,
若按照宽度的分辨率改变density的值,则
density = W / DESIGN_WIDTH = 3
此时density的改变会影响H(屏幕宽度)的像素分组
分组结果为 H / density = 2340 / 3 = 780dp
该结果大于我们期望的640dp,此时会造成设计稿只占用了屏幕宽度分组总数720组的640组像素,使得80dp的空间为空白
若按照高度的分辨率改变density的值
density = 2340 / 6440 = 3.65625
此时density的改变会影响W(屏幕宽度)的像素分组
分组结果为 W / density = 1080 / 3.65625 = 295.38dp
则此时会造成设计稿占用了屏幕宽度分组总数295.38组的360组像素,使得部分设计稿的视图不足以展示在屏幕内而被遮挡显示不全
当我们处理一个比值小于 16:9的机型时同理,若按照宽度的分辨率去改变density的值,则会使高度方向的设计内容有一部分被遮挡,
若按照高度的分辨率去改变density的值,则会使宽度方向的设计内容展示完全后有一部分空白
适应场景
故针对此方案,需要根据实际设计需求,区分对当前activity来说,是宽度优先还是高度优先
代码实现
kotlin实现的工具类如下
...
/**
* description :Android屏幕适配方式 按照设计稿宽度为360dp*640dp处理
* 解决问题场景:假设UI设计图按屏幕宽度360dp设计,在一个1920*1080、屏幕尺寸为5的手机上,
* dpi为440(sqrt(宽的平方+高的平方)/尺寸),屏幕宽度其实为1080/(440/160)=392.7dp
* 此时屏幕是比设计图要宽的,故此时在布局文件中使用dp也无法在不同设备上显示为同样效果,同时还存在部分设备屏幕宽度不足360dp导致实际显示不全的情况
* Created by Wangpeng on 2019-11-27
*/
private var sSysDensity: Float =0f
private var sSysScaledDensity: Float =0f
private val DESIGN_WIDTH =360
private val DESIGN_HEIGHT =640
// 在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px
private val ANDROID_DESIGN_STANTARD =160
/**
* 只关注宽度的精确适配
*/
fun setCustomDensitySuitWidth(activity: Activity, application: Application) {
setCustomDensity(activity, application, true)
}
/**
* 只关注高度的精确适配
*/
fun setCustomDensitySuitHeight(activity: Activity, application: Application) {
setCustomDensity(activity, application, false)
}
private fun setCustomDensity(activity: Activity, application: Application, isSuitWidth: Boolean) {
val appDisplayMetrics = application.resources.displayMetrics
if (sSysDensity ==0f) {
sSysDensity = appDisplayMetrics.density
sSysScaledDensity = appDisplayMetrics.scaledDensity
// 监听系统字体变化
application.registerComponentCallbacks(object : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration?) {
if (newConfig !=null &&newConfig.fontScale >0) {
sSysScaledDensity = application.resources.displayMetrics.scaledDensity
}
}
override fun onLowMemory() {}
})
}
// 设计稿宽度为360dp,高度为640dp,屏幕宽度(像素为单位)除以设计稿宽度(dp为单位) 可以得到实际需要的density
var targetDensity =if (isSuitWidth) {
(appDisplayMetrics.widthPixels /DESIGN_WIDTH).toFloat()
}else {
(appDisplayMetrics.heightPixels /DESIGN_HEIGHT).toFloat()
}
val targetScaleDensity = targetDensity * (sSysScaledDensity /sSysDensity)
val targetDensityDpi = (ANDROID_DESIGN_STANTARD * targetDensity).toInt()
appDisplayMetrics.density = targetDensity
appDisplayMetrics.scaledDensity = targetScaleDensity
appDisplayMetrics.densityDpi = targetDensityDpi
val atyDisplayMetrics = activity.resources.displayMetrics
atyDisplayMetrics.density = targetDensity
atyDisplayMetrics.scaledDensity = targetScaleDensity
atyDisplayMetrics.densityDpi = targetDensityDpi
}
网友评论