Explicación detallada de Drawable de Android

prefacio

Este artículo es la demostración práctica y posterior a la lectura del Capítulo 6 de "Exploración del arte de desarrollo de Android", que está relacionado con el conocimiento de Drawable de Android.

Cuando escribí el artículo de Bitmap antes, había una pregunta muy interesante, es decir, en Android, ¿cuál es la diferencia entre Bitmap, Drawable y vista personalizada? Parece que en el desarrollo normal, cuando nos encontramos con requisitos de interfaz de usuario simples, como fondos de botones redondeados, nuestra primera reacción es usar Drawable; luego, para requisitos de interfaz de usuario un poco más complejos, personalizamos View para lograr; pero requisitos de interfaz de usuario simples, también podemos use la Vista personalizada para lograr, y BitmapDrawable también puede cargar Bitmap, ¿cuál es el significado de este Drawable?

Bitmap son los datos de mapa de bits utilizados para guardar la imagen, que es para guardar la imagen; y View logra el efecto a través de los pasos de medición, diseño y dibujo; y Drawable es un concepto abstracto que se puede dibujar en Canvas, por así decirlo. Tiene la mitad de la funcionalidad de View, ya que solo tiene la mitad de la funcionalidad de View, ¿es necesario que exista?

Por supuesto que es necesario, Drawable puede simplificar la realización de algunos efectos de imagen, la mayor ventaja es la comodidad . Drawable generalmente se usa como fondo de la Vista, y la implementación es definirlo en el archivo XML.Si estos efectos usan una Vista personalizada, será un gran problema.

Hay muchos tipos integrados de Drawables en el código fuente. Estos Drawables pueden construir varios efectos de imagen a través de imágenes y colores. Dominar estos Drawables puede evitar la duplicación de ruedas durante el desarrollo.

texto

Primero presentamos uno de los parámetros más importantes de Drawable, y luego presentamos y usamos los diversos Drawables incorporados uno por uno.

Ancho y alto interno

En términos generales, Drawable no tiene concepto de tamaño, porque Drawable puede estar compuesto de imágenes o colores. Cuando se usa la composición de colores, Drawable se usa como fondo de View, y su tamaño se estirará al mismo tamaño de View .

Sin embargo, tiene el parámetro de ancho/alto interno, que se puede obtener a través de los dos métodos de getIntrinsicWidth y getIntrisicHeight, por supuesto, no todos los Drawables tienen este concepto, por ejemplo, un Drawable compuesto por una imagen, su ancho y alto internos. es el ancho de la imagen Altura y construcción de color, no hay concepto de ancho interno y altura.

Clasificación de Drawables

Hay muchos tipos de Drawables. Vamos a enumerarlos uno por uno y hablar sobre los detalles de su uso.

BitmapDrawable

El primero es el BitmapDrawable más simple, que representa una imagen. En el desarrollo real, podemos referirnos directamente a la imagen original, o podemos usar XML para describir el BitmapDrawable.

Se puede decir que puedo usar la imagen directamente, ¿por qué usar BitmapDrawable? Esto está relacionado con el escenario de uso más común de Drawable, es decir, puede usarse como fondo de View. Podemos configurar algunas propiedades a través de BitmapDrawable para lograr diferentes efectos.

Use XML para definir BitmapDrawable de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    
    android:src="@drawable/apple_16"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="fill"
    android:mipMap="true"
    android:tileMode="disable"
    />

Luego úsalo como fondo de una página de pantalla completa:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BitmapDrawableActivity"
    android:id="@+id/drawableBitmapLl"
    android:background="@drawable/drawable_bitmap"
    android:orientation="horizontal">

</LinearLayout>

El efecto obtenido es el siguiente:

image.png

Inicialmente se puede juzgar que la imagen original de la manzana se estira para ajustarse al tamaño de la página completa.

