探究drawable图片的加载原理和缩放规律

编程入门 行业动态 更新时间:2024-10-27 16:25:49

探究drawable图片的加载原理和<a href=https://www.elefans.com/category/jswz/34/1770385.html style=缩放规律"/>

探究drawable图片的加载原理和缩放规律

目录(?)[+]

  1. 前言
  2. Android中的度量单位
  3. 探究drawable图片的加载
  4. 后语

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


前言

Android的源码公开策略丰富了手持设备的多样性,但随之而来的却是较为严重的”碎片化”——版本繁多、尺寸多样、功能定制。在Android项目开发中,软件工程师都会面临一个问题:如何适配多不同分辨率的设备?

许多人采用的是这样的方式:利用不同的dimens和drawable资源适配不同分辨率的设备。这么做当然没错,可是它也同时带来一些弊端

  • 在调试UI时挨个修改多个dimen文件中的每个值。
    多数时候会先做一个分辨率出来,比如1920*1080;然后再对照这个效果适配其他分辨率的展示效果。如果要调整某个尺寸的大小,那么先要找到其对应的dimens文件,再去修改。
  • UI标注的困惑
    UI设计师一般只会在一套UI上标注具体的尺寸大小和颜色。比如,只在1920*1080上标注了一个TextView的长度是100px,那么在1280*720上的分辨率上该控件的大小又该是多少呢?自己再换算一下?
  • 多套drawable容易导致APK文件较大。
    图片多了,那么资源所占的体积必然会随之增大;在发布前为了减小APK的大小,可能又不得不做一些瘦身的操作,至于效果有时也觉得不痛不痒,乏善可陈。
  • 不同drawable资源带来的繁琐
    如果某个切图需要修改,那么就需要替换各个drawable中对应的图片。这个过程中,如果错放了或者漏放了某个尺寸的图片,那么又是一个小悲剧了,它会导致图片在某些分辨率的手机上失真

嗯哼,毫不避讳的说:以上这些坑我都掉进去过,有的坑还有点深,快到我脖子了。
当我最后一次掉在坑里的时候,我就下定决心,我要想个办法:一套图片,一套布局,一个dimen完成多分辨率的适配!

哇哈,如果你也有这个想法,那就上车吧!


Android中的度量单位

在此以华为P7为例,解释inch、px、pt、dpi、dip、densityDpi、TypedValue、sp等等Android中常见的度量单位

inch

inch即为英寸,它表示设备的物理屏幕的对角线长度。
比如该例中P7的屏幕尺寸为5英寸,表示的就是手机的右上角与左下角之间的距离,其中1 inch = 2.54 cm

px

pixel简称为px,它表示屏幕的像素,也就是大家常说的屏幕分辨率。
比如在该例中P7的分辨率为1920*1080,它表示屏幕的X方向上有1080个像素,Y方向上有1920个像素。

pt

pt类似于px,但常用于字体的单位,不再赘述

dpi和densityDpi

dot per inch简称为dpi,它表示每英寸上的像素点个数,所以它也常为屏幕密度。
在Android中使用DisplayMetrics中的densityDpi字段表示该值,并且不少文档中常用dpi来简化或者指代densityDpi。

在手机屏幕一定的情况下,如果分辨率越高那么该值则越大,这就意味着画面越清晰、细腻和逼真。
在此,仍然以华为P7为例,计算其dpi值。先利用勾股定理得其对角线的像素值为2202.91,再除以对角线的大小5,即2202.91/5=440.582;此处计算出的440.582便是该设备的屏幕密度。

Android中依据densityDpi的不同将设备分成了多个显示级别:
ldpi、mdpi、hdpi、xhdpi、xxhdpi
这些显示级别分别表示一定范围的dpi,比如160dpi—240dpi都称为hdpi,更多详情请参见下图。

其实,在Android的源码中也定义了这些常量,比如:

public static final int DENSITY_LOW = 120;

public static final int DENSITY_MEDIUM = 160;

public static final int DENSITY_XXHIGH = 480;

嗯哼,在了解了这些之后,现在我们再通过代码来获取设备的dpi值

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getDisplayInfo</span>(){Resources resources=getResources();DisplayMetrics displayMetrics = resources.getDisplayMetrics();<span class="hljs-keyword">float</span> density = displayMetrics.density;<span class="hljs-keyword">int</span> densityDpi = displayMetrics.densityDpi;System.out.println(<span class="hljs-string">"----> density="</span> + density);System.out.println(<span class="hljs-string">"----> densityDpi="</span> + densityDpi);
}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>

