xml_绘图: shape 篇

shape 大家应该都了解,一种 android 提供的,方便的在 android 绘制生成简单图形的 xml 文件,常见的 shape xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadiusRatio="5"
    android:shape="ring"
    android:thicknessRatio="5"
    android:useLevel="false"
>

    <stroke android:width="1dp" android:color="@color/colorPrimary"></stroke>
    <solid android:color="@color/colorPrimary"></solid>
</shape>

就是这么简单的一个 xml 文件,通过 shape 支持的 xml 标签,我们可以使用 shape 绘制出很多常用的图形,我们常常使用的 button 的背景都是用 shape 的绘制的,若是我们使用 png 代替 shape ,那么就会出很多问题的

shape 有着非常鲜明的优点,明显就是为了专门解决依赖一类需求,简单多变的多边形图案的绘制:

  • shape 支持宽高的无限拉伸,不存在 png 图片拉伸的问题
    首先 shape 不是 bitmap 位图,是我们通过 xml 文件声明号绘制信息,然后 shape 根据所在 view 的尺寸动态绘制出来的,和 SVG 矢量图有异曲同工之妙,只不过没有 SVG 这么强大罢了
  • shape 文件很小,节约 apk 安装文件大小,也能节约内存
    因为 shape 是通过 canvas 绘制出来的,所以不存在 bitmap 位图来占用大量内存

别看 shape 大家常用,但是 shape 支持很多标签和绘制技巧,这块很多同学就不是那么清楚了,用好 shape 真的能省我们很多因思考死的脑细胞,也能让 UI 少出一些图

1. shape 标签


shape 支持的基础标签不多,但是这每一个基础标标签中都支持很多属性设置,正式通过这些属性变化,绘制出一个个看似很复杂的图形出来,对于这几个基础标签我们来一个一个的看

shape 详细属性如下,不包含根标签


5445868-6ce6de5f5d971e71.png

1.1 shape 支持的基础标签

shape 支持根标签,大小,边框,边框圆角,填充色,渐变色 这6个标签,6个标签相互配合,每个标签中还有细分属性。当然了这 6个标签是干什么的很好理解,一来见名知已,二来也是大家经常使用的


Snip20180823_2.png
根本标签
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android">

    大小
    <size></size>
    边框
    <stroke></stroke>
    填充色
    <solid></solid>
     内边距
    <padding></padding>
    边框圆角
    <corners></corners>
    渐变色
    <gradient></gradient>

</shape>

1.2 shape 支持的 <shape> 根标签

Snip20180823_3.png
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle|line|oval|ring"
分别为矩形、线、椭圆、环。默认为矩形rectangle

    android:innerRadius="integer" 
shape为ring时可用,内环半径
    android:innerRadiusRatio="float"
shape为ring时可用,内环的厚度比,即环的宽度比表示内环半径,默认为3,可被innerRadius值覆盖

    android:thickness="integer"
 shape为ring时可用,环的厚度
    android:thicknessRatio="float"
shape为ring时可用,环的厚度比,即环的宽度比表示环的厚度,默认为9,可被thickness值覆盖
    android:tint="color"
给shape着色
    android:tintMode="src_in|src_atop|src_over|add|multiply|screen"
着色类型
    android:useLevel="false|true"
较少用,一般设为false,否则图形不显示。为true时可在LevelListDrawable使用
    android:visible="false|true"
    android:dither="false|true"
将在位图的像素配置与屏幕不同时(例如:ARGB 8888 位图和 RGB 565 屏幕)启用位图的抖动;值为“false”时则停用抖动。默认值为 true。
>
</shape>

我们需要注意的属性是 <shape>,该属性决定了本 shape 文件的形状,支持4种基础形状:

  • rectangle / 矩形
  • line / 线
  • oval / 椭圆
  • ring / 圆环
Snip20180826_5.png

可能 line 线的效果不是我们预想的那样,这个问题留待以后补充

善用这4种图形,结合其他属性设置,我们基本能画出常用的所有图形,比如椭圆在正方形的尺寸中就是一个圆,矩形加上圆角就是圆角矩形

剩下的其他参数:

  • tint / tintMode 是着色器,能不能在 API 19 上使用,我也没试
  • dither 一般也没见人写过
  • innerRadius / thickness 是画圆环的参数,一个内圆半径,一个外圆宽度
  • innerRadiusRatio / thicknessRatio 这是内圆半径和外圆宽度的比例,默认 3:9,测试过发现反过来写才能是我们理解的正常显示

1.3 corners支持的基础标签