Se puede encontrar que hay un total de 7 atributos en el XML anterior, hablemos de ellos por separado:

  • android:src es el ID de recurso de la imagen.
  • android:antialias, ya sea para habilitar la función de suavizado de imágenes, hará que la imagen sea más suave y reducirá la claridad de la imagen hasta cierto punto, pero esta reducción es insignificante, por lo que la opción de suavizado debe estar habilitada.
  • Android: interpolación, si habilitar el efecto de interpolación. Cuando la configuración de píxeles de la imagen es inconsistente con la configuración de píxeles de la pantalla del teléfono móvil, habilitar esta opción permite que las imágenes de alta calidad mantengan un mejor efecto de visualización en pantallas de baja calidad. Por ejemplo, el modo de color predeterminado de Bitmap es ARGB_8888. Cuando el modo de color del dispositivo es RGB_565, activar la opción de difuminado en este momento puede hacer que la imagen no se distorsione demasiado, por lo que debe activarse de forma predeterminada.
  • android:filter, ya sea para habilitar el efecto de filtro. Cuando el tamaño de la imagen se estira o comprime, habilitar el efecto de filtro puede mantener un mejor efecto de visualización. Debe estar habilitado de forma predeterminada.
  • android:gravity,当图片小于容器尺寸时,设置该选项可以队图片进行定位,这也是我们把图片设置为背景时,处理图片位置的重要API,可选项如下,不同的选项可以使用|来组合使用:
  1. top/bottom/left/right,将图片放在容器的顶部/底部/左部/右部,不改变图片的大小。
  2. center_vertical/center_horizontal,将图片竖直居中/水平居中,不改变图片的大小。
  3. fill_vertical/fill_horizontal,将图片竖直/水平方向填充容器。
  4. fill,图片在水平和竖直方向均填充,这是默认值。
  • android:mipMap,这是一种图像相关的处理技术,默认为false,这里不做深入探究了。
  • android:tileMode,平铺模式,默认是关闭,当选择是非关闭时,前面的gravity属性将失效,其他3种选项以及效果如下:
  1. repeat,重复模式,效果:

image.png

  1. mirror,镜像模式,效果:

image.png

  1. clamp,拉伸模式,它会将图片四周的像素扩散到周围区域,效果:

image.png

还有一个类似的叫做NinePatchDrawable,即.9格式的图片,其属性和使用和BitmapDrawable一样。

ShapeDrawable

这种Drawable可以说是我们平时用的最多的一种了,可以理解为通过颜色来构造的图形,它既可以是纯色的图形,也可以是具有渐变效果的图形。

同样我们也是用XML来定义,它的标签是shape标签,XML代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <!--4个角的角度,适用于矩形-->
    <corners
        android:radius="2dp"/>

    <!--渐变效果,和solid标签互斥,solid表示纯色-->
    <gradient
        android:angle="0"
        android:startColor="@color/blue"
        android:endColor="@color/orange"
        android:centerColor="@color/white"
        android:type="linear"
        android:gradientRadius="200"
        />

    <!--纯色填充-->
    <solid/>

    <!--描边颜色-->
    <stroke
        android:width="1dp"
        android:color="@color/red"
        android:dashWidth="10dp"
        android:dashGap="2dp"
        />
</shape>

这里有不少非常熟悉的属性,这个效果如下:

image.png

我们还是分别介绍:

  • android:shape,表示图形的形状,分别是默认的rectangle(矩形),oval(椭圆),line(横线)和ring(圆环)。其中line和ring必须要通过<stroke>标签来指定线的宽度和颜色,否则没有显示效果。

在XML中,我们先以矩形为例。

  • <corners>,表示4个角的角度,只适用于矩形shape,其实就是圆角角度。
  • <gradient>,表示内容的渐变效果,注意这个和<solid>是互斥的,solid表示是纯色填充,它的属性较多:
  1. android:angle,渐变的角度,默认是0,其值必须是45的倍数,0就是表示从左到右,90就是从下到上,比如上图改成90效果如图:

image.png

  1. android:startColor/centerColor/endColor,表示的就是渐变的起始色、中间色和结束色。
  2. android:centerX/centerY,渐变的中心点的横/纵坐标,渐变的中心点会影响渐变的具体效果。
  3. android:gradientRadius,渐变半径,仅当type为"radial"时有效。
  4. android:type,渐变的类别,有linear(线性渐变)、radial(径向渐变)和sweep(扫描渐变)这3种。

这里默认是线性渐变,其他2种效果如图:

image.png

其中这个径向渐变的半径需要特别注意,在实际使用中,因为Drawable作为View的背景时,其大小是不固定的,所以想其渐变色的大小正好和View一样大,一般需要手动设置:

//设置渐变半径为View的宽度一半
gradientView2.post {
    val gradientDrawable = gradientView2.background as GradientDrawable
    gradientDrawable.gradientRadius = gradientView2.width / 2F
}

注意这里ShapeDrawable的代码实现类是GradientDrawable

  • <stroke>,描边信息,这里有如下几个属性:
  1. android:width,描边的宽度,越大则边缘线看起来越粗。
  2. android:color,描边的颜色。
  3. android:dashWidth,组成虚线的线段的宽度。
  4. android:dashGap,组成虚线的线段之间的间隔,和上面属性有一个为0,则没有虚线效果。

除了上面所说的矩形,我们常用的还有圆形和环形,其中圆形没啥说的,比如下图效果:

image.png

对于环形使用比较特殊,它额外多了5个属性,如下:

  1. android:innerRadius,圆环的内半径,如果和innerRadiusRation同时存在,以该属性为准;
  2. android:thickness,圆环的厚度,即半径减去内径的大小,如果和thicknessRatio同时存在,以该属性为准;
  3. android:innerRadiusRatio,内径占整个Drawable的比例。
  4. android:thicknessRatio,厚度占的比例。
  5. android:useLevel,这个一般使用必须是false,除非被当作LevelListDrawable来使用。

