前面已经学习了Android绘图与动画中的一些基础知识,这次来学习下安卓中提供的 Drawable。其实本应该在学习Animation或者Bitmap之前学习Drawable的,在学习Animation的帧动画中我们也看到实现Animation中的帧动画所用到的类就是AnimDrawable,然后代码中调用AnimDrawable实例的start()以及stop()开始或停止播放帧动画,Bitmap其实除了之前学习的BitmapFactory方法外也可以通过BitmapDrawable获取。Android中提供了多达13种的 Drawable,这里就来学习下安卓提供的一些常用的Drawable。
Drawable是一种可以在Canvas上进行绘制的对象,即可绘制物;Drawable类是抽象类,是接下来要说的各种Drawable的基类。它常常用于View的背景或者作为ImageView中的图像显示。
Drawable可分为两种:
例如之前学习帧动画时(用到的类为AnimDrawable)在res/drawable定义的meizi.xml:
然后在activity_main.xml中通过 android:background="@drawable/meizi"将上面的Drawable资源引入到布局中:
Drawable 没有大小概念,某些Drawable可以通过getIntrinsicWidth()和getIntrinsicHeight()获取其内部宽和高。一些特殊的例子例如图片所形成的Drawable的内部宽和高就是图片的宽和高,颜色所形成的Drawable没有内部宽和高的概念。
前面说过Drawable是一种可以在Canvas上进行绘制的对象,那么当我们将ColorDrawable绘制到Canvas上的时候, 会使用一种固定的颜色来填充Paint,然后在画布上绘制出一片单色区域。这里比较简单通过Demo来学习,布局文件比较简单就不写了:
修改color.xml文件:
MainActicity:
public class MainActivity extends AppCompatActivity {
private ColorDrawable colorDrawable;
private TextView textView;
private Button button1,button2,button3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindviews();
//TextView Java中定义ColorDrawable:
colorDrawable = new ColorDrawable(0xffff2200);
textView.setBackground(colorDrawable);
//Button1 在xml中定义ColorDrawable:
int mycolor = getResources().getColor(R.color.mycolor);
button1.setBackgroundColor(mycolor);
//使用系统定义好的color
int getcolor = Resources.getSystem().getColor(android.R.color.darker_gray);
button2.setBackgroundColor(getcolor);
//前面在学习Animation时提到的ARGB方式
button3.setBackgroundColor(Color.argb(0x00, 0x00, 0x00, 0x00));
}
private void bindviews() {
textView = findViewById(R.id.textview);
button1 = findViewById(R.id.button1);
button2 = findViewById(R.id.button2);
button3 = findViewById(R.id.button3);
}
}
ShapeDrawable可表示纯色或有渐变效果的基础几何图形(矩形,圆形,线条等),根结点是shape节点,子节点较多。主要包含以下:
(1)< shape > 根节点 设置图形的形状 , 主要属性如下:
(2)< size >: 图形的固有大小
(3) < corners > shape的四个圆角的角度,只适用于矩形
(4) < gradient >渐变效果,与< solid >纯色填充相排斥
(5)< solid >纯色填充,与< gradient >渐变效果排斥
(6)< stroke > 描边:
(7)< padding >
这里也通过Demo学习下,在res/drawable下新建两个Drawable shape文件:
rect.xml:矩形渐变效果
rect_circle.xml:矩形纯色填充
activity_main.xml:
效果如下:
属性前面已经介绍过了,这里直接通过Demo来验证:
新建linear.xml:
radial.xml:
sweep.xml:
activity_main.xml:
三种效果如下:
Bitmap的一种封装,用于表示图片,可以设置被它包装的Bitmap在BitmapDrawable区域中的绘制方式,包括:平铺填充,拉伸填或保持图片原始大小。根节点为< bitmap >,主要属性如下:
通过Demo来验证效果:
新建tilemode.xml:
activity_main.xml中设置一个View:
tilemode.xml指定不同的tileMode效果如下:
disabled:
clamp原图:
repeat平铺:
mirror镜像平铺:
Java 代码中:
public class MainActivity extends AppCompatActivity {
private LinearLayout myLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myLayout = findViewById(R.id.mylayout);
//创建BitmapDrawable
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
BitmapDrawable bitmapDrawable = new BitmapDrawable(bitmap);
bitmapDrawable.setDither(true);
bitmapDrawable.setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);//MIRROR镜面平铺
//加载
myLayout.setBackgroundDrawable(bitmapDrawable);
}
}
表示把一个Drawable嵌入到另外一个Drawable的内部,并且在内部留一些间距, 与Drawable的padding属性相似,但padding属性表示的是Drawable的内容与Drawable本身的边距, 而InsetDrawable表示的是两个Drawable与容器之间的边距,当控件需要的背景比实际的边框 小的时候,比较适合使用InsetDrawable。根节点为< inset >,常用属性包括:
通过Demo验证一下,在res/drawable新建inset.xml:
效果如下:
表示裁剪一个Drawable,根节点为< clip >。主要属性如下:
通过Demo来学习一下,在res/drawable中定义clip.xml:
这里设置为水平方向从左开始裁剪
activity_main.xml:将imageview的src设置为clip
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private ClipDrawable clipDrawable;
int level;
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 0x123){
clipDrawable.setLevel(level);
level += 500;//每次裁剪500
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageview);
clipDrawable = (ClipDrawable) imageView.getDrawable();
level = clipDrawable.getLevel();
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0x123);
if (level>10000){
timer.cancel();
}
}
},0,50);//50ms为一个周期去裁剪
}
}
表示一种层次化的Drawable集合,包含一个Drawable数组,然后按照数组对应的顺序来绘制,索引值最大的Drawable会被绘制在最上层。虽然这些Drawable会有交叉或者重叠的区域,但它们位于不同的层,所以并不会相互影响,根节点为< layer-list >,< item >常用属性如下:
Demo实例
res/drawable新建layer.xml:
activity_main.xml:
效果如下:
TransitionDrawable是LayerDrawable的子类,TransitionDrawable只管理两层的Drawable,并且提供了透明度变化的动画,可以控制一层Drawable过度到另一层Drawable的动画效果。 根节点为< transition >,常用属性和LayerDrawable相同。并且需要调用startTransition方法才能启动两层间的切换动画, 也可以调用reverseTransition()方法反过来播放。
通过Demo来学习,在res/drawable创建一个TransitionDrawable的xml文件:
MainActivity:调用startTransition方法启动两层间的切换动画
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageview);
TransitionDrawable transitionDrawable = (TransitionDrawable) imageView.getDrawable();
transitionDrawable.startTransition(3000);
//transitionDrawable.reverseTransition(3000);
}
}
效果如下:
用来管理一组Drawable的,我们可以为里面的drawable设置不同的level, 当他们绘制的时候,会根据level属性值获取对应的drawable绘制到Canvas上,根节点为:< level-list >没有可以设置的属性 ,但是可以设置每个< item > 的属性。item属性如下:
LevelListDrawable若作为View背景,需要在Java代码中调用setLevel()方法,若作为ImageView前景,需要调用setImageLevel()。
加载时当某item的android:maxLevel 等于 setLevel所设置的数值时就会被加载。若都没有匹配的则都不显示。
通过Demo来学习:
res/drawable新建三个shape文件:
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView imageview;
private LevelListDrawable levelListDrawable;
int level = 0;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
if (levelListDrawable.getLevel() > 6000) {
levelListDrawable.setLevel(0);
}
imageview.setImageLevel(levelListDrawable.getLevel() + 2000);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageview = findViewById(R.id.imageview);
levelListDrawable = (LevelListDrawable) imageview.getDrawable();
imageview.setImageLevel(0);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0x123);
}
}, 0, 100);
}
}
表示一个Drawable的集合,每个Drawable对应着View的一种状态。根节点为< selctor >,常用属性包括:
< item >的属性包括:
简单的Demo,按钮点击改变颜色:
res/drawable新建shape_btn_normal.xml:代表普通状态下的shape
shape_btn_pressed.xml:代表按下状态下的shape
activity_main.xml:
除此之外,还要新建selector文件将两者结合起来:
state_pressed就代表着上面介绍属性说的控件是否被按下,效果如下:
参考之前的文章安卓开发之Animation学习(帧、补间、属性动画)中帧动画。
涉及到自定义View的一些知识,之后再系统的学习,这里先了解一下,参考《安卓开发艺术探索》。
前面说过Drawable 的使用范围比较单一,或作为ImageView中的图像来显示,或作为View的背景,后者用途居多。创建自定义Drawable,必须重写其draw()、setAlpha()、setColorFilter()、getOpacity()等方法,下面是一个自定义Drawable的实现过程,通过自定义Drawable来绘制一个圆形的Drawable,并且它的半径会随着View的变化而变化,这种Drawable可以作为View的通用背景,代码如下所示。
public class MyDrawable extends Drawable {
private Paint paint;
public MyDrawable(int color){
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
}
@Override
public void draw(@NonNull Canvas canvas) {
//得到Drawable的实际大小,一般和View尺寸相同
final Rect rect = getBounds();
//精确获取矩阵中心点
float x = rect.exactCenterX();
float y = rect.exactCenterY();
canvas.drawCircle(x,y,Math.min(x,y),paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
当自定义的Drawable有固有大小时,最好重写getIntrinsicWidth()和getIntrinsicHeight()这两个方法,因为它会影响到View的wrap_content布局。比如自定义Drawable是绘制一-张图片,那么这个Drawable的内部大小就可以选用图片的大小。在上面的例子中,自定义的Drawable是由颜色填充的圆形并且没有固定的大小,因此没有重写这两个方法,这个时候它的内部大小为-1,即内部宽度和内部高度都
为-1。需要注意的是,内部大小不等于Drawable的实际区域大小,Drawable 的实际区域大小可以通过它的getBounds方法来得到,一般来说它和View的尺寸相同。