corners 这个大家最熟悉了吧,圆角,配合 rectangle / 矩形 来使用了,我们使用 corners 圆角 可以画出圆角矩形来:

  • android:radius="integer"
    圆角半径,该值设置时下面四个属性失效
  • android:bottomLeftRadius="integer"
    左下角圆角半径
  • android:bottomRightRadius="integer"
    右下角圆角半径
  • android:topLeftRadius="integer"
    左上角圆角半径
  • android:topRightRadius="integer"
    右上角圆角半径

话说,为啥圆角的此存设置只有一个数值呢,按照 canvas 绘制圆角的思路,应该是有 x,y 2个参数来描述这个圆角的圆角有多大的,为啥这里就一个参数呢。只能这样理解了,shape 的圆角默认就是正方形尺寸的

1.4 padding 支持的基础标签

  • android:bottom="integer"
    设置底部边距
  • android:left="integer"
    左边边距
  • android:right="integer"
    右边
  • android:top="integer"
    顶部

1.5 solid 支持的基础标签

  • android:color="color"
    shape的填充色

填充色不用说了吧,不过有一点要说明下,要是在 shape 根标签里设置了 tint 着色器,那么就会覆盖 solid 的颜色

1.6 size支持的基础标签

  • android:height="integer"
    宽度
  • android:width="integer"
    高度

shape 一般都不指定具体的大小,因为大多数时候 shape 是作为view 的背景来存在的,此时 shape 的大小是随着 view 的大小走的。

但是有的时候 shape 无法从外接获取准确的大小参数,那么这个时候就需要 shape 自己指定大小了,比如我们在很多时候画圆和圆环当图片使用的时候,就必须指定大小了

但是若是 shape 既设置 size 了,外界页存在依附的 view 的情况下,view 的大小怎么样呢:

  • view 的 size = wrap_content
    此时 view 的最终大小跟着 shape 的大小走

  • view 的 size 有指定大小时,比如 100dp
    此时 shape 不影响 view 的最终大小

1.7 stroke 支持的基础标签

  • android:color="color"
    描边的颜色
  • android:width="integer"
    描边的宽度
  • android:dashGap="integer"
    虚线间隔
  • android:dashWidth="integer"
    虚线宽度

边框不用说了吧没,注意其中虚线的属性

1.8 gradient 支持的基础标签

找到一篇配图很准的,渐变可以参考这篇:

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

    <gradient
        android:angle="integer"
        渐变角度,当上面type为线性渐变linear时有效。角度为45的倍数,0度时从左往右渐变,角度方向逆时针
        android:startColor="color"
        渐变开始位置颜色
        android:centerColor="color"
        渐变中间位置颜色
        android:endColor="color"
        渐变结束位置颜色
        android:centerX="float"
        放射中心坐标
        android:centerY="float"
        放射中心坐标
        android:gradientRadius="integer"
        type为放射性渐变radial时有效,渐变的半径
        android:type="linear|radial|sweep"
        渐变类型,分别为线性、放射性、扫描性渐变,默认为线性渐变linear
        android:useLevel="false|true"
        与上面shape中该属性的一致
    />

</shape>

渐变是 shape 中的难点,很多人其实对渐变的设置很不熟悉,其实各个平台工具中渐变的设置都差不多,shape 中的渐变熟悉了,看其他的也大体能明白,所谓一法通万法通

什么是渐变,就是从一种颜色匀速过渡到另一种颜色,自行实现可以用 android 中的 color 插值器。这样肯定就有2个颜色,一个是开始的颜色,一个是结束的颜色,shape 还支持中间颜色,就是3色过渡

这样就能理解 startColor 、 centerColor 、 endColor 是干啥的了

另外支持3种渐变算法 type

  • linear - 线性
  • radial - 扫描性
  • sweep - 放射性
  • 默认是线性渐变 inear

对于这3种渐变算法,来个图最合适:


Snip20180828_9.png

一图在手,胜似千言万语,大家注意期中颜色变化的起始方向和顺序

  • linear
    线性这个好理解把,就是一条线,从左到右变化
  • radial - 扫描性
    也叫雷达图,按照圆心旋转
  • sweep - 放射性
    从内到外的变化

图中 radial 、sweep 都是从中间开始变化的,这是因为我们可以指定中心点,这里我们指定的是圆心
。这2个参数就是 centerX / centerY 了,取值区间[0,1],默认为0.5,即中心位置

另外还支持 centerColor 的便宜位置,默认 centerColor 肯定是在中间的,我们还可以动态设置 centerColor 在任何位置,这个属性就是 gradientRadius 了,其取值范围和动画 xml 取值一样:

  • 30dp,就是 30dp
  • 30%,图形的 30% 处

angle 属性是旋转角度,只能以 90 的倍数设置,90 代表旋转 90度,android 中是以逆时针旋转为准的