输出结果:

—-> density=3.0
—-> densityDpi=480

呃,获取到的densityDpi是480和我们计算出来的屏幕实际密度值440.582不一样。这是为什么呢?
在每部手机出厂时都会为该手机设置屏幕密度,若其屏幕的实际密度是440dpi那么就会将其屏幕密度设置为与之接近的480dpi;如果实际密度为325dpi那么就会将其屏幕密度设置为与之接近的320dpi。这也就是说常见的屏幕密度是与每个显示级别的最大值相对应的,比如:120、160、240、320、480、640等。顺便说一下,看到代码中的density么?嗯哼,其实它就是一个倍数关系罢了,它表示当前设备的densityDpi和160的比值,例如此处480/160=3。为啥是除以160而不是其他数值呢?甭急,马上就会讲到了。

话说,林子大了什么鸟都有,有的手机不一定会选择120、160、240、320、480、640中的值作为屏幕密度,而是选择实际的dpi作为屏幕密度。比如为了发烧而生的小咪手机,它的某些机型的densityDpi就是个非常规的值。

其实,关于这一点,我们从Android源码对于densityDpi的注释也可以看到一些端倪:

The screen density expressed as dots-per-inch.
May be either DENSITY_LOW,DENSITY_MEDIUM or DENSITY_HIGH

请注意这里的措辞”May be”,它也没有说一定非要是DENSITY_LOW、DENSITY_MEDIUM、 DENSITY_HIGH这些系统常量。
好吧,这可能就是Android”碎片化”的一个佐证吧。

dp

density-independent pixel简称为dip或者dp,它表示与密度无关的像素。
如果使用dp作为长度单位,那么该长度在不同密度的屏幕中显示的比例将保持一致。

既然dp与密度无关,那么它与px又有什么关系呢?

在刚提到的Android的多个显示级别中有一个mdpi,它被称为基准密度
正如官方文档所言:

The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen, which is the baseline density assumed by the system for a “medium” density screen.

当dpi=160时1px=1dp,也就是说所有dp和px的转换都是基于mdpi而言的。
比如当dpi=320(即xhdpi)时1dp=2px;当dpi=480(即xxhdpi)时1dp=3px,该过程的换算公式为:

dp * (dpi / 160)

完整的对应关系,请参照下图。

sp

scale-independent pixel简称为sp,它类似于dp,但主要用于表示字体的大小,不再赘述

TypedValue
刚才提到,依据densityDpi的不同将设备分成了多个显示级别:ldpi、mdpi、hdpi、xhdpi、xxhdpi。看到这句话时想必很多人都觉得这个玩意太眼熟了,在res下不是有drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxdpi文件夹么?是的,但是它们有什么联系么?
之前也说了:Android设备千差万别,不同设备的屏幕密度(densityDpi)自然也就各不相同,有的属于mdpi,某些又属于xhdpi,或者xxhdpi等等其他显示级别。设计师为了让同一个APP在各种手机上都获得较好的显示效果就会针对densityDpi的不同而单独提供一套UI图。
比如,客户要求APP适配显示级别为:ldpi、mdpi、hdpi、xhdpi、xxhdpi的设备,那么UI设计师就需要5套尺寸不一的UI图分别放入res下的drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi文件夹里。当手机设备的显示级别为hdpi时,此时APP会去加载drawable-hdpi中对应图片;同理如果手机的显示级别为xxhdpi那么APP就会去自动加载drawable-xxhdpi中的资源图片。

关于此处的这种对应关系,我们再来看一段代码:

<code class="language-java hljs  has-numbering"><span class="hljs-javadoc">/*** 原创作者:* 谷哥的小弟** 博客地址:* */</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getDrawableFolderDensity</span>(){TypedValue typedValue = <span class="hljs-keyword">new</span> TypedValue();Resources resources=mContext.getResources();<span class="hljs-keyword">int</span> id = getResources().getIdentifier(imageName, <span class="hljs-string">"drawable"</span> , packageName);resources.openRawResource(id, typedValue);<span class="hljs-keyword">int</span> density=typedValue.density;System.out.println(<span class="hljs-string">"----> density="</span>+density);
}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>

