Android 图像绘制之 Drawable

转载自:https://www.jianshu.com/p/4cd87e3c43d6,作者:不惜留恋_ (简书)

不要冲动去自定义 View

绘制简单的图形,以及显示静态的图片,可以用 Drawable,而不用自定义 View。例如显示一张圆形图片,其实可以用 RoundBitmapDrawable,这个后面我们会看到

Drawable是什么

查了下官方文档,Drawable 是一个抽象的概念,“something that can be drawn”,就我的理解话,Drawable 是一种媒介,它可以把内容绘制到 Canvas 上。
如果你还没理解,举个例子,BitmapDrawable 包装了一个 Bitmap,如果你还不了解 Bitmap,请看我的文章 Android 图像绘制之 Bitmap。其实 BitmapDrawable 最终也是调用了 Canvas 的 drawBitmap() 方法来把 Bitmap 绘制图像到 View 中的。
Drawable的实现类
查了下 Drawable 的实现类,如ShapeDrawable,BitmapDrawable,ClipDrawable,RoundBitmapDrawable,StateListDrawableRoateDrawable。从类的名子来看,大致可以猜到这些类是干嘛的吧,是不是有种相见恨晚的感觉,那么本篇文章就是介绍这些类。当然网上很多文章都有写过,本篇文章不注重抠每个细节,但是会让你感受到这个类到底实际中有何用。
GradientDrawable
一个创建图形的 Drawable,图形包括直线(line), 椭圆(oval),圆形(oval,当椭圆的宽高相等的时候就是圆形),矩形(rectangle),圆环(ring)。不过在这之前,我需要你对 Paint 有所了解,目前我还没有写关于 Paint 的文章,我希望你能从其他文章了解下,这样能更好理解 GradientDrawable 。

GradientDrawable 它在 XML 文件中对应的根节点为 ,但是它生成的不是 ShapeDrawable,而是 GradientDrawable,但是又达到了 ShapeDrawable 的效果, 我查了下官方文档,没有错,这 TM 就有点搞事了。如果有人知道,可以告诉我为什么。

直线

<?xml version="1.0" encoding="utf-8"?>
<shape    
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:shape="line">    
    <stroke android:width="5dp" android:color="#ffff0000"/>
</shape>

在这里插入图片描述

椭圆或者圆形

<?xml version="1.0" encoding="utf-8"?>
<shape    xmlns:android="http://schemas.android.com/apk/res/android"    
android:shape="oval">    
<stroke android:width="5dp" android:color="#ffff0000"/>    
</shape>

在这里插入图片描述

如果把定义宽和高相等的话就是圆形

<?xml version="1.0" encoding="utf-8"?>
<shape    xmlns:android="http://schemas.android.com/apk/res/android"    
android:shape="oval">    
    <stroke android:width="1dp" android:color="#ffff0000"/>    
    <size android:width="100dp" android:height="100dp"/>    
</shape>

在这里插入图片描述

如果不用 Stroke( 描边,对应 Paint.STROKE ),而用Solid (填充,对应 Paint.FILL),会出现实心圆形
在这里插入图片描述

填充的颜色也可以搞个渐变色

<?xml version="1.0" encoding="utf-8"?>
<shape 
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:shape="oval">    
<gradient        
    android:startColor="@color/colorAccent"    
    android:endColor="@color/colorPrimary"/>    
<size 
    android:width="100dp" 
    android:height="100dp"/>
</shape>

在这里插入图片描述

矩形

<?xml version="1.0" encoding="utf-8"?>
<shape 
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:shape="rectangle">    
<corners 
    android:radius="10dp"/>    

<stroke 
    android:width="1dp" 
    android:color="@color/colorAccent"/>    
<size        
    android:width="100dp"        
    android:height="100dp"/>
</shape>

在这里插入图片描述
圆环

<?xml version="1.0" encoding="utf-8"?>
<shape 
    xmlns:android="http://schemas.android.com/apk/res/android"       
    android:innerRadius="100dp"       
    android:shape="ring"       
    android:thickness="10dp"       
    android:useLevel="false">    
<solid 
    android:color="@color/colorAccent"/>
</shape>

在这里插入图片描述

这里要说下这个 useLevel, 只有在 LevelListDrawable 中才设置 true,这里要设置 false,否则显示不出来。

