“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击检查活动详情”
引言
关于 Drawable ,一向没有专门记录,日常开发中,也是属于忘记了再搜一下。主要是运用程度有限(仅仅仅仅shape或许 layer 等冰山一角),另一方面是 Android 对其的高度笼统,导致从没去重视过细节,然后关于 Drawable 没有真实的了解其规划与存在的含义。
反而是偶然一次发现其他同学的运用,才了解了自己的狭窄,为此,怀着无比惭愧的心情,写下此篇,与君共勉。
鉴于此,本篇将完好的描述开发中常见各种 Drawable ,以及在工程化项目的布景下,怎样更好的运用。整体难度较低,不涉及源码,适合轻松阅览。
来者何人
2022的今日,随意问一个Android开发,Drawable 是什么?
比方我。他(她)肯定会告诉你(鄙视的目光),你si不si傻,
Drawable都不知道,Drawable,Drawble,Drawable不便是…不便是常常用来设置的图画吗?(不确定口气,好像说的不完好)
上述说的有错吗,也没错。嗯,但总觉得差点什么,过于简略?细心的你肯定会觉得没这么简略。
那究竟什么是Drawable?
Drawable表明的是一种能够在Canvas上进行制作的笼统概念。人话便是 便是指可在屏幕上制作的图形。
就这?就这?就这?
这说了个啥,水水水,一天就知道水文章?
嗯,在开发视点来看,Drawable 是一个笼统的类,用来表明能够制作在屏幕上制作的图形。咱们常见有许多种 Drawable ,比方Bitmapxx,Colorxxx,Shapexxx,它们一般都用于表明图画,但严格上来说,又不全是图画。
后半句用人话怎样了解呢?
关于一般的图形或图片,咱们肯定没法更改,由于其现已固定了(资源文件)。
但是关于
Drawable,虽然某种程度上也是图形(矢量资源),但其具有处理或制作详细显现逻辑的办法。也便是说,这是一个支撑修改的图形,比方咱们能够把一张图塞给了BitmapDrawable,但仍然能够做二次调整,比方拉伸一下,改一下方位,给这张图上再添加点其他什么东西。或许也能够了解为这是一个简化版的View,只不过它更简易,目的朴实。其无法像View相同接纳事情或许和用户交互,其更像一个制作板,指哪打哪,仅作为显现运用。
当然除了简略的绘图,Drawable 还提供了许多通用api,使得咱们能够与正在制作的内容进行交互,然后更加完善。
相应的,Drawable 内部其实也有自己的宽高、经过 intrinsicWidth、intrinsicHeight 即可获取。需求留意的是:
-
Drawable的宽高不等于其展现时的巨细,咱们能够以为Drawable不存在巨细的概念,由于其用于View布景时,其会被拉伸至View的同等巨细。 - 也并不是一切的
Drawable都有内部宽高,比方,由一个图片所构成的Drawable,其相应的宽高也便是图片的宽高,而由色彩所构成的Drawable,相应的内部也不存在宽高。
Drawable的种类
如下所示,Drawable有如下类型:
好家伙,这也太多了吧,而且后续还会越来越多。
当然这么多,咱们一般其实底子不可能全部用上,常见的有如下几种类别:
无状况
-
BitmapDrawable
<<bitmap用于将图片转为BitmapDrawable;
-
ShapeDrawable
<<shape经过色彩来结构Drawable;
-
VectorDrawable
<<vector矢量图,Android5.0及以上支撑。便于在缩放过程中保证显现质量,以及一个矢量图支撑多个屏幕,削减apk巨细;
-
TransitionDrawable
<<transition用于完成Drawable间的淡入淡出作用;
-
InsetDrawable
<<inset用于将其他Drawable内嵌到自己傍边,并能够在四周留出必定的距离。当一个View期望自己的布景比实践的区域小时,能够选用其来完成。
有状况
-
StateListDrawable
<<selector用于有状况交互时的View设置,比方 按下时 的布景,松开时 的布景,有焦点时的布景灯;
-
LevelListDrawable
<<level-list依据等级(level)来切换不同的
Drawble。在View中能够经过设置 setImageLevel 更改不同的Drawable; -
ScaleDrawable
<<scale依据不同的等级(level)指定
Drawable缩放到必定份额; -
ClipDrwable
<<clip依据当时等级(level)来裁剪
Drawable;
常见的Drawable
BitmapDrawable
常见运用场景
用于表明一张图片,用于设置 bitmap 在 BitmapDrawable 区域内的制作办法时运用,如水平平铺或许竖直平铺以及扩展铺满。
xml中的标签:
常见的特点有如下:
-
android:src
资源id
-
android:antialias
敞开图片抗锯齿,用于让图片变得平滑,一起抗锯齿也会必定程度上下降图片清晰度,不过幅度几乎无法感知;
-
android:dither
敞开颤动作用,为低像素机型做的主动降级显现,保证显现作用。比方当时图片五颜六色形式为ARGB8888,而设备屏幕色彩形式为RGB555,此刻敞开颤动就能够避免图片显现失真;
-
android:filter
过滤作用。在图片尺度被拉伸或许紧缩时,有助于保持显现作用;
-
android:gravity
当时图片小于容器尺度时,此选项便于对图片进行定位,当titleMode敞开时,此特点将失效;
-
android:mipMap
纹理映射开关,主要是为了应对图片巨细缩放处理时,Android能够经过纹理映射技术提前按缩小的层级生成图片预存储在内存中,以此来进步速度与图片质量。默许情况下,mipmap文件夹里的默许敞开此开关,drawable默许封闭。但需求留意,此开关只能主张体系敞开此功用,至于终究是否真实敞开,取决于体系。
-
android:tileMode
用于设置图片的平铺形式,有以下几个值:[
disabled、clamp、repeat、mirror]-
disabled(默许值) 封闭平铺形式 -
clamp图片四周的像素会扩展到周围区域 -
repeat水平缓竖直方向上的平铺作用 -
mirror在水平缓竖直方向的的镜面投影作用
-
示例代码:
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_doge)
val drawable = BitmapDrawable(bitmap).apply {
setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
isFilterBitmap = true
gravity = Gravity.CENTER
setMipMap(true)
setDither(true)
}
ivDrawable.background = drawable
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"
android:filter="true"
android:gravity="center"
android:mipMap="true"
android:src="@drawable/test"
android:tileMode="repeat" />
ShapeDrawable
常见运用场景
经过色彩来结构图形,作为布景描边或许布景色突变时运用,能够说是最常见的一种 Drawable。
xml中的标签:
常见的特点如下:
-
shape
表明图形的形状,如下四个选项:
rectangle(矩形)、oval(椭圆)、line(横线)、ring(圆环) -
corners
表明shape的四个角的视点,只适用于矩形shape。
- android:
radius为四个角设置相同的视点 - android:
topLeftRadius设置左上角视点 - android:
bottomLeftRadius设置左下角视点 - android:
bottomRightRadius设定右下角的视点
- android:
-
gradient
用于表明突变作用,与 标签互斥(其表明纯色填充)
- android:
angle突变的视点,默许为0,其值必须为45的倍数, 0表明从左向右,90表明从下到上 - android:
centerX突变中心点的横坐标 - android:
centerY突变中心点纵坐标 - android:
startColor突变的开端色 - android:
centerColor突变的中心点 - android:
endColor突变的结束色 - android:
gradientRadius突变半径,仅当android:type=“radial”时有用 - android:
useLevel是否运用等级区分,在StateListDrawable时有用,默许 false - android:
type突变类型,linear(线性突变)、radial(径向突变)、sweep
- android:
-
solid 表明纯色填充
-
stroke 用于设置描边
- android:
width描边宽度 - android:
color描边色彩 - android:
dashWidth描边虚线时的宽度 - android:
dashGap描边虚线时的间隔
- android:
-
padding
用于表明空白,其代表了在View中运用时的空白。但其在shape中并没有什么作用,能够在
layer-list中进行运用。 -
size
用于表明
shape的 固有巨细 ,但其不代表shape终究显现的巨细。由于关于shape来说,其没有宽/高的概念,由于其终究被设置到View上作为布景时,其会被主动拉伸或缩放。但作为drawable,它拥有着固有宽/高,即经过getIntrinsicWidth,getIntrinsicHeight获取。关于某些Drawable而言,比方BitMapDrawable时,其宽高便是图片巨细;而关于shape时,其就不存在巨细,默许为-1。当然你也能够经过 size 设置巨细,但其终究代表的是shape的固有宽高,作为布景时其并不是终究显现时的宽高。
示例如下:
LayerDrawable
表明一种层次化的调集 drawable ,一般常见于需求多个 Drawable 叠加 摆放作用时运用。
一个 layer-list 中能够包括多个 item ,每个item表明一个 Drawable ,其常用的特点 android:top,left,right,bottom 。相当于相对View的 上下左右 偏移量,单位为像素。此外也能够经过 Drawable 引用一个已有的 Drwable 资源。
xml中的标签:
示例如下:
StateListDrawable
用于为不同的 View状况 引用不同的 Drawable ,比方在View 按下 时,View 禁用 时等。
xml中的标签:
常用的特点如下:
-
constantSize
表明其固有巨细是否跟着状况而改动。
由于每次切换状况时,都会伴跟着
Drawable的改动,假如此刻不是用于布景,则假如Drawable的固有巨细不一致,则会导致StateListDrawable的巨细发生变化。假如此值为 true ,则表明当时StateDrawable的固有巨细是当时其内部一切Drawable中最大值。反之,则依据状况决定; -
android:dither
是否敞开颤动作用,用于在低质量屏幕上取得较好的显现作用;
-
variablePadding
表明padding是否跟着状况而改动,true表明跟随状况而决定,取决于当时显现的drawable,false则选取drawable调集中padding最大值。
示例如下:
![]() |
![]() |
|---|
LevelListDrawable
用于依据不同的等级表明一个 Drawable 调集。
默许等级范围为0,最小为0,最大为10000,能够在View中运用 Drawable 然后设置相应的 level 切换不同的 Drawable。假如这个drawable被用于ImageView 的 前景Drawable,还能够经过 ImageView.setImageViewLevel 来切换。
xml中的标签:
示例代码如下:
在代码中即可经过 setLevel切换。
view.background.level = 10
view.background.level = 200
TransitaionDrawable
用于完成两个 Drawable 之间的淡入淡出作用。
xml中的标签:
示例如下:
![]() |
![]() |
|---|
InsetDrawable
用于将其他 Drawable 内嵌到自己傍边,并能够在四周留出必定的距离。比方当某个 View 期望自己的布景比自己的实践区域小时,能够选用这个 Drawable ,当然选用 LayerDrawable 也能够完成。
xml中的标签:
其特点分别如下:
- android:inset 表明四边内凹巨细
- android:insetTop 表明顶部内凹巨细
- android:insetLeft 表明左面内凹巨细
- android:insetBottom 表明底部内凹巨细
- android:insetRight 表明右边内凹巨细
ScaleDrawable
用于依据等级(level)将指定的 Drawable 缩放到必定份额。
xml中的标签:
相应的特点如下所示:
-
android:scaleGravity
相似于与android:gravity
-
android:scaleWidth
指定宽度的缩放份额(相关于原drawable缩放了多少)
-
android:scaleHeight
指定高度的缩放份额(相关于原drawable缩放了多少)
-
android:level(minSdk>=
24)指定缩放等级,默许为0,即最小,最高10000,此值越大,终究显现的drawable越大
需求留意的是,当level为0时,其不会显现,所以咱们运用ScaleDrawable时,需求在代码中,将 drawable.level 调为1。
示例如下:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/level2_drawable"
android:level="1"
android:scaleWidth="70%"
android:scaleHeight="70%"
android:scaleGravity="center" />
ClipDrawable
用于依据当时等级(level)来裁剪另一个 Drawable。
xml中的标签:
详细特点如下:
-
android:drawable
需求裁剪的drawable
-
android:clipOrientation
裁剪方向,有水平(horizontal)、竖直(vertical) 两种
-
android:level(minSdk>=
24)设置等级,为0时表明彻底裁剪(即躲藏),值越大,裁剪的越小。最大值10000(即不裁剪,原图)。
-
android:gravity
参数 含义 top 内部drawable坐落容器顶部,不改动巨细。ps: 竖直裁剪时,则从底部开端裁剪。 bottom 内部drawable坐落容器底部,不改动巨细。ps: 竖直裁剪时,则从顶部开端裁剪。 left(默许值) 内部drawable坐落容器底部,不改动巨细。ps: 水平裁剪时,则从顶部开端裁剪。 right 内部drawable坐落容器右边,不改动巨细。ps: 水平裁剪时,从右边开端裁剪。 start 同left end 同right center 使内部drawable在容器中居中,不改动巨细。 ps:竖直裁剪时,从上下一起开端裁剪;水平裁剪时,从左右一起开端。 center_horizontal 内部的drawable在容器中水平居中,不改动巨细。 ps:水平裁剪时,从左右两边一起开端裁剪。 center_vertical 内部的drawable在容器中垂直居中,不改动巨细。 ps:竖直裁剪时,从上下两边一起开端裁剪。 fill 使内部drawable填充溢容器。 ps:仅当level为0(0表明ClipDrawable被彻底裁剪,即不可见)时,才具有裁剪行为。 fill_horizontal 使内部drawable在水平方向填充容器。 ps:假如水平裁剪,仅当level为0时,才具有裁剪行为。 fill_vertical 使内部drawable在竖直方向填充容器。 ps:假如垂直裁剪,仅当level为0时,才具有裁剪行为。 clip_horizontal 竖直方向裁剪。 clip_vertical 竖直方向裁剪。
示例如下:
自定义Drawable
通常情况下,咱们往往用不到自定义 Drawable ,主要源于Android现已提供了许多通常会用到的功用,不过了解自定义 Drawable 在某些场景下能够十分便于咱们开发体会。
自定义 Drawable 也很简略,咱们只需求继承 Drawable 即可,然后完成:
-
draw()
完成自定义的制作。
假如要获取当时
Drawable制作的边界巨细,能够经过 getBounds() 获取;假如需求获取当时
Drawable的中心点,也能够经过 getBounds().exactCenterX() ,或许 getBounds().centerX() ,差异在于前者用于获取准确方位; -
setAlpha()
设置透明度;
-
setColorFilter()
设置滤镜作用;
-
getOpacity()
回来当时
Drawable的透明度;
比方画一个相似的 ProgressBar ,由于其是一个 Drawable ,所以能够用作任意的 View。
class CustomCircleProgressDrawable : Drawable(), Animatable {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rectF = RectF()
private var progress = 0F
private val valueAnimator by lazy(LazyThreadSafetyMode.NONE) {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 2000
repeatCount = Animation.INFINITE
interpolator = LinearInterpolator()
addUpdateListener {
progress = it.animatedValue as Float
invalidateSelf()
}
}
}
init {
paint.style = Paint.Style.STROKE
paint.strokeWidth = 10f
paint.strokeCap = Paint.Cap.ROUND
paint.color = Color.GRAY
start()
}
override fun draw(canvas: Canvas) {
var min = (min(bounds.bottom, bounds.right) / 2).toFloat()
paint.strokeWidth = min / 10
min -= paint.strokeWidth * 3
val centerX = bounds.exactCenterX()
val centerY = bounds.exactCenterY()
rectF.left = centerX - min
rectF.right = centerX + min
rectF.top = centerY - min
rectF.bottom = centerY + min
paint.color = Color.GRAY
canvas.drawArc(rectF, -90f, 360f, false, paint)
paint.color = Color.GREEN
canvas.rotate(360F * progress, centerX, centerY)
canvas.drawArc(rectF, -90F, 30F + 330F * progress, false, paint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
invalidateSelf()
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
override fun start() {
if (valueAnimator.isRunning) return
valueAnimator.start()
}
override fun stop() {
if (valueAnimator.isRunning) valueAnimator.cancel()
}
override fun isRunning(): Boolean {
return valueAnimator.isRunning
}
}
原理也很简略,咱们完成了 onDraw 办法,在其间使用 canvas 制作了两个圆环,其间前者是作为布景,后者不断地使用特点动画进行变化,而且不断旋转 canvas ,然后完成相似进度条的作用。
作用如下:
实践引荐
比方咱们现在有这样一个 View ,需求在左上角展现一个文字,布景是张图片,别的还有一个从顶部到下的半透明突变暗影。
如下图所示:
一般情况下,咱们肯定会一挥而就的写出如下代码。
上述写法没有问题,但其并不是一切场景的最引荐办法。比方这种样式此刻需求在 RecyclerView 中展现呢?
所以,此刻就能够使用 Drawable 简化咱们的 View 层级,改造思路如下:
如上所示,相关于最开端,咱们将布局层级由 3 层下降为了 1 层,关于功能的提升也将指数级上升。
现在有同学可能要问了,你的这个 View 很简略,自定义一个 Drawable 还好说,那 View 很复杂呢?难不成我真要纯纯自定义吗?
要回答这个问题,其实很简略,咱们要清晰 Drawable 的含义,其仅仅一个可制作的图画 。过于复杂的View,咱们能够将其拆分为多个层级,然后关于其间纯展现的View,运用 Drawable 下降其复杂度。
从某个视点来说,Drawable也能够是咱们自定义View的好帮手。
总结
合理使用 Drawable 会很大程度进步咱们的运用体会。相应的,关于布局优化,Drawable 也是一大利器。问题回到文章最开端,假如现在再问你。Drawable 究竟是什么? 怎样自定义一个 Drawable ? 怎样使用其做一些骚操作?我想,这都不是问题。
参阅
- Android艺术探索 – Android中的Drawable
- Android开发者 – 可制作资源
关于我
我是 Petterp ,一个 Android工程师 ,假如本文对你有所帮助,欢迎点赞支撑,你的支撑是我继续创造的最大鼓励!


















