“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击检查活动详情”

引言

关于 Drawable ,一向没有专门记录,日常开发中,也是属于忘记了再搜一下。主要是运用程度有限(仅仅仅仅shape或许 layer 等冰山一角),另一方面是 Android 对其的高度笼统,导致从没去重视过细节,然后关于 Drawable 没有真实的了解其规划与存在的含义。

反而是偶然一次发现其他同学的运用,才了解了自己的狭窄,为此,怀着无比惭愧的心情,写下此篇,与君共勉。

鉴于此,本篇将完好的描述开发中常见各种 Drawable ,以及在工程化项目的布景下,怎样更好的运用。整体难度较低,不涉及源码,适合轻松阅览。

来者何人

2022的今日,随意问一个Android开发,Drawable 是什么?

比方我。他(她)肯定会告诉你(鄙视的目光),你si不si傻,Drawable 都不知道,Drawable,DrawbleDrawable不便是…

不便是常常用来设置的图画吗?(不确定口气,好像说的不完好)

上述说的有错吗,也没错。嗯,但总觉得差点什么,过于简略?细心的你肯定会觉得没这么简略。

那究竟什么是Drawable?

Drawable 表明的是一种能够在Canvas上进行制作的笼统概念。人话便是 便是指可在屏幕上制作的图形。

就这?就这?就这?

这说了个啥,水水水,一天就知道水文章?

嗯,在开发视点来看,Drawable 是一个笼统的类,用来表明能够制作在屏幕上制作的图形。咱们常见有许多种 Drawable ,比方Bitmapxx,Colorxxx,Shapexxx,它们一般都用于表明图画,但严格上来说,又不全是图画

由浅入深、详解Android中Drawable的那些事

后半句用人话怎样了解呢?

关于一般的图形或图片,咱们肯定没法更改,由于其现已固定了(资源文件)。

但是关于 Drawable,虽然某种程度上也是图形(矢量资源),但其具有处理或制作详细显现逻辑的办法。也便是说,这是一个支撑修改的图形,比方咱们能够把一张图塞给了 BitmapDrawable ,但仍然能够做二次调整,比方拉伸一下,改一下方位,给这张图上再添加点其他什么东西。或许也能够了解为这是一个简化版的View,只不过它更简易,目的朴实。其无法像 View 相同接纳事情或许和用户交互,其更像一个制作板,指哪打哪,仅作为显现运用。

当然除了简略的绘图,Drawable 还提供了许多通用api,使得咱们能够与正在制作的内容进行交互,然后更加完善。

相应的,Drawable 内部其实也有自己的宽高、经过 intrinsicWidthintrinsicHeight 即可获取。需求留意的是:

  • Drawable 的宽高不等于其展现时的巨细,咱们能够以为 Drawable 不存在巨细的概念,由于其用于View布景时,其会被拉伸至View的同等巨细。
  • 也并不是一切的 Drawable 都有内部宽高,比方,由一个图片所构成的 Drawable ,其相应的宽高也便是图片的宽高,而由色彩所构成的Drawable ,相应的内部也不存在宽高。

Drawable的种类

如下所示,Drawable有如下类型:

由浅入深、详解Android中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

常见运用场景

用于表明一张图片,用于设置 bitmapBitmapDrawable 区域内的制作办法时运用,如水平平铺或许竖直平铺以及扩展铺满。

xml中的标签:

常见的特点有如下:

  • android:src

    资源id

  • android:antialias

    敞开图片抗锯齿,用于让图片变得平滑,一起抗锯齿也会必定程度上下降图片清晰度,不过幅度几乎无法感知;

  • android:dither

    敞开颤动作用,为低像素机型做的主动降级显现,保证显现作用。比方当时图片五颜六色形式为ARGB8888,而设备屏幕色彩形式为RGB555,此刻敞开颤动就能够避免图片显现失真;

  • android:filter

    过滤作用。在图片尺度被拉伸或许紧缩时,有助于保持显现作用;

  • android:gravity

    当时图片小于容器尺度时,此选项便于对图片进行定位,当titleMode敞开时,此特点将失效;

  • android:mipMap

    纹理映射开关,主要是为了应对图片巨细缩放处理时,Android能够经过纹理映射技术提前按缩小的层级生成图片预存储在内存中,以此来进步速度与图片质量。默许情况下,mipmap文件夹里的默许敞开此开关,drawable默许封闭。但需求留意,此开关只能主张体系敞开此功用,至于终究是否真实敞开,取决于体系。

  • android:tileMode

    用于设置图片的平铺形式,有以下几个值:[disabledclamprepeatmirror]

    • disabled (默许值) 封闭平铺形式
    • clamp 图片四周的像素会扩展到周围区域
    • repeat 水平缓竖直方向上的平铺作用
    • mirror 在水平缓竖直方向的的镜面投影作用

由浅入深、详解Android中Drawable的那些事

示例代码:

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 设定右下角的视点
  • 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
  • solid 表明纯色填充

  • stroke 用于设置描边

    • android:width 描边宽度
    • android:color 描边色彩
    • android:dashWidth 描边虚线时的宽度
    • android:dashGap 描边虚线时的间隔

    由浅入深、详解Android中Drawable的那些事

  • padding

    用于表明空白,其代表了在View中运用时的空白。但其在shape中并没有什么作用,能够在 layer-list 中进行运用。

  • size

    用于表明 shape固有巨细 ,但其不代表shape终究显现的巨细。由于关于shape来说,其没有宽/高的概念,由于其终究被设置到View上作为布景时,其会被主动拉伸或缩放。但作为drawable,它拥有着固有宽/高,即经过 getIntrinsicWidthgetIntrinsicHeight 获取。关于某些Drawable而言,比方BitMapDrawable时,其宽高便是图片巨细;而关于shape时,其就不存在巨细,默许为-1。当然你也能够经过 size 设置巨细,但其终究代表的是shape的固有宽高,作为布景时其并不是终究显现时的宽高。

