Android中的Drawable表示的是一种可以在Canvas上进行绘制的抽象的概念,它的种类有很多,最常见的图片和颜色都可以是一个Drawable。我们从这篇文章可以了解一些常见的Drawable,和一些drawable使用的技巧,最后会有自定义drawable的示例。从基础到进阶全面了解drawable。
Drawable基础知识介绍
Drawable简介
Drawable表示一种图像的概念,但是它们又不完全是图片,通过颜色也可以构造出各式各样的图像效果。Drawable是一个抽象类,它是所有Drawable对象的基类。它的层次关系下图可以看到:
可以看到它是一个大家族。我们挑选几个来了解,如果想要了解每一个可以到官网去查看。
Drawable分类
BitmapDrawable
这是我们最常使用的Drawable:位图图片。它表示一张图片,实际开发中,我们都是直接使用R.drawable.xxx来引用。注意我们可以通过XML的方式来描述它。如下所示:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@[package:]drawable/drawable_resource"
android:antialias=["true" | "false"]
android:dither=["true" | "false"]
android:filter=["true" | "false"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"]
android:mipMap=["true" | "false"]
android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />
- android:src
可绘制对象图片资源ID。 - android:antialias
启动或停止扛锯齿。开启后图片会更加平滑,同时会在一定程度降低图片清晰度,但是这个可以忽略。 - android:dither
开启或关闭抖动效果。当为土的像素配置和屏幕不同时,开启抖动可以让图片显示不会过于失真。 - filter
是否开启位图过滤。当位图收缩或拉伸以使其外观平滑。 - gravity
定义位图的权重。当位图小于容器时,可以绘制对象在容器中放置的位置。不同选项可以使用”|”来组合。
值 | 说明 |
---|---|
top | 将对象放在其容器顶部,不改变其大小。 |
bottom | 将对象放在其容器底部,不改变其大小。 |
left | 将对象放在其容器底部,不改变其大小。 |
right | 将对象放在其容器右边缘,不改变其大小。 |
center_vertical | 将对象放在其容器的垂直中心,不改变其大小。} |
fill_vertical | 按需要扩展对象的垂直大小,使其完全适应其容器。 |
center_horizontal | 将对象放在其容器的水平中心,不改变其大小。 |
fill_horizontal | 按需要扩展对象的水平大小,使其完全适应其容器。 |
center | 将对象放在其容器的水平和垂直轴中心,不改变其大小。 |
fill | 按需要扩展对象的垂直大小,使其完全适应其容器。这是默认值。 |
clip_vertical | 可设置为让子元素的上边缘和/或下边缘裁剪至其容器边界的附加选项。裁剪基于垂直重力:顶部重力裁剪上边缘,底部重力裁剪下边缘,任一重力不会同时裁剪两边。 |
clip_horizontal | 可设置为让子元素的左边和/或右边裁剪至其容器边界的附加选项。裁剪基于水平重力:左边重力裁剪右边缘,右边重力裁剪左边缘,任一重力不会同时裁剪两边。 |
-android:mipMap
启用或停止mipmap提示。一种图像相关的处理技术,也叫纹理映射。默认值为false。
Android 在 API level 17 加入了 mipmap 技术,对 bitmap 图片的渲染支持 mipmap 技术,来提高渲染的速度和质量。Google建议把启动图标放到mipmap下面,其他图片还是放在drawable目录下面。android 通过 mipmap 技术提前对按缩小层级生成图片预先存储在内存中,这样就提高了图片渲染的速度和质量。
- android:tileMode
定义平铺模式。有如下几个值:
值 | 说明 |
---|---|
disabled | 不平铺位图。这是默认值。 |
clamp | 当着色器绘制范围超出其原边界时复制边缘颜色 |
repeat | 水平和垂直重复着色器的图像。 |
mirror | 水平和垂直重复着色器的图像,交替镜像图像以使相邻图像始终相接。 |
repeat效果:
mirror效果:
clamp效果:
这里的clamp四周没有向四周扩散,这个还有待进一步了解。
NinePatchDrawable
我们开发中使用到的.9图片,在其中可定义当视图中的内容超出正常图像边界时 Android 缩放的可拉伸区域。同样也可以通过XML来定义,只是开头以nine-patch来写。属性和BitmapDrawable类似。
ShapeDrawable
官方描述是形状可绘制的drawable,我们同样也可以理解为颜色可绘制的drawable。它既可以是纯色的图形,也可以是自定义形状的具有渐变的图形。它的实体类是GradientDrawable。
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient
android:angle="integer"
android:centerX="float"
android:centerY="float"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["true" | "false"] />
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size
android:width="integer"
android:height="integer" />
<solid
android:color="color" />
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>
- android:shape
定义形状的类型:
值 | 描述 |
---|---|
rectangle | 填充包含视图的矩形。这是默认形状。 |
oval | 适应包含视图尺寸的椭圆形状。 |
line | 跨越包含视图宽度的水平线。此形状需要 元素定义线宽。 |
ring | 环形。 |
针对ring属性,有5个特殊的属性如下:
值 | 描述 |
---|---|
android:innerRadius | 环内部(中间的孔)的半径,以尺寸值或尺寸资源表示。 |
android:innerRadiusRatio | 内半径占整个Drawable宽度的比率,默认值是9.如果为n,那么内半径=宽度/n |
android:thickness | 圆环的厚度,即外半径减去内半径的大小,和android:thicknessRatio同时存在时,以android:thickness为准 |
android:thicknessRatio | 厚度占整个Drawable宽度的比例,默认值为3,如果为n,那么厚度=宽度/n |
android:useLevel | 如果这用作 LevelListDrawable,则此值为“true”。这通常应为“false”,否则形状不会显示。 |
corners:
它表示shape的四个角的角度。这个角度指圆角的程度,有如下5个属性。
-android:radius—为四个角同时设定相同的角度,优先级较低,会被其他四个属性覆盖- android:topLeftRadius—设定左上角的角度
- android:topRightRadius—设定右上角的角度
- android:bottomLeftRadius—设定左下角的角度
- android:bottomRightRadius—设定右下角的角度
gradient:
它与solid标签是互相排斥的,其中solid表示纯色填充,而gradient表示渐变效果,属性如下:- android:angle—渐变的角度,默认为0,其值必须为45的倍数,0表示从左到右,90表示从下到上。
- android:centerX—渐变的中心点的横坐标
- android:centerY—渐变的中心点的纵坐标
- android:startColor—渐变的起始色
- android:centerColor—渐变的中间色
- android:endColor—渐变的结束色
- android:gradientRadius—渐变半径,仅当android:type=”radial”有效
- android:useLevel—一般为false,当Drawable作为LevelListDrawable使用时为true
- android:type—渐变的类别,有linear(线性渐变),radial(径向渐变,起始颜色为中心颜色)sweep(流线型渐变)。
padding
包含视图元素的内边距。有四个属性:android:left,android:top,android:right,android:bottom。size
shape的大小,有两个属性:android:height和android:widthsolid
表示填充颜色stroke
shape的描边.- android:width—瞄边的宽度
- android:color—瞄边的颜色
- android:dashWidth—组成虚线的线段的宽度
- android:dashGap—组成虚线的线段之间的间隔。
注意:dashWidth和dashGap有任何一个为0,那么虚线将不能生效。
还有很多drawable,我们这里不一一描述,官网介绍
Drawable适配
我们在开发的时候res目录下面有drawable-dpi ,drawable-mdpi ,drawable-xhdpi ,drawable-xxhdpi ,drawable-xxxhdpi,我们清楚这是Android设备为了适配多种设备而在上面可以选择当前设备的dpi来显示图片信息的。而这其中有很多细微的知识点需要我们了解。
基本单位介绍
- px
像素:英文单词pixel的缩写,屏幕上面的最小的单位。像素都是整数。我们平常说的分辨率如480X800就是说的像素,横向480个像素,纵向800个像素。 - in
英寸:是屏幕的物理尺寸。每英寸等于2.54厘米。通常我们说的5(英)寸就是指这个单位。这个尺寸是屏幕的对角线的长度,如果4英寸,表示手机对角线长度为4*2.54厘米。 - dpi
每英寸包含的像素个数。屏幕的密度单位。例如系统确定dpi:比如320X480分辨率的手机,宽2英寸,高3英寸, 每英寸包含的像素点的数量为320/2=160dpi(横向)或480/3=160dpi(纵向),160就是这部手机的dpi,横向和纵向的这个值都是相同的,原因是大部分手机屏幕使用正方形的像素点。 - density
屏幕密度density:density=dpi/160。 - 实际密度和系统密度
实际密度=对角线像素/英寸数,通过后续计算所得。
系统密度dpi(注意和上面的density区分)为hdpi,mdpi,hdpi等这些系统设定的一个初始的固定密度。因为手机碎片化,有些手机的实际密度并不和固定的密度数值一样,只是遵循初始固定密度在那个范围内确定手机为hdpi还是别的dpi。 - dp
密度无关的像素:我们开发中常写的一种单位,也即dip(注意和上面的dpi区分)。Android所特有的单位,在不同的设备上面显示的长度比例是相同的。那么这其中一定有一个dp根据不同的密度转换为px的机制来保证长度比例相同。在屏幕密度dpi=160的屏幕上面为基准1dp=1px。具体的dpi和屏幕密度以及dp和像素的比例值如下表:
密度 | ldpi | mdpi | hdpi | xhdpi | xxhdpi | xxxhdpi |
---|---|---|---|---|---|---|
dpi范围 | 0-120 | 120-160 | 160-240 | 240-320 | 320-480 | 480-640 |
px/dp | 0.75 | 1 | 1.5 | 2 | 3 | 4 |
注意:在Android中上面的屏幕密度density=dpi/160既为0.75,1,1.5,2,3,4这个是屏幕密度density,而120,160,240这些事屏幕密度dpi
- sp
和dp换算为px算法一样,为适配多个屏幕而写,一般用于设置字体大小,和dp的区别为它可以根据用户的字体偏好设置来缩放。
图片放在哪个文件夹
我们在开发中有多个文件夹可以存放图片,我们明白android提供多个文件夹来是为了我们可以适配多个屏幕密度的手机,但是我们在实际开发的时候一般只会选择一个文件夹来房我们的图片,目的显而易见是为了减少apk的大小。我们如果在每个文件夹下面房一套那可增大很多的空间。(当然使用webp格式的图片可以大大减少图片的大小,我们一般选择一套来就足够。但是我们得明白我们选择哪一套,图片一般放在哪。)
我们可以通过如下两种方式得到屏幕密度
- 使用代码获取手机的屏幕密度:
float xdpi = getResources().getDisplayMetrics().xdpi;//x轴dpi
float ydpi = getResources().getDisplayMetrics().ydpi;//y轴dpi
float densityDpi = getResources().getDisplayMetrics().densityDpi;//手机dpi
//由手机dpi/160得到系统屏幕密度
float dpi = getResources().getDisplayMetrics().density;
- 使用adb命令查看:
adb shell wm size:获取屏幕分辨率
adb shell wm density:获取屏幕密度
测试得到我的模拟器使用的分辨率为320,根据上表知道分辨率为720*1280,图片应该放在xhdpi下。选择一张大小为270*400的图片放到drawable-xhdpi目录下面。因为图片的大小和应用运行的内存有直接的关系,图片加载到内存,我们也记录一下各个目录下面内存占用的大小。
- 在drawable-xhdpi目录下面:
内存占用大小:
- 接下来把图片移到drawable-xxhdpi目录下面看看效果:
- 图片放到drawable-hdpi目录下面:
我们似乎可以看出一些规律:
我们的屏幕密度为xhdpi,图片放在这个目录上面xhdpi目录下面,内存为4.49。图片放到xxhdpi目录下面大小为4.32。hdpi占用4.92。因为当手机分辨率下面有图片时候显示图片的原始尺寸大小。当图片放在当前屏幕密度高的文件夹的时候Android会把图片进行压缩,故内存也会变小。当图片放在比手机屏幕密度低的文件夹下面,取出图片的时候会进行放大。这个缩放的大小关系为:缩放比率=当前手机屏幕密度/文件夹最大屏幕密度。使用原图片乘以这个比率就是放在低密度hdpi下面图片放大的大小 。
有一个要了解的概念为 drawable-nodpi文件夹,这个文件夹是一个密度无关的文件夹,放在这里的图片系统就不会对它进行自动缩放,原图片是多大就会实际展示多大。Android找图片资源的顺序为查找当前手机密度下面有没有,如果没有就到高密度文件夹下面找,高文件夹是从第到高规则查找。如果没有找到就到nodpi这个文件夹下面查找。接下来没有找到就到低屏幕密度文件夹下面找,低密度文件夹查找顺序为从高到低查找。这个过程好像以当前密度为基准向两边逐渐查找为止。
- 我们的图片资源放在xxhdpi文件夹下面最好,因为兼容低密度的手机缩放图片资源。而且还节约内存。高密度的放大就只有xxxhdpi密度的,这个密度的手机比较少。
这里感谢郭霖总结出来的分析:查看
以及对这个原理的代码底层分析:查看