一、Drawable 简介
在 Android 系统中,drawable 表示的是一种图像的概念,但是它们不是全是图片,通过颜色也可以构造出各式各样的图像效果。在实际开发中,Drawable 常被用来做为 View 的背景使用。 Drawable 一般都是通过 XML 来定义的,也可以通过代码来创建具体的 Drawable 对象,只是用代码创建会稍显复杂。在 Android 中,Drawable 是一个抽象类,它是所有 Drawable 对象的基类,每个具体的 Drawable 都是它的子类,比如 ShapeDrawable、BitmapDrawable 等。
Drawable 内部的宽高是一个比较重要的参数,通过 getIntrinsicWidth 和 getIntrinsicHeighi 可以获得宽高,但是不是所有的 Drawable 都有这两个参数,比如一张图片形成的 Drawable 的宽高就是图片的宽高,但是颜色形成的 Drawable 就没有这个概念了。
二、Drawable 分类
Drawable 的种类繁多,常见的有 BitmapDrawable、ShapeDrawable、LayerDrawable 以及 StateListDrawable 等。
1、BitmapDrawable
BitmapDrawable 表示的就是一张图片,在实际开发中,可以直接引用原始的图片即可,但是 也可以通过 XML 方式来描述它,通过 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:tileMode=["disabled" | "clamp" | "repeat" | "mirror"]
/>
这些属性含义如下:
-
android:src
类型:Drawable resource。必需。 引用一个drawable resource. -
android:antialias
类型:Boolean。是否开启抗锯齿。开启后图片会变得更平滑些,因此一般建议开启,设置为true即可。 -
android:dither
类型:Boolean。是否允许抖动,如果位图与屏幕的像素配置不同时,开启这个选项可以让高质量的图片在低质量的屏幕上保持较好的显示效果(例如:一个位图的像素设置是 ARGB 8888,但屏幕的设置是RGB 565,开启这个选项可以是图片不过于失真)一般建议开启,为true即可。 -
android:filter
类型:Boolean。是否允许对位图进行滤波。当图片被压缩或者拉伸时,使用滤波可以获得平滑的外观效果。一般建议开启,为true即可 -
android:gravity
当图片小于容器尺寸时,设置此选项可以对图片经典定位,这个属性比较多,不同选项可以使用‘|’来组合使用。
gravity 属性.png -
android:mipMap
这是一种图像相关的处理技术,也叫纹理映射,不常用,默认 false 就行。 -
android:titleMode
平铺模式。共有以下几个值
disabled :默认值,表示不使用平铺
clamp :复制边缘色彩
repeat :X、Y 轴进行重复图片显示,也就是我们说要说的平铺
mirror :在水平和垂直方向上使用交替镜像的方式重复图片的绘制
接着说一下NinePatchDrawable(九宫格图片),表示的是 .9 格式的图片,这个图片可以自动根据需要进行相应的缩放而不失真。使用和 BitmapDrawable 一样,直接引用图片就行。
<?xml version="1.0" encoding="utf-8"?>
<nine-patch
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@[package:]drawable/drawable_resource"
android:dither=["true" | "false"] />
其属性含义和 BitmapDrawable 相同。需要注意的是,BitmapDrawable 中,bitmap 标签也可以使用 .9 图片。
以上是 XML 定义 BitmapDrawable,接一下看一下代码定义 BitmapDrawable。
BitmapDrawable.png
常用的构造函数有三个:
public BitmapDrawable(Resources res, Bitmap bitmap)
public BitmapDrawable(Resources res, String filepath)
public BitmapDrawable(Resources res, java.io.InputStream is)
代码示例:
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
BitmapDrawable mBitmapDrawable = new BitmapDrawable(getResources(),mBitmap);
mBitmapDrawable.setTileModeXY(TileMode.MIRROR, TileMode.MIRROR);//平铺方式
mBitmapDrawable.setAntiAlias(true);//抗锯齿
mBitmapDrawable.setDither(true);//防抖动
//设置到imageView上即可
imageView.setImageDrawable(mBitmapDrawable);
2、ShapeDrawable
ShapeDrawable 是一种常见的 Drawable,可以理解为通过颜色来构造的图形,既可以是纯色的图形,也可以是具有渐变效果的图形。语法如下:
<?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="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:usesLevel=["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>
需要注意的是,<shape>标签所创建的 Drawable,其实体类实际上是 GradientDrawable。其各个属性含义如下:
-
android:shape
这个属性表示图像的形状,可以是rectangle(矩形)、oval(椭圆)、line(横线)、ring(圆环)。默认为rectangle。
针对 ring 这个形状,有五个特殊的属性,如下:
ring 属性.png -
corners
表示 shaper 的四个角的角度,适用于矩形 shape,这里的角度为圆角的程度,用 px 表示,有五个属性:
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
其中 radius 表示四个角的角度,其他为具体的角。
-
gradient
与 <solid> 是互斥的,这个表示渐变效果。
属性及含义如下:
gradient.png -
solid
表示纯色填充。通过 android:color 指定颜色。 -
stroke
表示 Shape 的描边,有如下属性
shape.png -
padding
表示空白,并不是 shape 的空白,而是包含它的 View 的空白。
有四个属性: left,top,right 和 bottom。 -
size
设置背景大小,width和height俩属性。一般来说这个值不是shape的最终显示大小,因为shape作为背景时会根据View的大小而填充其背景,因此Shape的大小很多时候是View的大小决定的。
3、LayerDrawable
LayerDrawable 对用的 XML 标签是 <layer-list>,表示一种层次化的 Drawable 集合,通过将不同的 Drawable 放置在不同的层面上达到叠加的效果。语法如下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@[package:]drawable/drawable_resource"
android:id="@[+][package:]id/resource_name"
android:top="dimension"
android:right="dimension"
android:bottom="dimension"
android:left="dimension" />
</layer-list>
一个layer-list可以包含多个item,而每个item则表示一个Drawable。其属性如下:
android:id
资源ID,一个为这个item定义的唯一的资源ID。 使用:”@+id/name”.这样的方式。可以检索或修改这个drawable通过下面的方式:View.findViewById() or Activity.findViewById().
android:top
Integer,Drawable相对于View的顶部的偏移量,单位像素
android:right
Integer,Drawable相对于View的右边的偏移量,单位像素
android:bottom
Integer,Drawable相对于View的底部的偏移量,单位像素
android:left
Integer,Drawable相对于View的左边的偏移量,单位像素
android:drawable
Drawable资源,可以引用已有的drawable资源,也可在item中自定义Drawable。默认情况下,layer-list中的Drawable都会被缩放至View的大小,因此在必要的情况下,我们可以使用android:gravity属性来控制图片的展示效果,防止图片变形或者被过度拉伸。 Layer-list 有层次的概念,下面的 item 会覆盖上面的 item,通过合理的分层,可是实现一些特殊的叠加效果。
4、StateListDrawable
SateListDrawable 对应于 <selector> 标签,表示 Drawable 集合,每个 Drawable 都对应着 View 的一种状态,这样系统就会根据 View 的状态选择合适的 Drawable。其语法如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize=["true" | "false"]
android:dither=["true" | "false"]
android:variablePadding=["true" | "false"] >
<item
android:drawable="@[package:]drawable/drawable_resource"
android:state_pressed=["true" | "false"]
android:state_focused=["true" | "false"]
android:state_hovered=["true" | "false"]
android:state_selected=["true" | "false"]
android:state_checkable=["true" | "false"]
android:state_checked=["true" | "false"]
android:state_enabled=["true" | "false"]
android:state_activated=["true" | "false"]
android:state_window_focused=["true" | "false"] />
</selector>
Item 其属性如下:
StateListDrawable.png
Selector 标签属性如下:
- android:constantSize
StateListDrawable的固有大小是否随着其状态改变而改变,因为在状态改变后,StateListDrawable会切换到别的Drawable,而不同的Drawable其大小可能不一样。true表示大小不变,这时其固有大小是内容所有Drawable的固有大小的最大值。false则会随着状态改变而改变,默认值为false - android:variablePadding
表示 StateListDrawable的padding是否随状态的改变而改变,默认值为false,一般建议设置为false就行。 - android:dither
是否开启抖动效果,开启后可使高质量的图片在低质量的屏幕上仍然有较好的显示效果,一般建议开启,设置为true。
5、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="@drawable/drawable_resource"
android:maxLevel="integer"
android:minLevel="integer" />
</level-list>
属性如下:
LevelListDrawable.png
6、TransitionDrawable
TransitionDrawable 对应与 <transition> 标签,用于实现两个 Drawable 之间的淡入淡出,语法如下:
<?xml version="1.0" encoding="utf-8"?>
<transition
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@[package:]drawable/drawable_resource"
android:id="@[+][package:]id/resource_name"
android:top="dimension"
android:right="dimension"
android:bottom="dimension"
android:left="dimension" />
</transition>
属性与之前的属性含义相同。
实际使用:
一般先定义 Drawable:
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/image1"/>
<item android:drawable="@drawable/image2" />
</transition>
然后布局应用:
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/transition_drawable"
/>
最后就是代码控制效果:
TransitionDrawable drawable= (TransitionDrawable) imageView.getBackground();
drawable.startTransition(4000);
如果是 通过 src 指定的 drawable,那么控制代码为:
TransitionDrawable drawable= (TransitionDrawable) imageView.getDrawable();
drawable.startTransition(4000);
代码实现方式如下:
ImageView imageView= (ImageView) findViewById(R.id.tranImage);
Bitmap bitmap1= BitmapFactory.decodeResource(getResources(), R.drawable.image1);
Bitmap bitmap2= BitmapFactory.decodeResource(getResources(), R.drawable.image2);
final TransitionDrawable td = new TransitionDrawable(new Drawable[] { new BitmapDrawable(getResources(), bitmap1),
new BitmapDrawable(getResources(), bitmap2) });
imageView.setImageDrawable(td);
td.startTransition(4000);
7、InsetDrawable
InsetDrawable 对应于 <inset> 标签,可以将其他 Drawable 内嵌到自己当中,并且可以在四周留出一定的间距。当一个 View 希望自己的背景比自己的实际区域小的时候,可以采用 InsetDrawable 来实现。语法如下:
<?xml version="1.0" encoding="utf-8"?>
<inset
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:insetTop="dimension"
android:insetRight="dimension"
android:insetBottom="dimension"
android:insetLeft="dimension" />
8、ScaleDrawable
ScaleDrawable 对应于 <Scale> 标签,可以根据自己的等级 (level) 将指定的 Drawable 缩放到一定的比例,语法如下:
<?xml version="1.0" encoding="utf-8"?>
<scale
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:scaleGravity=["top" | "bottom" | "left" | "right" | "center_vertical"
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"]
android:scaleHeight="percentage"
android:scaleWidth="percentage" />
- android:scaleGravity:设置位置的。
- android:scaleHeight 表示Drawable的高的缩放比例,值越大,内部Drawable的高度显示得越小,例如android:scaleHeight=”70%”,那么显示时Drawable的高度只有原来的30%。
- android:scaleWidth 表示Drawable的宽的缩放比例,值越大,内部Drawable的宽显示得越小,例如android:scaleWidth=”70%”,那么显示时Drawable的宽度只有原来的30%。
有点需要特别注意的是我们如果定义好了ScaleDrawable,要将其显示出来的话,必须给ScaleDrawable设置一个大于0小于10000的等级(级别越大Drawable显示得越大,等级为10000时就没有缩放效果了),否则将无法正常显示。我们可以看看其draw函数的源码:
@Override
public void draw(Canvas canvas) {
final Drawable d = getDrawable();
if (d != null && d.getLevel() != 0) {
d.draw(canvas);
}
}
9、ClipDrawable
ClipDrawable 对应于 <clip> 标签,可以根据自己当前等级 (level) 来裁剪另一个 Drawable,裁剪方向可以通过 android:clipOrientation 和 android:gravity 两个属性来控制,语法如下:
<?xml version="1.0" encoding="utf-8"?>
<clip
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:clipOrientation=["horizontal" | "vertical"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"] />
其中android:clipOrientation和android:gravity属性共同控制Drawable被裁剪的方向,其中clipOrientation表示裁剪的方向(水平和垂直两种),gravity比较复杂必须和clipOrientation一起才能起作用,同样的我们可以通过“|”来组合使用gravity的属性值。gravity属性值说明如下:
ClipeDrawable.png
10、ColorDrawable
ColorDrawable 是最简单的Drawable,它实际上是代表了单色可绘制区域,它包装了一种固定的颜色,当ColorDrawable被绘制到画布的时候会使用颜色填充Paint,在画布上绘制一块单色的区域。 在xml文件中对应<color>标签,它只有一个android:color属性,通过它来决定ColorDrawable的颜色。
xml:
<?xmlversion="1.0" encoding="utf-8"?>
<color xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/normal"
/>
java:
ColorDrawable cd = new ColorDrawable(0xff000000);
ImageView iv = (ImageView)findViewById(...);
iv.setImageDrawable(cd);
11、GradientDrawable
GradientDrawable 表示一个渐变区域,可以实现线性渐变、发散渐变和平铺渐变效果。
示例:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<gradient android:angle="90"
android:startColor="@color/colorPrimary"
android:centerColor="#fff"
android:endColor="@color/color_state"
android:type="linear"
/>
</shape>
三、自定义 Drawable
Drawable 的使用范围很单一,一个是作为 ImageView 中的图像来显示,另一个就是作为 View 的背景,大多数情况下就是 Drawable 都是以 View 的背景这种形式出现。Drawable 的工作原理很简单,核心就是 draw 方法。我们可以通重写 Drawable 的 draw 方法来自定义 Drawable 。
通常我们没有必要自定义 Drawable,因为没法在 XML 中使用,但是如果我们一定要自定义也是可以的。下面演示自定义 Drawable。
1、圆角 Drawable
RoundImageDrawable.java
public class RoundImageDrawable extends Drawable
{
private Paint mPaint;
private Bitmap mBitmap;
private RectF rectF;
public RoundImageDrawable(Bitmap bitmap)
{
mBitmap = bitmap;
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setShader(bitmapShader);
}
@Override
public void setBounds(int left, int top, int right, int bottom)
{
super.setBounds(left, top, right, bottom);
rectF = new RectF(left, top, right, bottom);
}
@Override
public void draw(Canvas canvas)
{
canvas.drawRoundRect(rectF, 30, 30, mPaint);
}
@Override
public int getIntrinsicWidth()
{
return mBitmap.getWidth();
}
@Override
public int getIntrinsicHeight()
{
return mBitmap.getHeight();
}
@Override
public void setAlpha(int alpha)
{
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf)
{
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity()
{
return PixelFormat.TRANSLUCENT;
}
}
虽然最主要的只是 draw 方法,但是其他的方法都用实现,需要注意的是,getIntrinsicWidht 和 getIntrinsicHeight 这两个方法要实现,因为这两个方法会影响 Drawable 的 View 的 wrap_content 布局。
xml 布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<ImageView
android:id="@+id/drawable_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Java 使用:
public class DrawableDemo extends AppCompatActivity {
ImageView img ;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable);
img = findViewById(R.id.drawable_img);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.girl);
img.setImageDrawable(new RoundImageDrawable(bitmap));
}
}
效果:
Round.png
圆形 Drawable:
public CircleImageDrawable(Bitmap bitmap)
{
mBitmap = bitmap ;
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setShader(bitmapShader);
mWidth = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
}
@Override
public void draw(Canvas canvas)
{
canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, mPaint);
}
@Override
public int getIntrinsicWidth()
{
return mWidth;
}
@Override
public int getIntrinsicHeight()
{
return mWidth;
}
@Override
public void setAlpha(int alpha)
{
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf)
{
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity()
{
return PixelFormat.TRANSLUCENT;
}
}
效果:
Circle.png
参考 《Android 开发艺术探索》
https://developer.android.com/guide/topics/resources/drawable-resource#ninepatch-element
https://blog.csdn.net/javazejian/article/details/52247324
https://blog.csdn.net/lmj623565791/article/details/43752383
网友评论