Android中常用的Drawable
q3202dasz
8年前
<h2><strong>前言</strong></h2> <p> </p> <ul> <li> <p>本文的目录结构</p> <ul> <li>前言</li> <li>Drawable简介</li> <li>Drawable分类 <ul> <li>BitmapDrawable</li> <li>NinePatchDrawable</li> <li>ShapeDrawable</li> <li>LayerDrawable</li> <li>StateListDrawable</li> <li>LevelListDrawable</li> <li>TransitionDrawable</li> <li>InsetDrawable</li> <li>ScaleDrawable</li> <li>ClipDrawable</li> </ul> </li> <li>自定义Drawable</li> </ul> </li> </ul> <h2><strong>Drawable简介</strong></h2> <ul> <li> <p>Drawable表示的是一种可以在Canvas上进行绘制的抽象的概,可以是纯颜色,也可以是图片等。</p> </li> <li> <p>Drawable一般是通过XML来定义,当然也可以通过代码来创建。</p> </li> <li> <p>Drawable类是抽象类,它是所有Drawable的基类。</p> </li> <li> <p>Drawable的 getIntrinsicWidth() 和 getIntrinsicHeight() 获取其内部宽/高。并不是所有Drawable都有内部宽/高。图片所形成的Drawable的内部宽/高就是图片的宽/高。颜色所形成的Drawable就没有内部宽/高的概念了。</p> </li> <li> <p>Drawable是没有大小概念的,当用作View的背景时,会被拉伸至View的同等大小。</p> </li> <li> <p>Drawable的使用范围很单一:一是作为ImageView中的图像显示,二是作为View的背景。</p> </li> </ul> <h2><strong>Drawable分类</strong></h2> <h2><strong>BitmapDrawable</strong></h2> <ul> <li> <p>对应 <bitmap> 标签,几乎是最简单的Drawable,表示一张图片。</p> </li> <li> <p>在实际开发中,我们可直接引用原始的图片,也可通过XML的方式来描述它。</p> </li> </ul> <pre> <code class="language-java"><?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:mipMap="[true | false]" android:tileMode="[disabled | clamp | repeat | mirror]" /></code></pre> <ul> <li> <p>android:src :图片的资源id。</p> </li> <li> <p>android:antialias :是否开启图片抗锯齿。开启后会让图片变得平滑,同时也会一定程度上降低图片的清晰度。</p> </li> <li> <p>android:dither :是否开启抖动效果。开启后可让高质量的图片在低质量的屏幕上能保持较好的显示效果。</p> </li> <li> <p>android:filter :是否开启过滤效果。当图片尺寸被拉伸或压缩时,开启过滤效果可保持较好的显示效果。</p> </li> <li> <p>android:gravity :当图片小于容器的尺寸时,设置此属性对图片进行定位。此属性的可选项较多,也可用 | 来组合使用。</p> </li> </ul> <table> <thead> <tr> <th>可选项</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>top</td> <td>将图片放在容器的顶部,不改变其大小。</td> </tr> <tr> <td>bottom</td> <td>将图片放在容器的底部,不改变其大小。</td> </tr> <tr> <td>left</td> <td>将图片放在容器的左部,不改变其大小。</td> </tr> <tr> <td>right</td> <td>将图片放在容器的右部,不改变其大小。</td> </tr> <tr> <td>center_vertical</td> <td>使图片竖直居中,不改变其大小。</td> </tr> <tr> <td>center_horizontal</td> <td>使图片水平居中,不改变其大小。</td> </tr> <tr> <td>fill_vertical</td> <td>图片竖直方向填充容器</td> </tr> <tr> <td>fill_horizontal</td> <td>图片水平方向填充容器</td> </tr> <tr> <td>center</td> <td>使图片居中,不改变其大小。</td> </tr> <tr> <td>fill</td> <td>图片填充容器,默认值。</td> </tr> <tr> <td>clip_vertical</td> <td>竖直方向的裁剪。</td> </tr> <tr> <td>clip_horizontal</td> <td>水平方向的裁剪。</td> </tr> </tbody> </table> <ul> <li> <p>android:mipMap :是否开启纹理映射。</p> </li> <li> <p>android:tileMode :平铺模式。</p> </li> </ul> <table> <thead> <tr> <th>可选项</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>disabled</td> <td>默认值,关闭平铺模式。</td> </tr> <tr> <td>clamp</td> <td>图片四周的像素会扩展到周围区域。</td> </tr> <tr> <td>repeat</td> <td>简单的水平和竖直方向上的平铺效果。</td> </tr> <tr> <td>mirror</td> <td>在水平和竖直方向上的镜面投影效果。</td> </tr> </tbody> </table> <h2><strong>NinePatchDrawable</strong></h2> <ul> <li> <p>表示一张 .9 格式的图片。 .9 图片可自动地根据所需的宽/高进行相应的缩放并保证不失真。</p> </li> <li> <p>在实际使用中直接引用 .9 图片,也可以通过XML来描述它。</p> </li> </ul> <pre> <code class="language-java"><?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]" /></code></pre> <ul> <li> <p>android:src :图片的资源id。</p> </li> <li> <p>android:dither :是否开启抖动效果。开启后可让高质量的图片在低质量的屏幕上能保持较好的显示效果。</p> </li> </ul> <h2><strong>ShapeDrawable</strong></h2> <ul> <li>图形。可是纯色的图形,也可以是具有渐变效果的图形。</li> </ul> <pre> <code class="language-java"><?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:topLeftRaidus="integer" android:topRightRaidus="integer" android:bottomLeftRaidus="integer" android:bottomRightRaidus="integer" /> <gradient android:angle="integer" android:centerX="integer" android:centerY="integer" android:centerColor="color" android:endColor="color" android:gradientRadius="integer" android:startColor="color" android:type="[linear | radial | sweep]" android:useLevel="[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" /></code></pre> <ul> <li> <p>android:shape :图形的形状,可选有 <strong>rectangle(矩形)、oval(椭圆)、line(横线)和ring(圆环)</strong> 。默认是rectangle。注意line和ring必须通过 <stroke> 标签来指定线的宽度和颜色等信息,否则无法达到预期的显示效果。</p> </li> <li> <p>android:shape="ring" 有5个特殊属性。</p> </li> </ul> <table> <thead> <tr> <th>属性</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>android:innerRadius</td> <td>圆环的内半径。<Br>会覆盖android:innerRadiusRatio。</td> </tr> <tr> <td>android:innerRadiusRatio</td> <td>内半径占整个Drawable宽度的比例。<br> 默认为9,若为n,则内半径 = 宽度 / n。</td> </tr> <tr> <td>android:thickness</td> <td>圆环的厚度。即外半径减去内半径的大小。<Br>会覆盖android:thicknessRatio</td> </tr> <tr> <td>android:thicknessRatio</td> <td>厚度占整个Drawable宽度的比例。<br> 默认为3,若为n,则厚度 = 宽度 / n。</td> </tr> <tr> <td>android:useLevel</td> <td>常为false,除非它被当做是LevelListDrawable。</td> </tr> </tbody> </table> <ul> <li> <p><corners> :表示shape的四个圆角的角度,只适用于矩形。</p> <ol> <li>android:radius :为四个角同事设定相同的角度。优先级比以下4个属性要低。</li> <li>android:topLeftRadius :左上角的角度。</li> <li>android:topRightRadius :右上角的角度。</li> <li>android:bottomLeftRadius :左下角的角度。</li> <li>android:bottomRightRadius :右下角的角度。</li> </ol> </li> <li> <p><solid> :纯色填充,通过 android:color 指定填充的颜色。</p> </li> <li> <p><gradient> :渐变效果。与 <solid> 纯色填充是互相排斥的。</p> <ol> <li>android:angle :渐变的角度。默认为0,其值必须为45的倍数。此角度会影响渐变的方向,0表示从左到右,90表示从下到上。</li> <li>android:centerX :渐变的中心点的X坐标。</li> <li>android:centerY :渐变的中心点的Y坐标。</li> <li>android:startColor :渐变的起始色。</li> <li>android:centerColor :渐变的中间色。</li> <li>android:endColor :渐变的结束色。</li> <li>android:gradient :渐变半径。仅当 android:type="radial" 时有效。</li> <li>android:useLevel :一般为false,当Drawable作为StateListDrawable时为true。</li> <li>android:type :渐变的类别。有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变),默认为linear。</li> </ol> </li> <li> <p><stroke> :描边。</p> <ol> <li>android:width :描边的宽度。</li> <li>android:color :描边的颜色。</li> <li>android:dashWidth :虚线的宽度。</li> <li>android:dashGap :虚线之间的间隔。</li> </ol> </li> <li> <p><padding> :空白。有 top 等四个属性。</p> </li> <li> <p><size> :大小。其 android:width 和 android:height 分别设定shape的宽/高。注意,这个表示的是shape的固有大小,但并不是其最终大小。</p> </li> </ul> <h2><strong>LayerDrawable</strong></h2> <ul> <li>对应 <layer-list> 标签,表示一种层次化的Drawable集合,通过将不同的Drawable放置在不同的层上面从而达到一种叠加后的效果。</li> </ul> <pre> <code class="language-java"><?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" /> <!-- 其他item --> </layer-list></code></pre> <ul> <li> <p>一个layer-list可包含多个item,每个item表示一个Drawable。可在 android:drawable 中引用一个现有的Drawable资源,也可在 <item> 中自定义Drawable。</p> </li> <li> <p>默认情况下,layer-list中的所有Drawable都会被缩放至View的大小。可设置Drawable相对于View的上下左右偏移量。另外对于bitmap,需要使用其 android:gravity 来控制图片的显示效果。</p> </li> <li> <p>layer-list有层次的概念, <strong>下面的item会覆盖上面的item</strong> 。通过合理的分层,可实现一些特殊的叠加效果。</p> </li> <li> <p>利用layer-list实现一个文本输入框。</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> <solid android:color="#0ac39e" /> </shape> </item> <item android:bottom="6dp"> <shape android:shape="rectangle"> <solid android:color="#ffffff" /> </shape> </item> <item android:bottom="1dp" android:left="1dp" android:right="1dp"> <shape android:shape="rectangle"> <solid android:color="#ffffff" /> </shape> </item> </layer-list></code></pre> <img src="https://simg.open-open.com/show/4ef180904917f5f1229108d561a1c970.jpg"> <p>layer-list背景的输入框</p> </li> </ul> <h2><strong>StateListDrawable</strong></h2> <ul> <li>对应 <selector> 标签,表示一个Drawable的集合。 <strong>每个Drawable对应着View的一种状态</strong> ,系统会根据View的状态来选择合适的Drawable。</li> </ul> <pre> <code class="language-java"><?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]" /> <!-- 其他item --> </selector></code></pre> <ul> <li> <p>android:constantSize :StateListDrawable的固有大小是否不随着其状态的改变而改变。默认为false,即随着状态的改变而改变大小。若为true,则表示其内部所有Drawable的固有大小的最大值。</p> </li> <li> <p>android:dither :是否开启抖动效果。</p> </li> <li> <p>android:variblePadding :StateListDrawable的padding是否随着其状态的改变而改变。默认为false,表示其内部所有Drawable的padding的最大值。若为true,表示会随着状态的改变而改变。</p> </li> <li> <p><item> :表示某种状态下的一个具体的Drawable。用 android:drawable 指定一个现有Drawable的资源id,剩下的属性表示的是View的各种状态。</p> </li> </ul> <table> <thead> <tr> <th>状态</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>android:state_pressed</td> <td>表示按下状态。</td> </tr> <tr> <td>android:state_focused</td> <td>表示已经获取了焦点。</td> </tr> <tr> <td>android:state_selected</td> <td>表示用户选择了View。</td> </tr> <tr> <td>android:state_checked</td> <td>表示用户选中了View。<Br>适用于CheckBox这类在选中和非选中状态之间进行切换的View。</td> </tr> <tr> <td>android:state_enabled</td> <td>表示View当前处于可用状态。</td> </tr> </tbody> </table> <ul> <li>系统会按照从上到下的顺序查找,直至查找到第一条匹配的item。一般来说,默认的item都应该放在selector的最后一条,并且不附带任何的状态。</li> </ul> <h2><strong>LevelListDrawable</strong></h2> <ul> <li>对应 <level-list> 标签,表示一个Drawable集合,集合中的每个Drawable都有一个等级的概念。根据不同的等级,LevelListDrawable会切换为对应的Drawable。</li> </ul> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <level-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@[package:]drawable/drawable_resource" android:maxLevel="integer" android:minLevel="integer" /> <!-- 其他item --> </level-list></code></pre> <ul> <li> <p>每个item表示一个Drawable,并且有对应的等级范围,在 maxLevel 和 minLevel 之间。取值范围为0~10000,默认为0。</p> </li> <li> <p>若作为View背景时,可通过Drawable的 setLevel() 来设置不同的等级来切换具体的Drawable。若作为ImageView的前景,可通过ImageView的 setImageLevel() 来切换。</p> </li> </ul> <h2><strong>TransitionDrawable</strong></h2> <ul> <li>对应于 <transition> 标签,用于实现两个Drawable之间的淡入淡出效果。</li> </ul> <pre> <code class="language-java"><?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" /> <!-- 另一个item,注意transition只包含两个item --> </transition></code></pre> <ul> <li>常将TransitionDrawable作为View的背景,再调用它的 startTransition() 和 reverseTransition() 来实现淡入淡出效果以及它的逆过程。</li> </ul> <pre> <code class="language-java">ImageView ivBg = (ImageView) findViewById(R.id.iv_bg); TransitionDrawable drawable = (TransitionDrawable) ivBg.getBackground(); drawable.startTransition(1000);</code></pre> <h2><strong>InsetDrawable</strong></h2> <ul> <li>对应 <inset> 标签,可将其他Drawable内嵌到自己当中,并可在四周留出一定的间距。</li> </ul> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@[package:]drawable/drawable_resource" android:inset="dimension" android:insetTop="dimension" android:insetRight="dimension" android:insetBottom="dimension" android:insetLeft="dimension" /></code></pre> <ul> <li>当一个View希望自己的背景比自己的实际区域小的时候,可采用InsetDrawable,并通过 inset* 属性来设置留白。</li> </ul> <h2><strong>ScaleDrawable</strong></h2> <ul> <li>对应 <scale> 标签,将Drawable缩放到一定比例。</li> </ul> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <scale xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@[package:]drawable/drawable_resource" android:scaleGravity="[top | bottom | left | right | center_vertical | center_horizontal | center | fill_vertical | fill_horizontal | fill | clip_vertical | clip_horizontal]" android:scaleWidth="percentage" android:scaleHeight="percentage" /></code></pre> <ul> <li> <p>android:scaleGravity :等同于BitmapDrawable的 android:gravity 。</p> </li> <li> <p>android:scaleWidth 和 android:scaleHeight :指定Drawable宽/高的缩放比例,以百分比的形式表示。如 70% ,表示缩放到原来的30%。</p> </li> </ul> <ul> <li>从ScaleDrawable的 draw() 源代码来看,若等级为0时,不可见。等级默认为0,所以调用ScaleDrawable的 setLevel() 设置一个大于0的等级,它才会可见。</li> </ul> <pre> <code class="language-java">public void draw(Canvas canvas) { final Drawable d = getDrawable(); if (d != null && d.getLevel() != 0) { d.draw(canvas); } }</code></pre> <ul> <li>分析ScaleDrawable的 onBoundsChange() 可看出 <strong>其内部mDrawable的大小和等级以及缩放比例的关系</strong> 。</li> </ul> <pre> <code class="language-java">// iw常为0 final int iw = min ? d.getIntrinsicWidth() : 0; // 1. MAX_LEVEL为10000,即等级的取值范围为0~10000。 // 2. 计算得到的最终w为其内部mDrawable最后显示的宽度。因为是-=,所以右边算数得到的值越大,则最终w就越小。 // 3. level越大,其内部mDrawable就越大。 // 4. ScaleDrawable的XML中所定义的缩放比例越大,即mState.mScaleWidth越大,则内部mDrawable就越小。 w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);</code></pre> <h2><strong>ClipDrawable</strong></h2> <ul> <li>对应 <clip> 标签,用来裁剪另一个Drawable。</li> </ul> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <clip xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@[package:]drawable/drawable_resource" android:clipOrientation="[vertical | horizontal]" android:gravity="[top | bottom | left | right | center_vertical | center_horizontal | center | fill_vertical | fill_horizontal | fill | clip_vertical | clip_horizontal]" /></code></pre> <ul> <li> <p>android:clipOrientation :表示裁剪方向,可选为水平和竖直。</p> </li> <li> <p>android:gravity :表示对齐方式,需要和 clipOrientation 一起发挥作用。它的取值有下表各种选项,可用 | 来组合使用。</p> </li> </ul> <table> <thead> <tr> <th>选项</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>top</td> <td>放在容器的顶部,不改变大小。<br> 若为竖直裁剪,则从底部开始裁剪。</td> </tr> <tr> <td>bottom</td> <td>放在容器的底部,不改变大小。<br> 若为竖直裁剪,则从顶部开始裁剪。</td> </tr> <tr> <td>left</td> <td>这是默认值。放在容器的左边,不改变大小。<br> 若为水平裁剪,则从右边开始裁剪。</td> </tr> <tr> <td>right</td> <td>放在容器的右边,不改变大小。<br> 若为水平裁剪,则从左边开始裁剪。</td> </tr> <tr> <td>center_vertical</td> <td>在容器中竖直居中,不改变大小。<br> 若为竖直裁剪,则从上下同时裁剪。</td> </tr> <tr> <td>center_horizontal</td> <td>在容器中水平居中,不改变大小。<br> 若为水平裁剪,则从左右同时裁剪。</td> </tr> <tr> <td>center</td> <td>居中,不改变大小。<br> 若为竖直裁剪,则从上下同时裁剪。<br> 若为水平裁剪,则从左右同时裁剪。</td> </tr> <tr> <td>fill_vertical</td> <td>在竖直方向上填充容器。<br> 若为竖直裁剪,仅当等级为0时才能有裁剪行为。</td> </tr> <tr> <td>fill_horizontal</td> <td>在水平方向上填充容器。<br> 若为水平裁剪,仅当等级为0时才能有裁剪行为。</td> </tr> <tr> <td>fill</td> <td>完全填充容器。<br> 仅当等级为0时才能有裁剪行为。</td> </tr> <tr> <td>clip_vertical</td> <td>附加选项,表示竖直方向的裁剪。较少使用。</td> </tr> <tr> <td>clip_horizontal</td> <td>附加选项,表示水平方向的裁剪。较少使用。</td> </tr> </tbody> </table> <ul> <li>ClipDrawable的裁剪程度由level控制,调用 setLevel() 可修改此值。其取值范围为0~10000。0表示完全裁剪,即整个Drawable都不可见;而10000表示不裁剪。也 可将level理解为可见区域大小,0表示可见区最小,10000表示可见区最大。</li> </ul> <h2><strong>自定义Drawable</strong></h2> <ul> <li> <p>Drawable的工作原理很简单,其核心就是 draw() 。系统会调用Drawable的 draw() 来绘制View的背景或ImageView的图像,于是我们可通过重写其 draw() 来实现自定义Drawable。</p> </li> <li> <p>通常我们没有必要去自定义Drawable,因为无法在XML中使用自定义Drawable,这就降低了其使用范围。</p> </li> <li> <p>创建自定义Drawable,必须重写其 draw() 、 setAlpha() 、 setColorFilter() 、 getOpacity() 等方法。以下为自定义Drawable示例:</p> </li> </ul> <pre> <code class="language-java">/** * 自定义Drawable * Created by daking on 16/9/15. */ public class CustomDrawable extends Drawable { private Paint mPaint; public CustomDrawable(int color) { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(color); } @Override public void draw(Canvas canvas) { final Rect rect = getBounds(); float cx = rect.exactCenterX(); float cy = rect.exactCenterY(); canvas.drawCircle(cx, cy, Math.min(cx, cy), mPaint); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); invalidateSelf(); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); invalidateSelf(); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } }</code></pre> <pre> <code class="language-java">CustomDrawable drawable = new CustomDrawable(Color.BLUE); imageView.setBackgroundDrawable(drawable);</code></pre> <ul> <li> <p>若自定义的Drawable有固有大小时,要重写 getIntrinsicWidth() 和 getIntrinsicHeight() 。因为这两个方法会影响到View的 wrap_content 布局。</p> </li> <li> <p>注意,Drawable的内部大小不等于Drawable的实际区域大小。可通过 getBounds() 获得Drawable的实际区域大小,一般与它的View尺寸相同。</p> </li> </ul> <p> </p> <p>来自:http://www.jianshu.com/p/fd358b03b64c</p> <p> </p>