效果如下图:

image.png

总的来说,ShapeDrawable是我们平时用的非常多的地方,对于其中的一些属性都必须要掌握。

LayerDrawable

这里Layer翻译为"层级"的意思,所以LayerDrawable所表达的就是一种层次化的Drawable集合,它对应的标签是<layer-list>,通过将不同的Drawable放置在不同的层上面从而达到一种叠加后的效果,其叠加效果就和FrameLayout即帧布局是一样的。

LayerDrawable所对应的XML布局属性较少,我们直接看代码:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape
            android:shape="rectangle"
            >
            <solid
                android:color="@color/black_666666"
                />
        </shape>
    </item>

    <item
        android:bottom="6dp"
        >
        <shape
            android:shape="rectangle"
            >
            <solid
                android:color="@color/white"
                />
        </shape>
    </item>

    <item
        android:bottom="1dp"
        android:left="1dp"
        android:right="1dp"
        >
        <shape
            android:shape="rectangle"
            >
            <solid
                android:color="@color/white"
                />
        </shape>
    </item>

</layer-list>

可以发现一个LayerDrawable由多个item标签组成,而每个item就代表一个Drawable,其中item的属性也非常简单,就是android:top/bottom/left/right,表示Drawable相对于View的上下左右的偏移量,单位为像素。

所以上述XML代码中,我们先放置了一个灰色背景,然后放置一个白色遮挡,和下面偏移为6px,然后最后再放一个白色遮挡,和左、下、右都偏移1px,这样就可以实现一个输入框背景的效果,如下:

image.png

对于LayerDrawable的使用,主要是要熟悉其他Drawable的使用,使用遮盖叠加的特性实现不同效果。

StateListDrawable

这个是状态列表Drawable,每个Drawable都对应着View的一种状态,这也是我们非常常用的一种,它的标签是<selector>,它的使用场景就是当View的状态改变时,改变其背景,我们还是来个简单代码:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!--按下状态-->
    <item
        android:state_pressed="true"
        android:drawable="@drawable/drawable_shape_button_pressed"
        >
    </item>

    <!--默认状态-->
    <item
        android:drawable="@drawable/drawable_shape_button_normal"
        >
    </item>

</selector>

这里也是多个item组成的结果,其中常见的状态有如下:

  1. state_pressed:表示按下状态。
  2. state_focused:表示View已经获取了焦点。
  3. state_selected:表示用户选择了View。
  4. state_checked:表示用户选中了View,一般适用于CheckBox这类在选中和非选中之间进行切换的View。
  5. state_enabled:表示View当前处于可用状态。

比如上述XML代码所实现的效果如下图:

image.png

点击改变背景:

image.png

这里会有个问题,就是在selector中会定义多个item,每当View的状态变化时,系统按照从上到下的顺序来找到第一条匹配的item,所以默认的item应该放在selector最后一条并且不要附带任何的状态

LevelListDrawable

LevelListDrawable对应于<level-list>标签,它表示一个Drawable集合,集合中的每个Drawable都有一个等级(level)的概念,根据不同的等级,LevelListDrawable会切换为对应不同的Drawable,使用比较简单,比如下面代码:

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@color/color_1"
        android:maxLevel="0"
        />
    <item
        android:drawable="@color/color_2"
        android:maxLevel="1"
        />
    <item
        android:drawable="@color/color_3"
        android:maxLevel="2"
        />
    <item
        android:drawable="@color/color_4"
        android:maxLevel="3"
        />
    <item
        android:drawable="@color/color_5"
        android:maxLevel="4"
        />
    <item
        android:drawable="@color/color_6"
        android:maxLevel="5"
        />
    <item
        android:drawable="@color/color_7"
        android:maxLevel="6"
        />

</level-list>

注意这里minLevel和maxLevel表示当前Drawable所显示的范围,其中最小值为0,最大值为10000,然后在代码中,获取到该Drawable,设置其level即可:

var color = 0

private fun startTimer(){
    val timeTask = object : TimerTask(){
        override fun run() {
            val level = color % 7
            runOnUiThread {
                (levelListView.background as LevelListDrawable).level = level
            }
            color ++
        }
    }
    Timer().schedule(timeTask, Date(),300)
}

这里300ms变化一次背景,效果如下:

17.gif

这个类型的Drawable当背景有较多情况需要切换时,可用使用该方法。

TransitionDrawable

TransitionDrawable对应于<transition>标签,它可用实现俩个Drawable之间的淡入淡出的效果,这个还是非常方便的,使用起来非常方便,使用2个item表示需要变化的Drawable即可:

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@color/blue"
        />

    <item
        android:drawable="@color/black_999999"
        />

</transition>