在此,我们可以发现:
如果将图片放入drawable-ldpi中那么density值为120
如果将图片放入drawable-mdpi那么density的值为160

类似地操作总结如下图:

嗯哼,看到这是不是就将densityDpi和TypedValue中的density理解性地结合在一起了呢?说白了,设备会去res下找寻与之适应的资源图片,在这个找寻的过程中判断”是否合适”的方式就是将自身的densityDpi与res文件夹的TypedValue.density字段相比较。

TypedValue中除了刚说的density字段外,还有一个挺重要的方法applyDimension( ),源码如下:

<code class="language-java hljs  has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">applyDimension</span>(<span class="hljs-keyword">int</span> unit, <span class="hljs-keyword">float</span> value,DisplayMetrics metrics){<span class="hljs-keyword">switch</span> (unit) {<span class="hljs-keyword">case</span> COMPLEX_UNIT_PX:<span class="hljs-keyword">return</span> value;<span class="hljs-keyword">case</span> COMPLEX_UNIT_DIP:<span class="hljs-keyword">return</span> value * metrics.density;<span class="hljs-keyword">case</span> COMPLEX_UNIT_SP:<span class="hljs-keyword">return</span> value * metrics.scaledDensity;<span class="hljs-keyword">case</span> COMPLEX_UNIT_PT:<span class="hljs-keyword">return</span> value * metrics.xdpi * (<span class="hljs-number">1.0</span>f/<span class="hljs-number">72</span>);<span class="hljs-keyword">case</span> COMPLEX_UNIT_IN:<span class="hljs-keyword">return</span> value * metrics.xdpi;<span class="hljs-keyword">case</span> COMPLEX_UNIT_MM:<span class="hljs-keyword">return</span> value * metrics.xdpi * (<span class="hljs-number">1.0</span>f/<span class="hljs-number">25.4</span>f);}<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>

该方法的作用是把Android系统中的非标准度量尺寸(比如dip、sp、pt等)转变为标准度量尺寸px。在这段代码里,同样可以见到一个density;但是请注意它是DisplayMetrics中的字段而不是TypedValue的,请注意区分。


探究drawable图片的加载

这得从一次掉坑的经历说起。

有天下午,都快下班了,测试妹子跑到我工位前,急匆匆地说:图片失真了。哎,又不是失身,急啥嘛。我慢条斯理地瞅瞅了代码:代码没错呀,以前也都是这些的呀。到底是哪里出了幺蛾子呢?经过一番排查,发现是图片放错了地方:本来是该放到drawable-xxhdpi中的但是小手一抖错放到了drawable-xhdpi中导致了图片放大失真。

嗯哼,这个坑我们可能自己踩过,或者说这个现象我们略知一二,但是导致这个现象的原因是什么呢?它的背后隐藏着什么呢?

来吧,一起瞅瞅。

在此,准备了一张图,该图就是我的CSDN博客头像

图片的宽为144,高为180。

然后在res文件夹下建立drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxdpi文件夹,并且将该图片放入drawable-xxhdpi中

再利用ImageView显示该图片,代码如下:

<code class="language-xml hljs  has-numbering"><span class="hljs-tag"><<span class="hljs-title">ImageView
</span>    <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/imageView"</span><span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span><span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span><span class="hljs-attribute">android:layout_centerInParent</span>=<span class="hljs-value">"true"</span><span class="hljs-attribute">android:src</span>=<span class="hljs-value">"@drawable/lfdfhl"</span>/></span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

运行之后,看一下效果

最后,在Java代码中获取图片的宽高及其所占内存的大小,代码如下:

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getImageInfo</span>() {mImageView.post(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-annotation">@Override</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {BitmapDrawable bitmapDrawable = (BitmapDrawable) mImageView.getDrawable();<span class="hljs-keyword">if</span> (<span class="hljs-keyword">null</span> != bitmapDrawable) {Bitmap bitmap = bitmapDrawable.getBitmap();<span class="hljs-keyword">int</span> width = bitmap.getWidth();<span class="hljs-keyword">int</span> height = bitmap.getHeight();<span class="hljs-keyword">int</span> byteCount = bitmap.getByteCount();System.out.println(<span class="hljs-string">"----> width="</span> + width + <span class="hljs-string">",height="</span> + height);System.out.println(<span class="hljs-string">"----> byteCount="</span> + byteCount);}}});
}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

输出结果如下:
width=144,height=180,byteCount=103680
嗯哼,获取到的图片宽高和其原本的宽高一致。那么这个byteCount又是怎么算出来的呢?
Android系统在利用drawable中的图片生成Bitmap时默认采用的色彩模式是Bitmap.Config.ARGB_8888;在该模式中一共有四个通道,其中A表示Alpha,R表示Red,G表示Green,B表示Blue;并且这四个通道每个各占8位即一个字节,所以合起来共计4个字节。于是可以算出:144*180*4=103680字节

现在将图片移至drawable-hdpi中,运行后查看效果:

输出结果如下:
width=288,height=360,byteCount=414720
哇哈,看到没有呢?——图片的宽和高都翻倍了,图片所占的内存大小也随之变大了4倍。

继续尝试,在将图片移至drawable-ldpi中,运行后查看效果:

输出结果如下:
width=576,height=720,byteCount=1658880
这就更明显了,图片的宽和高都变大了4倍,图片所占的内存大小也随之变大了16倍。

嗯哼,如果将图片放入drawable-mdpi,drawable-xhdpi,drawable-xxxhdpi中也会发现类似的现象:图片的宽高及其所占内存在按照比例放大或者缩小,详情请参见下图

既然已经看到了这个现象,那就再从源码(Lollipop 5.0)角度来看看当加载drawable中的图片时的具体实现

  1. 调用BitmapFactory中的的decodeResource()加载drawable文件夹里的图片,源码如下:

    <code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Bitmap <span class="hljs-title">decodeResource</span>(Resources res, <span class="hljs-keyword">int</span> id, Options opts) {Bitmap bm = <span class="hljs-keyword">null</span>;InputStream is = <span class="hljs-keyword">null</span>;<span class="hljs-keyword">try</span> {<span class="hljs-keyword">final</span> TypedValue value = <span class="hljs-keyword">new</span> TypedValue();is = res.openRawResource(id, value);bm = decodeResourceStream(res, value, is, <span class="hljs-keyword">null</span>, opts);} <span class="hljs-keyword">catch</span> (Exception e) {} <span class="hljs-keyword">finally</span> {<span class="hljs-keyword">try</span> {<span class="hljs-keyword">if</span> (is != <span class="hljs-keyword">null</span>) is.close();} <span class="hljs-keyword">catch</span> (IOException e) {}}<span class="hljs-keyword">if</span> (bm == <span class="hljs-keyword">null</span> && opts != <span class="hljs-keyword">null</span> && opts.inBitmap != <span class="hljs-keyword">null</span>) {<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Problem decoding into existing bitmap"</span>);}<span class="hljs-keyword">return</span> bm;
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li></ul>

    在该方法中第6行调用openRawResource()后,value中就保存了该资源所在文件夹的destiny,这点和刚才的讲解是一致的,不再赘述。在此之后,继续执行decodeResourceStream()

  2. 调用decodeResourceStream( )方法

    <code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Bitmap <span class="hljs-title">decodeResourceStream</span>(Resources res, TypedValue value,InputStream is, Rect pad, Options opts) {<span class="hljs-keyword">if</span> (opts == <span class="hljs-keyword">null</span>) {opts = <span class="hljs-keyword">new</span> Options();}<span class="hljs-keyword">if</span> (opts.inDensity == <span class="hljs-number">0</span> && value != <span class="hljs-keyword">null</span>) {<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> density = value.density;<span class="hljs-keyword">if</span> (density == TypedValue.DENSITY_DEFAULT) {opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (density != TypedValue.DENSITY_NONE) {opts.inDensity = density;}}       <span class="hljs-keyword">if</span> (opts.inTargetDensity == <span class="hljs-number">0</span> && res != <span class="hljs-keyword">null</span>) {opts.inTargetDensity = res.getDisplayMetrics().densityDpi;}     <span class="hljs-keyword">return</span> decodeStream(is, pad, opts);
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>

    在该方法中有两个非常重要的操作。

    第一步:
    为opts.inDensity赋值,请参见代码第6-13行。

    经过操作opts.inDensity会被赋值为120、160、240、320、480、640中的一个值

    第二步:
    为opts.inTargetDensity赋值,请参见代码第14-16行。

    经过操作opts.inTargetDensity会被赋值为手机屏幕的densityDpi

  3. 调用decodeStream()方法
    在该方法中会调用decodeStreamInternal();它又会继续调用nativeDecodeStream( ),该方法是native的;在BitmapFactory.cpp可见这个方法内部又调用了doDecode()它的核心源码如下:

    <code class="language-C hljs cpp has-numbering"><span class="hljs-keyword">static</span> jobject doDecode(JNIEnv*env,SkStreamRewindable*stream,jobject padding,jobject options) {
    ......
    <span class="hljs-keyword">if</span> (env->GetBooleanField(options, gOptions_scaledFieldID)) {<span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> density = env->GetIntField(options, gOptions_densityFieldID);<span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);<span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);<span class="hljs-keyword">if</span> (density != <span class="hljs-number">0</span> && targetDensity != <span class="hljs-number">0</span> && density != screenDensity) {scale = (<span class="hljs-keyword">float</span>) targetDensity / density;}
    }
    }
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">bool</span> willScale = scale != <span class="hljs-number">1.0f</span>;
    ......
    SkBitmap decodingBitmap;
    <span class="hljs-keyword">if</span> (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
    <span class="hljs-keyword">return</span> nullObjectReturn(<span class="hljs-string">"decoder->decode returned false"</span>);
    }
    <span class="hljs-keyword">int</span> scaledWidth = decodingBitmap.width();
    <span class="hljs-keyword">int</span> scaledHeight = decodingBitmap.height();
    <span class="hljs-keyword">if</span> (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = <span class="hljs-keyword">int</span>(scaledWidth * scale + <span class="hljs-number">0.5f</span>);
    scaledHeight = <span class="hljs-keyword">int</span>(scaledHeight * scale + <span class="hljs-number">0.5f</span>);
    }
    <span class="hljs-keyword">if</span> (willScale) {
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> sx = scaledWidth / <span class="hljs-keyword">float</span>(decodingBitmap.width());
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> sy = scaledHeight / <span class="hljs-keyword">float</span>(decodingBitmap.height());
    ......
    SkPaint paint;
    SkCanvas canvas(*outputBitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(decodingBitmap, <span class="hljs-number">0.0f</span>, <span class="hljs-number">0.0f</span>, &paint);
    }
    ......
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src=".png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul>

    主要步骤分析如下:

    第一步:
    获取opts.inDensity的值赋给density,请参见代码第4行。

    第二步:
    获取opts.inTargetDensity的值赋给targetDensity,请参见代码第5行。

    第三步:
    计算缩放比scale,请参见代码第8行。

    从这里也可以看出,这个缩放比scale就等于opts.inTargetDensity/opts.inDensity

    第四步:
    得到图片原始的宽和高,请参见代码第18-19行。

    请注意此时的图像在frameworks/base/core/jni/android/graphics/BitmapFactory.cpp中是一个SkBitmap

    第五步:
    依据scale计算缩放后SkBitmap的宽和高,请参见代码第21-22行。

    第六步:
    计算SkBitmap的宽和高缩放的倍数,请参见代码第25-26行。

    在此得到宽的缩放倍数为sx, 高的缩放倍数为sy

    第七步:
    依据sx和sy缩放canvas,请参见代码第30行。

    第八步:
    画出图片,请参见代码第31行。

    至此终于完成了doDecode()版的天龙八部。在梳理了整个过程之后不难发现:对于图片缩放的比例其实还是scale即opts.inTargetDensity/opts.inDensity起了决定性的作用。

    好吧,现在回过头瞅瞅我掉进去的那个坑:我的手机华为P7其dpi值为480,有一张图片我把它放到drawable-xxhdpi里在手机上显示出来是不失真的,非常合适;但是错放到了drawable-xhdpi(其TypedValue的value值为320)后再次显示时发现图片被放大了,而且放大了480/320=1.5倍。既然图片被放大了那么该图片所占的内存当然也变大了。

    这也就解释了我们有时遇到的类似困惑:为什么图片放在drawable-xxhdpi是正常的,但是放到drawable-mdpi后图片不仅仅放大失真而且所占内存也大幅增加了。


后语

至此,对于Andoid中常见的度量单位已经介绍完了;关于drawable的加载原理也做了一个完整分析。

在明白这些之后,我们再去谈多分辨率的适配也就会多了一份从容和自信。

更多推荐

探究drawable图片的加载原理和缩放规律

本文发布于:2024-03-15 06:44:54,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1738315.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:缩放   加载   规律   原理   图片

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!