示例如下:

由浅入深、详解Android中Drawable的那些事


LayerDrawable

表明一种层次化的调集 drawable ,一般常见于需求多个 Drawable 叠加 摆放作用时运用。

一个 layer-list 中能够包括多个 item ,每个item表明一个 Drawable ,其常用的特点 android:top,left,right,bottom 。相当于相对View的 上下左右 偏移量,单位为像素。此外也能够经过 Drawable 引用一个已有的 Drwable 资源。

xml中的标签:

示例如下:

由浅入深、详解Android中Drawable的那些事


StateListDrawable

用于为不同的 View状况 引用不同的 Drawable ,比方在View 按下 时,View 禁用 时等。

xml中的标签:

常用的特点如下:

  • constantSize

    表明其固有巨细是否跟着状况而改动。

    由于每次切换状况时,都会伴跟着 Drawable 的改动,假如此刻不是用于布景,则假如 Drawable 的固有巨细不一致,则会导致StateListDrawable 的巨细发生变化。假如此值为 true ,则表明当时 StateDrawable 的固有巨细是当时其内部一切 Drawable 中最大值。反之,则依据状况决定;

  • android:dither

    是否敞开颤动作用,用于在低质量屏幕上取得较好的显现作用;

  • variablePadding

    表明padding是否跟着状况而改动,true表明跟随状况而决定,取决于当时显现的drawable,false则选取drawable调集中padding最大值。

示例如下:

由浅入深、详解Android中Drawable的那些事
由浅入深、详解Android中Drawable的那些事

LevelListDrawable

用于依据不同的等级表明一个 Drawable 调集。

默许等级范围为0,最小为0,最大为10000,能够在View中运用 Drawable 然后设置相应的 level 切换不同的 Drawable。假如这个drawable被用于ImageView 的 前景Drawable,还能够经过 ImageView.setImageViewLevel 来切换。

xml中的标签:

示例代码如下:

由浅入深、详解Android中Drawable的那些事

在代码中即可经过 setLevel切换。

 view.background.level = 10
 view.background.level = 200

TransitaionDrawable

用于完成两个 Drawable 之间的淡入淡出作用。

xml中的标签:

示例如下:

由浅入深、详解Android中Drawable的那些事
由浅入深、详解Android中Drawable的那些事

InsetDrawable

用于将其他 Drawable 内嵌到自己傍边,并能够在四周留出必定的距离。比方当某个 View 期望自己的布景比自己的实践区域小时,能够选用这个 Drawable ,当然选用 LayerDrawable 也能够完成。

xml中的标签:

其特点分别如下:

  • android:inset 表明四边内凹巨细
  • android:insetTop 表明顶部内凹巨细
  • android:insetLeft 表明左面内凹巨细
  • android:insetBottom 表明底部内凹巨细
  • android:insetRight 表明右边内凹巨细

由浅入深、详解Android中Drawable的那些事


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 竖直方向裁剪。

示例如下:

由浅入深、详解Android中Drawable的那些事

由浅入深、详解Android中Drawable的那些事


自定义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 ,然后完成相似进度条的作用。

作用如下:

由浅入深、详解Android中Drawable的那些事

实践引荐

比方咱们现在有这样一个 View ,需求在左上角展现一个文字,布景是张图片,别的还有一个从顶部到下的半透明突变暗影。

如下图所示:

由浅入深、详解Android中Drawable的那些事

一般情况下,咱们肯定会一挥而就的写出如下代码。

由浅入深、详解Android中Drawable的那些事

上述写法没有问题,但其并不是一切场景的最引荐办法。比方这种样式此刻需求在 RecyclerView 中展现呢?

所以,此刻就能够使用 Drawable 简化咱们的 View 层级,改造思路如下:

由浅入深、详解Android中Drawable的那些事

如上所示,相关于最开端,咱们将布局层级由 3 层下降为了 1 层,关于功能的提升也将指数级上升。

现在有同学可能要问了,你的这个 View 很简略,自定义一个 Drawable 还好说,那 View 很复杂呢?难不成我真要纯纯自定义吗?

要回答这个问题,其实很简略,咱们要清晰 Drawable 的含义,其仅仅一个可制作的图画 。过于复杂的View,咱们能够将其拆分为多个层级,然后关于其间纯展现的View,运用 Drawable 下降其复杂度。

从某个视点来说,Drawable也能够是咱们自定义View的好帮手。

总结

合理使用 Drawable 会很大程度进步咱们的运用体会。相应的,关于布局优化,Drawable 也是一大利器。问题回到文章最开端,假如现在再问你。Drawable 究竟是什么? 怎样自定义一个 Drawable ? 怎样使用其做一些骚操作?我想,这都不是问题。

参阅

  • Android艺术探索 – Android中的Drawable
  • Android开发者 – 可制作资源

关于我

我是 Petterp ,一个 Android工程师 ,假如本文对你有所帮助,欢迎点赞支撑,你的支撑是我继续创造的最大鼓励!