其实 GradientDrawable 或者说是 ShapeDrawable 的绘制原理就是利用 Canvas.drawCircle() ,Canvas.drawRect() 等等方法

**

BitmapDrawable

**

BitmapDrawable 主要是用于图片的平铺( tileMode )显示,我们先看看没有设置 tileMode 的 BitmapDrawable

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

antialias:抗锯齿
dither:抗抖动,当位图的像素配置与屏幕不同时(例如 ARGB_8888 的位图和 RGB_565 的屏幕)
filter:过滤。当位图收缩或者拉伸,为了使外观平滑使用过滤
mipMap:一种图像处理技术 ,主要用于在图片缩小时,还要获得高质量图片。
在这里插入图片描述

BitmapDrawable 的侧重功能属性在于 android:tileMode=[“disabled” | “clamp” | “repeat” | “mirror”]

disable:不解释,默认不平铺
clamp:当位图小于实际显示区域的时候,边缘的颜色拉伸
repeat:当位图小于实际显示区域的时候,水平和垂直方向,重复显示图片
mirror:当位图小于实际显示区域的时候,水平和垂直方向,以镜像的方式(或者说对称的方式)重复显示图片

为了说明 tileMode ,先看一张原图

在这里插入图片描述

repeat:
在这里插入图片描述
mirror:
在这里插入图片描述

Clamp

这个就有点意思了,我看到了一个实际用法(参考博客http://blog.csdn.net/u012702547/article/details/51594131)

不会 PS 是硬伤,只能借用下别人的图了。但是我们主要学习这个实际中怎么应用。

原图是这样
在这里插入图片描述
clamp 平铺后
在这里插入图片描述

把它应用到 TextView 背景后

<TextView    
    android:id="@+id/image_view"        
    android:layout_width="match_parent"     
    android:layout_height="match_parent"    
    android:background="@drawable/bitmapfile"      
    android:padding="40dp"    
    android:text="@string/bitmap_clamp_text"    
    android:textSize="20sp"/>

在这里插入图片描述

LayerListDrawable

图层列表,就像我们平时把一张照片放在另外一张照片上,最后的照片在最上面。 LayerListDrawable 就是这样设计的。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">    
<item>        
    <shape android:shape="oval">            
        <solid android:color="@color/colorAccent"/>        
    </shape>    
</item>    
    <item android:width="64dp" android:height="64dp">        
        <bitmap           
            android:gravity="center"            
            android:src="@android:drawable/ic_input_add"/>      
    </item>
</layer-list>

在这里插入图片描述

这个是不是有点像 FloatingActionButton,把这个设置为 Button 的背景,然后设置 android:elevation,是不是就 perfect了。

使用注意事项:

我们用 控制显示的大小和位置,而用 的子节点控制显示区域,如 ,, 等等 Drawable
不用显示图片因为图片会根据显示区域缩放,如果我们将图片放在子节点中,例如 ,就算图片小于显示区域也不会放大。

StateListDrawable
在 MD 设计之前,大家应该有个印象,在点击像 ListView 的 Item 的时候,是没有波纹效果的,我们往往会自己设计一个简单的效果,就是点击 Item 的时候,背景切换个颜色。而这个就是用 StateListDrawable。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">    
  <item android:state_pressed="true">        
    <shape>            
        <corners android:radius="10dp"/>            
        <solid android:color="@color/colorAccent"/>              
    </shape>    
  </item>    

  <item>        
    <shape>            
        <corners android:radius="10dp"/>            
        <stroke 
            android:width="1dp"   
            android:color="#685252"/>        
    </shape>    
  </item>
</selector>

设置为 Button 的 background 后效果如下
在这里插入图片描述

LevelListDrawable

根据等级(level)来显示图片。典型的应用就是 WIFI 信号。用 setLevel()来控制 level,根据 level 所在的范围显示相应的图片。

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
    <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_2" />
    <item android:maxLevel="5" android:drawable="@drawable/ic_wifi_signal_3" />
    <item android:maxLevel="9" android:drawable="@drawable/ic_wifi_signal_4" />
</level-list>

maxLevel = 0 就是默认图片,看下默认效果:
在这里插入图片描述
现在给 ImageView 背景

<ImageView
        android:layout_centerInParent="true"
        android:id="@+id/wifi_image"
        android:onClick="onClickWifi"
        android:background="@drawable/level_list_drawable"
        android:layout_width="200dp"
        android:layout_height="200dp"/>
    private static int i = 1;
    public void onClickWifi(View view) {
        ImageView imageView = (ImageView) findViewById(R.id.wifi_image);
        imageView.getBackground().setLevel(i++ % 10);
    }

现在效果,每点击一次 ImageView,level 改变一次。第一次点击 ,level 是1,显示 maxLevel=“2” 图片,再点击一次,level 是2,还是显示 maxLevel=“2” 图片,依此类推,出现如下效果:
在这里插入图片描述

TransitionDrawable

在两个图像之间,通过改变 alpha 来交替显示。

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="@color/colorAccent"/>
        </shape>
    </item>

    <item>
        <bitmap android:src="@drawable/sample_1"/>
    </item>
</transition>

为 ImageView 设置这个背景

 <ImageView
        android:id="@+id/transition"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/transition_drawable"
        android:onClick="onClickTransition"/>

实现点击事件

private boolean isReverse;
    public void onClickTransition(View view) {
        ImageView imageView = (ImageView) findViewById(R.id.transition);
        TransitionDrawable drawable = (TransitionDrawable) imageView.getBackground();
        if (!isReverse) {
            drawable.startTransition(2000);
            isReverse = true;
        } else {
            drawable.reverseTransition(2000);
            isReverse = false;
        }
    }

在这里插入图片描述

InsetDrawable

看这个命名,意思是 插入式的 Drawable。也就是说,可以按照指定的位置(left,top,right,bottom)来显示图片

<?xml version="1.0" encoding="utf-8"?>
<inset
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/sample_1"
    android:insetLeft="20dp"
    android:insetRight="10dp"
    android:insetTop="10dp"/>
<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@color/colorAccent"
        android:src="@drawable/inset_drawable"/>

在这里插入图片描述
可以看到 left , top , right 有明显的 padding

ClipDrawable

从名字上看,意思是可以裁减的 Drawable。

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

clipOrientation 控制裁减的方向
gravity 控制从哪里开始裁减

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:id="@+id/clip_image"
        android:background="@drawable/clip_drawable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar2"/>
</LinearLayout>

Java Code

 final ImageView imageView = (ImageView) findViewById(R.id.clip_image);
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar2);
        seekBar.setMax(10000);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                imageView.getBackground().setLevel(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
});

