安卓开发之Drawable学习

前面已经学习了Android绘图与动画中的一些基础知识,这次来学习下安卓中提供的 Drawable。其实本应该在学习Animation或者Bitmap之前学习Drawable的,在学习Animation的帧动画中我们也看到实现Animation中的帧动画所用到的类就是AnimDrawable,然后代码中调用AnimDrawable实例的start()以及stop()开始或停止播放帧动画,Bitmap其实除了之前学习的BitmapFactory方法外也可以通过BitmapDrawable获取。Android中提供了多达13种的 Drawable,这里就来学习下安卓提供的一些常用的Drawable。

Drawable

Drawable是一种可以在Canvas上进行绘制的对象,即可绘制物;Drawable类是抽象类,是接下来要说的各种Drawable的基类。它常常用于View的背景或者作为ImageView中的图像显示。

Drawable可分为两种:

  • 1.常见的普通的图片资源,一般放在res/mipmap或者res/drawable下也可以,可通过R.drawable/mipmap.xxx方式使用
  • 2.XML形式的Drawable资源,一般放在res/drawable下,然后在布局文件中通过android:background = "@drawable/xxx"引入布局中。
    也可以在Java 代码中通过Resource的getDrawable(R.drawable/mipmap.xxx)获得drawable资源或者new一个所需Drawable并set相关属性,最后加载到布局中

例如之前学习帧动画时(用到的类为AnimDrawable)在res/drawable定义的meizi.xml:
在这里插入图片描述
然后在activity_main.xml中通过 android:background="@drawable/meizi"将上面的Drawable资源引入到布局中:
在这里插入图片描述
Drawable 没有大小概念,某些Drawable可以通过getIntrinsicWidth()和getIntrinsicHeight()获取其内部宽和高。一些特殊的例子例如图片所形成的Drawable的内部宽和高就是图片的宽和高,颜色所形成的Drawable没有内部宽和高的概念。

ColorDrawable

前面说过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

ShapeDrawable可表示纯色或有渐变效果的基础几何图形(矩形,圆形,线条等),根结点是shape节点,子节点较多。主要包含以下:

(1)< shape > 根节点 设置图形的形状 , 主要属性如下:

  • visible:设置是否可见
  • shape:形状,可选值包括rectangle(矩形,包括正方形),oval(椭圆,包括圆),line(线段),ring(环形)
  • innerRadiusRatio:当shape为ring时有效,表示环内半径所占半径的比率,如果设置了innerRadius会被忽略
  • innerRadius:当shape为ring时有效,表示环的内半径的尺寸
  • thicknessRatio:当shape为ring时有效,表环厚度占半径的比率
  • thickness:当shape为ring时有效,表示环的厚度,即外半径与内半径的差
  • useLevel:当shape为ring时有效,表示是否允许根据level来显示环的一部分,通常为false

(2)< size >: 图形的固有大小

  • width:设定shape宽度
  • height:设定shape高度

(3) < corners > shape的四个圆角的角度,只适用于矩形

  • radius:圆角半径,为上下左右四个角设置相同的角度
  • topLeftRadius,topRightRadius,BottomLeftRadius,tBottomRightRadius: 依次为左上,右上,左下,右下的圆角值,优先级高于radius

(4) < gradient >渐变效果,与< solid >纯色填充相排斥

  • startColor:渐变的起始颜色
  • centerColor:渐变的中间颜色
  • endColor:渐变的结束颜色
  • type:渐变类型,可选linear线性渐变(可设置渐变角度angle),radial发散/径向渐变(gradientRadius必须设置),sweep平铺/扫描线渐变
  • centerX:渐变中心点的x坐标,取值范围为:0-1
  • centerY:渐变中心点的Y坐标,取值范围为:0-1
  • angle:只有linear类型的渐变才有效,表示渐变角度,值必须为45的倍数;0表示从左到右,90表示从下到上
  • gradientRadius:渐变效果的半径,只有radial和sweep类型的渐变才有效
  • useLevel:判断是否根据level绘制渐变效果,当Drawable为StateListDrawable时值为true

(5)< solid >纯色填充,与< gradient >渐变效果排斥

  • color:背景填充色,设置solid后会覆盖gradient设置的所有效果

(6)< stroke > 描边:

  • width:边框的宽度,值越大,shape的边缘线越粗
  • color:边框的颜色
  • dashWidth:边框虚线段的长度
  • dashGap:边框的虚线段的间距

(7)< padding >

  • left,top,right,bottm:依次是左上右下方向上与四周空白的边距

这里也通过Demo学习下,在res/drawable下新建两个Drawable shape文件:
rect.xml:矩形渐变效果在这里插入图片描述
rect_circle.xml:矩形纯色填充
在这里插入图片描述
activity_main.xml:
在这里插入图片描述
效果如下:
在这里插入图片描述

GradientDrawable

属性前面已经介绍过了,这里直接通过Demo来验证:
新建linear.xml:
在这里插入图片描述
radial.xml:
在这里插入图片描述
sweep.xml:
在这里插入图片描述
activity_main.xml:
在这里插入图片描述
三种效果如下:
在这里插入图片描述

BitmapDrawable