1.9 所有标签

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:dither="false|true"             //将在位图的像素配置与屏幕不同时(例如:ARGB 8888 位图和 RGB 565 屏幕)启用位图的抖动;值为“false”时则停用抖动。默认值为 true。
    android:shape="rectangle|line|oval|ring"//分别为矩形、线、椭圆、环。默认为矩形rectangle
    android:innerRadius="integer"           // shape为ring时可用,内环半径
    android:innerRadiusRatio="float"        // shape为ring时可用,内环的厚度比,即环的宽度比表示内环半径,默认为3,可被innerRadius值覆盖
    android:thickness="integer"             // shape为ring时可用,环的厚度
    android:thicknessRatio="float"          // shape为ring时可用,环的厚度比,即环的宽度比表示环的厚度,默认为9,可被thickness值覆盖
    android:tint="color"                    // 给shape着色
    android:tintMode="src_in|src_atop|src_over|add|multiply|screen" // 着色类型
    android:useLevel="false|true"           // 较少用,一般设为false,否则图形不显示。为true时可在LevelListDrawable使用
    android:visible="false|true" 
    >
    <!-- 圆角 -->
    <corners
        android:radius="integer"            // 圆角半径,该值设置时下面四个属性失效
        android:bottomLeftRadius="integer"  // 左下角圆角半径
        android:bottomRightRadius="integer" // 右下角圆角半径
        android:topLeftRadius="integer"     // 左上角圆角半径
        android:topRightRadius="integer"    // 右上角圆角半径
        />
    <!-- 渐变 -->
    <gradient
        android:useLevel="false|true"       // 与上面shape中该属性的一致
        android:type="linear|radial|sweep"  // 渐变类型,分别为线性、放射性、扫描性渐变,默认为线性渐变linear
        android:angle="integer"             // 渐变角度,当上面type为线性渐变linear时有效。角度为45的倍数,0度时从左往右渐变,角度方向逆时针
        android:centerColor="color"         // 渐变中间位置颜色
        android:startColor="color"          // 渐变开始位置颜色
        android:endColor="color"            // 渐变结束位置颜色
        android:centerX="float"             // type为放射性渐变radial时有效,设置渐变中心的X坐标,取值区间[0,1],默认为0.5,即中心位置
        android:centerY="float"             // type为放射性渐变radial时有效,设置渐变中心的Y坐标,取值区间[0,1],默认为0.5,即中心位置
        android:gradientRadius="integer"    // type为放射性渐变radial时有效,渐变的半径
        />
    <!-- 内边距 -->
    <padding
        android:bottom="integer"  // 设置底部边距
        android:left="integer"    // 左边边距
        android:right="integer"   // 右边
        android:top="integer"     // 顶部
    />
    <!-- 大小 -->
    <size
        android:height="integer"  // 宽度
        android:width="integer"   // 高度
        />
    <!-- 填充 -->
    <solid
        android:color="color"     // shape的填充色
        />
    <!-- 描边 -->
    <stroke
        android:color="color"       // 描边的颜色
        android:width="integer"     // 描边的宽度
        android:dashGap="integer"   // 虚线间隔
        android:dashWidth="integer" // 虚线宽度
    />
</shape>

2. shape 经典实战


上面讲解 xml 属性时我也没贴具体的 shape 例子,就是好放到这里,搜集了一些 shape 的典型应用,应该差不多全了,大伙忘了的来这里找找

2.1 shape 画虚线

shape 虚线的 xml 很简单

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="line"
       android:useLevel="false">

    <stroke
        android:width="5px"
        android:color="@color/colorPrimary"
        android:dashGap="5dp"
        android:dashWidth="5dp"></stroke>

</shape>
Snip20180829_10.png

上面的几个参数就能搞定,但是 shape 画虚线里面很几个坑,膈应人极了

  1. view 的 height 高度必须大于stroke 的 width 宽度,等于都不行,像上面虚线宽 5px,我给 view height 设 5px 都不行,必须大于 5px 才行,我给的 6px,另外 wrap_content 也不行,要不显示不出来
  2. 4.0 以上手机,默认使用硬解码, 虚线在实机会显示实现,必须给相关的 view 设置成软解才行 layerType="software"‘
    <View
        android:layout_width="match_parent"
        android:layout_height="6px"
        android:layerType="software"
        android:background="@drawable/shape_dash"></View>

画实现和虚线的注意点一样,区别就在于虚线的几个参数上

2.2 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"></corners>
    <stroke android:width="1dp" android:color="@color/colorPrimary"></stroke>
    <size android:width="20dp" android:height="20dp"></size>

</shape>
Snip20180829_11.png

圆角矩形没什么可说的,都是标准配参数就行了