然后调用其startTransition和reverseTransition方法来实现淡入淡出的效果以及它的逆向过程:

val drawable = transitionDrawable.background as TransitionDrawable
drawable.startTransition(1000)

InsetDrawable

这个翻译过来就是内嵌Drawable,顾名思义就是可用把其他Drawable内嵌到自己当中,并且可用在四周留出一定的间距。

当一个View希望自己的背景比自己实际区域小的时候,可用采用InsetDrawable来实现,同理,我们可用使用LayerDrawable进行遮盖、重叠实现一样的效果。

ScaleDrawable

熟悉动画的开发者都知道这个是啥了,就是缩放Drawable,ScaleDrawable可用根据自己的等级(Level)将指定的Drawable缩放到一定的比例。

但是它的使用有点复杂,稍微不注意会出错,首先下面是我们定义一个简单ScaleDrawable,其中使用<scale>标签,XML代码如下:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/test"
    android:scaleHeight="50%"
    android:scaleWidth="50%"
    android:scaleGravity="center"
    >
</scale>

其中有2个属性必须注意:

  1. android:scaleHeight/scaleWidth,这个表示高度/宽度的缩放比例,比如设置为50%,则图片宽可以缩放到原来的50%,当设置为90%,则图片可以缩放到原来的10%。
  2. android:scaleGravity,这个和gravity属性一个意思,即缩放后的位置。

定义上面XML,直接使用会发现还是无法使用,这时需要设置其等级(Level),等级的范围是0-10000,默认为0,是不可见,如何理解这个呢?

这里假如图片宽度是1000,在XML中设置了scaleWidth为90%,那么这时level设置为1时,显示宽度就是10,当level设置为10000时,宽度就是1000。

比如下图GIF,上图设置了缩放范围为50%,下图设置为了90%:

21.gif

然后对于scaleGravity也比较容易理解,比如像下图GIF,上图设置了为left,下图设置为bottom:

22.gif

对于scaleDrawable实现的效果和动画类似,所以它表示的东西是完全不一样的,这个只是改变Drawable的大小,即图像的大小。

ClipDrawable

ClipDrawable对应的是<clip>标签,表示裁剪Drawable,其中主要属性就android:clipOrientation表示裁剪方向,有水平方向和竖直方向可选,还有就是android:gravity表示裁剪后Drawable在哪个位置,关于这个gravity的可选项非常多,而且每一种搭配都会产生不同的效果,整个候选项如下图:

image.png

比如下面XML代码:

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/test"
    android:gravity="bottom"
    android:clipOrientation="vertical"
    >
</clip>

从表中可知,这将是竖直方向,从顶部开始裁剪,除了这个,还需要配置等级(Level),用法和上面ScaleDrawable一样,范围是0-10000,下图gif表示上面的效果:

25.gif

这种效果在我们平时开发中,也遇到过这种需求。

好了,上面就是源码中内置的所有Drawable,其中有一些不是很熟悉的,API又比较多,我们不必都记住,有点印象即可,在使用时能想到即可。

自定义Drawable

Drawable的使用范围还是比较单一,一个是作为ImageView中的图像来显示,另一个就是作为View的背景,大多数情况下Drawable就是作为View的背景。

而且Drawable的工作原理非常简单,其核心就是draw方法,所以我们可以自定义Drawable。注意这里自定义Drawable和自定义View的区别,在前面也说了,Drawable可以看成一半功能的View,所以适合比较简单的场景。

通常我们是不必要自定义Drawable,因为自定义的Drawable无法在XML中使用,这就大大降低了Drawable的使用范围。我们现在就自定义一个简单的Drawable,来说一下其使用,比如下面代码:

class CustomDrawable(val color: Int) : Drawable(){

    private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    init {
        mPaint.color = color
    }

    override fun draw(canvas: Canvas) {
        val rect = bounds
        val centerX = rect.exactCenterX()
        val centerY = rect.exactCenterY()
        canvas.drawCircle(centerX,centerY, centerX.coerceAtMost(centerY),mPaint)
    }

    override fun setAlpha(alpha: Int) {
        mPaint.alpha = alpha
        invalidateSelf()
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        mPaint.colorFilter = colorFilter
        invalidateSelf()
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }
}

这里绘制一个圆当作背景,且其大小会随着View的变化而变化,效果如下:

image.png

可以看出一共需要重新4个方法,其中draw方法为主要方法,通过canvas进行绘制,这里也就不多说了。

总结

Drawable作为最常用的图像,通过本章学习知道了其实有很多内置的效果,并且它的实现比自定义View简单,效率也比自定义View高,在后续工作中,如果能使用Drawable实现的,可以灵活使用。

本篇demo代码的地址: github.com/horizon1234…

Supongo que te gusta

Origin juejin.im/post/7115323096554274852
Recomendado
Clasificación