在这里插入图片描述

ScaleDrawable

意思是可缩放的 Drawable。不过用起来有点费劲。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">


    <ImageView
        android:id="@+id/scale_image"
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:background="#FF4081"
        android:src="@drawable/scale_drawable"/>

    <SeekBar
        android:layout_alignParentBottom="true"
        android:id="@+id/seekBar3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"/>
</RelativeLayout>

scaleGravity 控制显示的位置

    <ImageView
        android:id="@+id/scale_image"
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:background="#FF4081"
        android:src="@drawable/scale_drawable"/>

基本设置已经完毕,但是就是不显示,我们需要控制 ScaleDrawable 的 Level,我们加入一个 SeekBar 控制 Level

 final ImageView imageView = (ImageView) findViewById(R.id.scale_image);
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar3);
        seekBar.setMax(10000);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                imageView.getDrawable().setLevel(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
});

level 控制缩小的比例的,level 值是从 0 到 10000,0代表不显示,从1开始控制图片放大,10000就是原图,看下面效果演示
在这里插入图片描述

android:level 属性在 API 24 版本后,可以在 XML 文件中添加
android:useIntrinsicSizeAsMinimum=“true”,这个设置为 true 后,就会一直显示原图的大小。
如果你想知道为什么,可以探究源码 ScaleDrawable.java 的 onBoundsChange() 方法

结束

通过本篇文章,我们要达到两点效果,一是 Drawable 是什么,二是为什么要用 Drawable。
由于篇幅原因,现在就介绍这么多,
如果大家有何疑问,可以正文评论,我会尽量解答,大家大同学习进步。

作者:不惜留恋_
链接:https://www.jianshu.com/p/4cd87e3c43d6
来源:简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值