Android屏幕适配方式探索

基本定义

屏幕尺寸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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260