Bitmap的一种封装,用于表示图片,可以设置被它包装的Bitmap在BitmapDrawable区域中的绘制方式,包括:平铺填充,拉伸填或保持图片原始大小。根节点为< bitmap >,主要属性如下:

  • src:图片资源id
  • antialias:是否支持抗锯齿
  • filter:是否支持位图过滤,当图片尺寸被拉伸或压缩时,开启过滤效果可保持较好的显示效果
  • dither:是否对位图进行抖动处理,开启后高质量的图片在比较低质量的屏幕上不失真
  • gravity:若位图比容器小,可以设置位图在容器中的相对位置
  • tileMode:指定图片平铺填充容器的模式,设置这个的话,gravity属性会被忽略,有以下可选值:
    disabled(整个图案拉伸平铺),clamp(原图大小), repeat(平铺),mirror(镜像平铺)

通过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);
    }
}

效果如下:
在这里插入图片描述

InsetDrawable

表示把一个Drawable嵌入到另外一个Drawable的内部,并且在内部留一些间距, 与Drawable的padding属性相似,但padding属性表示的是Drawable的内容与Drawable本身的边距, 而InsetDrawable表示的是两个Drawable与容器之间的边距,当控件需要的背景比实际的边框 小的时候,比较适合使用InsetDrawable。根节点为< inset >,常用属性包括:

  • drawable:引用的Drawable资源id,如果为空,必须有一个Drawable类型的子节点
  • visible:设置Drawable是否留有边距
  • insetLeft,insetRight,insetTop,insetBottm:设置距离容器左右上下的边距

通过Demo验证一下,在res/drawable新建inset.xml:
在这里插入图片描述
效果如下:
在这里插入图片描述

ClipDrawable

表示裁剪一个Drawable,根节点为< clip >。主要属性如下:

  • clipOrietntion:设置剪切的方向,可以设置水平和竖直2个方向。并且需要在Java代码中调用setLevel()方法控制可见区大小。level为0表示完全裁剪,即不可见;值为10000表示不裁剪,level越大可见区越大。例如Android中的进度条就是使用ClipDrawable来实现的,它通过设置level的值来决定剪切区域即可见区域的大小
  • gravity:从哪个位置开始裁剪,需要和clipOrientation配合使用
  • drawable:引用的drawable资源id,为空的话需要有一个Drawable类型的子节点

通过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为一个周期去裁剪
    }
}

效果如下图所示:
在这里插入图片描述

LayerDrawable

表示一种层次化的Drawable集合,包含一个Drawable数组,然后按照数组对应的顺序来绘制,索引值最大的Drawable会被绘制在最上层。虽然这些Drawable会有交叉或者重叠的区域,但它们位于不同的层,所以并不会相互影响,根节点为< layer-list >,< item >常用属性如下:

  • drawable:引用的位图资源id,如果为空需要有一个Drawable类型的子节点
  • id:层的id
  • left:层相对于容器的左边距
  • right:层相对于容器的右边距
  • top:层相对于容器的上边距
  • bottom:层相对于容器的下边距

Demo实例
res/drawable新建layer.xml:
在这里插入图片描述
activity_main.xml:
在这里插入图片描述
效果如下:
ScaleDrawable

TransitionDrawable

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);
    }
}

效果如下:
在这里插入图片描述

LevelListDrawable

用来管理一组Drawable的,我们可以为里面的drawable设置不同的level, 当他们绘制的时候,会根据level属性值获取对应的drawable绘制到Canvas上,根节点为:< level-list >没有可以设置的属性 ,但是可以设置每个< item > 的属性。item属性如下:

  • drawable:引用的位图资源,如果为空需要有一个Drawable类型的子节点
  • minlevel:level对应的最小值,取值范围为0~10000,默认为0
  • maxlevel:level对应的最大值,取值范围为0~10000,默认为0

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);
    }
}

效果如下:
在这里插入图片描述

StateListDrawable

表示一个Drawable的集合,每个Drawable对应着View的一种状态。根节点为< selctor >,常用属性包括:

  • constantSize:固有大小是否随其状态的改变而改变。默认为false,表示固有大小会随着状态的改变而改变。为true,则表示固有大小是固定值,是内部所有Drawable的固有大小中的最大值
  • dither:是否开启抖动效果。开启后让高质量的图片的比较低质量的屏幕上不失真
  • variblePadding:其padding是否随状态的改变而改变。默认为false,表示padding是固定值,是其内部所有Drawable的padding中的最大值。为true,则表示padding会随着状态的改变而改变

< item >的属性包括:

  • drawable:引用的Drawable位图id
  • state_focused:是否获得焦点
  • state_window_focused:是否获得窗口焦点
  • state_enabled:控件是否可用
  • state_checkable:控件可否被勾选,适用于checkbox这类组件
  • state_checked:控件是否被勾选
  • state_selected:控件是否被选择,针对有滚轮的情况
  • state_pressed:控件是否被按下
  • state_active:控件是否处于活动状态,适用于slidingTab这类组件
  • state_single:控件包含多个子控件时,确定是否只显示一个子控件
  • state_first:控件包含多个子控件时,确定第一个子控件是否处于显示状态
  • state_middle:控件包含多个子控件时,确定中间一个子控件是否处于显示状态
  • state_last:控件包含多个子控件时,确定最后一个子控件是否处于显示状态

简单的Demo,按钮点击改变颜色:

res/drawable新建shape_btn_normal.xml:代表普通状态下的shape
在这里插入图片描述
shape_btn_pressed.xml:代表按下状态下的shape
在这里插入图片描述
activity_main.xml:
在这里插入图片描述
除此之外,还要新建selector文件将两者结合起来:
在这里插入图片描述
state_pressed就代表着上面介绍属性说的控件是否被按下,效果如下:
在这里插入图片描述

AnimDrawable

参考之前的文章安卓开发之Animation学习(帧、补间、属性动画)中帧动画。

自定义Drawable

涉及到自定义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的尺寸相同。

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/107213540