Android:屏幕显示适配实战 , 详解 PX DPI DP/DIP Density的区别。

知识储备 PX DPI DP/DIP Density 区别

前言

我们需要先了解尺寸分辨率的定义。

尺寸

一般而言提到屏幕的尺寸指的就是屏幕对角线的尺寸。

  • 含义:手机对角线的物理尺寸

  • 单位:英尺(inch),1英尺=2.54cm

    Android手机常见的尺寸有5寸、5.5寸、6寸、6.5寸及以上等等

分辨率

指手机在横向、纵向上的像素点数总和

可以描述为屏幕的"高×宽" = A×B

含义:屏幕在纵向有A个像素点,在横向有B个像素点

比如:2248×1080,代表纵向有2248个像素点,横向有1080个像素点

PX

传统计算机语言中描述的像素,在Android则代表绝对像素。

1PX = 1像素

Android中不推荐使用这种单位,因为不同的设备其分辨率是不一致的:

比如:我们现在将某个Button的width设为160px,则会出现如下情况:

  • 在分辨率为“320宽”的设备里,该按钮显示占屏幕宽度一半
  • 在分辨率为“640宽”的设备里,该按钮显示占屏幕宽度的四分之一

DPI

可以称作:屏幕密度。

  • 含义:每英寸的像素点数

  • dpi (dots per ich)

    加入设备每英寸有400个像素,那么设备的屏幕像素密度 = 400dpi

如何计算到dpi呢?

d

p

i

=

线

/

=

(

2

+

2

)

/

dpi = 像素值对角线 / 尺寸 = sqrt(宽^2+高^2)/有效屏幕尺寸

dpi=线/=(

2+2)/

DP/DIP

density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。

  • 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果

  • Android开发时用dp而不是px单位设置图片大小,是Android特有的单位

    假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480宽分辨率手机上设置应为240px;在320宽的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。

  • dp与px的转换关系

    在Android中,规定以160dpi为基准:1dp=1px

    如果屏幕密度为"320dpi"时,此时"1dp = 2px"了,以此类推

    转换关系为 1dp = (dpi / 160) * px

Density

这个单词本身直接翻译的意思而言,其也代表“密度”。 需要注意的是,在Android中,其实并非如此

我们可以通过context.getResources().getDisplayMetrics().density获取density,通过该方法获取到的该值,实际上是等价于“dpi / 160”的一个结果值。

也就是说getResources().getDisplayMetrics().density = getResources().getDisplayMetrics().densityDpi / 160

最终公式

d

p

=

d

p

i

/

160

p

x

=

d

e

n

s

i

t

y

p

x

dp = dpi / 160 * px = density * px

dp=dpi/160px=densitypx

问题来源

近期发现一个问题,同样一行字,在oppo手机上显示的字体特别大。经过深入研究比较发现,不仅oppo好像华为的手机上也有一些偏大。

具体表现为,同样一行文本,在手机宽度相同的情况下,小米手机、oppo手机和华为手机显示的数量不一样。相关测试数据如下表格(以下相关数据,均为程序内部获取)

手机型号 DPI 宽度(像素) 14dp下显示的字数/行 宽度(像素)/(DPI/160*14)
小米8 440 1080 28 28.05
oppoA3 480 1080 25 25.7
华为mate20Pro 640 1080 19 19.28

很明显可以看得出,实际测出来的单行显示的字数和通过程序获取相关数据计算出来的结果基本一致。

适配方案

基于目前了解到的适配要求,要求不同的手机上,一行显示的字数保持一致

方案一😮

依据上面的信息,思考可以单行在以相应的dp上显示的数量作为参考坐标,反推出此时需要的dpi,在基类里面重新设置到程序即可

比如🌰

我们要求 标准的 参考系为 14dp情况下每行显示字体数量为 27个

那么 我们可以从程序中获取到 宽度的像素值 比如说 此时获取到了 1440

我们可以计算到 14dp情况下单个字体此时所占用的px = 1440 / 27 = 53.3

根据dp与px以及dpi的关系 可以计算出 此时dpi = 53.3 / 14 * 160 = 609

这样将获取到的dpi 设置到程序即可。😮

相关程序

注意:目前写死的参考系是 14dp情况下一行允许显示27个子 该项需要定义标准 且应该可配置!

object CompatibleDisplayUtil {

    /**
     * dp 参考坐标
     */
    private const val REFERENCE_DP = 14

    /**
     * num 参考数量
     * 在以 {@link CompatibleDisplayUtil#REFERECE_DP} 的前提下 一行最多希望可显示的数量
     */
    private const val REFERENCE_NUM = 27

    /**
     * 兼容
     */
    @JvmStatic
    fun compatible(context: Context?) {
        if (context != null) {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                //更新dpi
                context.resources.let { resources ->
                    resources.updateConfiguration(
                        resources.configuration.apply {
                            densityDpi = calculateDPI(context)
                        },
                        resources.displayMetrics
                    )
                }
            }
        }
    }

    /**
     * 计算dpi
     */
    private fun calculateDPI(context: Context): Int {
        return (obtainWidth(context).toFloat() / REFERENCE_NUM / REFERENCE_DP * 160).toInt()
    }

    /**
     * 获取宽度像素值
     */
    private fun obtainWidth(context: Context) = context.resources.displayMetrics.widthPixels
}

在基类进行使用

BaseActivity
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CompatibleDisplayUtil.compatible(this);
        ...
}
BaseFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
	CompatibleDisplayUtil.compatible(getActivity());
	...
}

方案二😮

以设计稿的宽度作为参考值,来进行计算density。来进行适配不同机型的显示。比如原型设计的宽度为375 所以 以375宽度为基准进行适配,这个按照自己的设计图可以修改!

相关程序

    /**
     *  默认按照设计图宽度是375dp  进行适配
     */
    @JvmStatic
    fun compatibleByteDance(context: Context?) {
        val targetWidth = 375.0
        if (context != null) {
        	//这个根据自身实际需求,获取application即可
            val application = AppUtil.application
            val displayMetrics = application.resources.displayMetrics
            val targetDensity = displayMetrics.widthPixels / targetWidth
            val targetScaledDensity = targetDensity * (displayMetrics.scaledDensity / displayMetrics.density)
            val targetDPI = targetDensity * 160
            with(displayMetrics){
                density = targetDensity.toFloat()
                scaledDensity = targetScaledDensity.toFloat()
                densityDpi = targetDPI.toInt()
            }
            context.resources.displayMetrics.apply {
                density = targetDensity.toFloat()
                scaledDensity = targetScaledDensity.toFloat()
                densityDpi = targetDPI.toInt()
            }
        }
    }
}

在基类进行使用

BaseActivity
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CompatibleDisplayUtil.compatibleByteDance(this);
        ...
}
BaseFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    CompatibleDisplayUtil.compatibleByteDance(getActivity());
    ...
}

创作不易,如有帮助一键三连咯🙆‍♀️。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>