Android 知识

Android Drawable 学习

2018-09-01  本文已影响21人  5260fbd1e4e1

一、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"] 
     />

这些属性含义如下:

接着说一下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。其各个属性含义如下:

<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />

其中 radius 表示四个角的角度,其他为具体的角。

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 标签属性如下:

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" />

有点需要特别注意的是我们如果定义好了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

上一篇 下一篇

猜你喜欢

热点阅读