2.3 shape 画圆形

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="oval">

    <solid android:color="@android:color/white"></solid>
    <stroke android:width="1dp" android:color="@color/colorPrimary"></stroke>

</shape>
Snip20180829_13.png

还记得 oval 画的是什么吗,是椭圆,前面说过,正方形画椭圆就是正圆,所以只要能保证尺寸是正方形就行了,可以给外层 view 设置同样的 宽高,也可以在 shape 里面写 size 设宽高,不过一般做背景时我们都是不写 size 的

2.4 shape 画圆环

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:useLevel="false">

    <solid android:color="@color/colorPrimary"></solid>
</shape>

Snip20180829_14.png

画圆环时前面说过外环和内圆默认是 3:9 的关系,我们不改就是这样的

蛋疼呢,shape 的圆环也是几个注意要点:

  1. 注意必须写 useLevel="false" ,否则不显示
  2. thickness 、 innerRadius 只支持实际单位,不支持诸如 30% 这样的相对参数,毕竟后面还有thicknessRatio 、innerRadiusRatio 这样的比例参数呢,另外 thickness 、 innerRadius 要是设置了实际大小,比如 50dp,那么不论附着的外层 view 有多大,圆环都只能按照 50dp 显示,除非我们作为 drawable 使用,否则不要写这 thickness 、 innerRadius 2个参数

2.5 shape 画渐变色

用上面圆环的例子来一个

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:useLevel="false">

    <gradient
        android:type="sweep"
        android:centerColor="@android:color/white"
        android:centerX="0.5"
        android:centerY="0.5"
        android:endColor="@android:color/holo_blue_light"
        android:startColor="@android:color/holo_red_light"/>

</shape>
Snip20180829_15.png

是不是挺好看的,早先系统默认的 loading 那个转圈的就是用 shape 写的

需要注意的是:

  • 加了渐变色,就不能在写填充色了,要不就覆盖了,什么也看不出来了

2.6 shape 画单边框

就是利用 layer-list 图层来做,2图,底下一层是边框颜色图层,上面是背景色图层,通过移动上面背景色图层来把下面边框颜色图层让出一部分出来,形成边框

效果是这样子的:


Snip20180829_1.png
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape>
            <solid android:color="@android:color/holo_red_light"></solid>
        </shape>
    </item>

    <item android:bottom="1dp">
        <shape>
            <solid android:color="@android:color/white"></solid>
        </shape>
    </item>
    
</layer-list>

这里是利用 item 的 位移属性来做的,top 就是向上移动,top = 1dp,就是给下面图层让出 1dp 出来,这就成了边框。

当然这是单边框,我们还可以给其他边设置位移,形成任意数量边框的图

试试3个边框的:


Snip20180829_2.png
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape>
            <solid android:color="@android:color/holo_red_light"></solid>
        </shape>
    </item>

    <item android:bottom="1dp" android:left="1dp" android:right="1dp">
        <shape>
            <solid android:color="@android:color/white"></solid>
        </shape>
    </item>

</layer-list>

上面是设置的正数,我们还可以设置负数,思路和上面本质上都是一样,区别是2个图层需要倒过来,具体就不写代码了。

2.7 shape 画角标图

经典的显示消息数量的角标图

效果图:


Snip20180829_4.png
<?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="@android:color/white"></stroke>
    <solid android:color="@android:color/holo_orange_dark"></solid>

</shape>

当然为了显示精确还是推荐使用自定义 view 来做,因为不同设备子的大小有差异,可能会出现文字除了背景图的问题,自己有用 canvas 就没有这个问题,还能根据文字的多少决定是圆形的还是长条圆角的,有的啃爹产品让显示 999+ ,明显圆形就放不下4个字了,能放下4个字圆也会太大显得突兀

2.8 shape 画阴影

shape 画阴影和单边框一个原理,都是移动图层,形成视觉差,我找到一个图解,看着比较容易理解:


Snip20180829_6.png

然后咱自己画一个,可以选择给一边记上边框,这样显得更显眼一点

Snip20180829_8.png
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:top="2dp" android:left="2dp">
        <shape>
            <solid android:color="@android:color/darker_gray"></solid>
        </shape>
    </item>

    <item android:right="2dp" android:bottom="2dp">
        <shape>
            <stroke android:width="1dp" android:color="#f2f2f2"></stroke>
            <solid android:color="@android:color/white"></solid>
        </shape>
    </item>

</layer-list>

以前有个很流行的交互效果没,就是按钮点下后出阴影,就是把做了2张 shape 图,一个有阴影,一个不带,然后放到 selector 里面。说实话以前我都不知道是怎么实现的,现在回过头再来看,真是简简单单啊,说明系统知识一定要全面啊,要不有时候费死劲都不知道怎么实现啊

参考文章:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容