1、前言
今天来跟大家聊一聊关于Android当中的绘图技巧,绘图技巧,不管是自定义控件,还是在我们去做自定义商业动画,都会常常的使用到,所以在这里和大家聊一聊关于绘图技巧的问题,如果那里有错误,希望各位大神指出。
2、正文
我们知道绘图的话,关于画布,画笔,这些Goolge提供的类有很大的关联,但是我们今天着重不是这些,而是从我们要给什么地方画图,这个是今天的重点,至于画图的细节我们以后会说的。
Android系统当中把我们所有的关于可以绘制的对象抽象出来,叫做Drawable对象,相信这个对象大家一定都不会陌生,在我们的实际开发当中我们基本天天都能遇到,今天就和大家聊一聊关于Drawable的各种用法。
我们想一想在我们的实际开发当中我们是怎么样去运用我们的Drawable的,我们会在res/drawable这个资源文件下面定义各种各样的Xml的文件,我们的Xml有不同的节点,其实我们这里的一个Xml的节点,就对应我们的Drawable的实现类,也就是说我们我们创建出来的Xml的文件,会被SDK自动的根据你的节点去创建相应的一个Drawable实现来,从而去实现我们所要的效果,在这里我们可以去类比一下我们的View,我们View当中的控件貌似也是这么定义的,在Xml当中去设定我们View控件所要的属性。但是我们的View可以在代码当中New出来,我们在这里试想一下,我们的Drawable也是可以在代码当中去new出来的,然后通过代码去给他去添加属性,答案是可以的,但是我们又想了想,我们new一个View有用么,我们一般不是new一个Button,一个ImageVeiw的么,我们可以去new一个Drawable的实现类,然后通过Drawable去进行管理,就像我们可以去用我们的View,ViewGroup去管理我们的控件和布局。
那么我们来看看Drawable的哪些节点对应的哪些实现类呢!然后会给大家详细的去做介绍。为了让各位看的更加的清楚,相信大家就非常的清楚了
- selector : StateListDrawable
- layer-list : LayerDrawable
- transition : TransitionDrawable
- color : ColorDrawable
- shape : GradientDrawable
- scale : ScaleDrawable
- clip : ClipDrawable
- rotate : RotateDrawable
- animation-list : AnimationDrawable:
- insert : InsetDrawable:
- bitmap : BitmapDrawable
- nine-patch : NinePatchDrawable:
1 selector StateListDrawable
这个叫做状态的选择器,首先我们需要明白一点,就是StateListDrawable里面维护了不止一个Drawable对象,就是一组Drawable对象。既然它叫做状态选择器,说白了就是可以去根据不同的状态去返回不同的Drawable对象,而这些状态是由系统去进行提供的,至于都有哪些状态,按下状态、选中状态、默认状态、禁用状态等等,如果想了解更加全面,请大家参考Google的API,我们的Button的背景一般都使用这个Drawable,还有就是TV的海报也是用它,基本我们去和用户做交互的控件的背景都会使用到它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/press"/>
<item android:drawable="@drawable/normal"/>
</selector>
|
在我们的Layout当中只需要引入一下我们刚刚用Xml定义的Drawable,就OK了
1 2 3 4 5 6 7 8 9 10 11 12
| <?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:gravity="center">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/selector_bg" />
</RelativeLayout>
|
效果图如下,当我点击的按下的时候会发生改变,当我抬起的时候会变为原来的,这只是一种状态,还有别的状态,笔者这里就不一一列举了
2、level-list LevelListDrawable
这个我的理解是自定义选择器,首先在LevelListDrawable里面和我们StateListDrawable一样,维护了一组的Drawable的对象,同时维护了每一个Drawable要显示的Level范围,不同的是上述是通过系统发出的状态去做出一个处理去显示我们的Drawable对象,而这个是自己定义一个范围去匹配响应的Drawable去显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <level-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/fm_vehicle_linking_1" android:maxLevel="1"/> <item android:drawable="@drawable/fm_vehicle_linking_2" android:maxLevel="2"/> <item android:drawable="@drawable/fm_vehicle_linking_3" android:maxLevel="3"/> <item android:drawable="@drawable/fm_vehicle_linking_4" android:maxLevel="4"/> <item android:drawable="@drawable/fm_vehicle_linking_5" android:maxLevel="5"/> <item android:drawable="@drawable/fm_vehicle_linking_6" android:maxLevel="6"/> <item android:drawable="@drawable/fm_vehicle_linking_7" android:maxLevel="7"/> <item android:drawable="@drawable/fm_vehicle_linking_8" android:maxLevel="8"/> <item android:drawable="@drawable/fm_vehicle_linking_9" android:maxLevel="9"/> <item android:drawable="@drawable/fm_vehicle_linking_10" android:maxLevel="10"/> <item android:drawable="@drawable/fm_vehicle_linking_11" android:maxLevel="11"/> <item android:drawable="@drawable/fm_vehicle_linking_12" android:maxLevel="12"/> <item android:drawable="@drawable/fm_vehicle_linking_13" android:maxLevel="13"/> <item android:drawable="@drawable/fm_vehicle_linking_14" android:maxLevel="14"/> </level-list>
|
我们的Layout使用一个seekBar 和一个ImageView去展示我们的图片,相当于一个载体。没有什么难度,楼主在这里就不去贴图片了我们直接来看看我们的Activity的代码,在我们的seekBar上面增加一个监听器,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private void initView() { final ImageView image = (ImageView) findViewById(R.id.iv); final Drawable drawable = getResources().getDrawable(R.drawable.levellist); SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar.setMax(14); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { drawable.setLevel(progress); image.setImageDrawable(drawable); }
@Override public void onStartTrackingTouch(SeekBar seekBar) { }
@Override public void onStopTrackingTouch(SeekBar seekBar) { } }); }
|
效果呢!就是如下这样啦
3、layer-list LayerDrawable
这个是图层的Drawable,它同样是维护了一组的Drawable,但是会根据我们在XML当中定义的顺序去给它做一个顺序显示,列表的最后一个drawable绘制在最上层。每一个Drawable处于一个不同的图层,也许会出现重叠,交叉的现象,但是不会首到另外Drawable的影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/live_color_2"/>
<item android:drawable="@drawable/live_color_3" android:top="30dip" android:left="30dip" android:right="30dip" android:bottom="30dip" />
<item android:drawable="@drawable/live_color_4" android:top="60dip" android:left="60dip" android:right="60dip" android:bottom="60dip" />
</layer-list>
|
我们的Layout同第一个一样,直接去引用一下就OK
1 2 3 4 5 6 7
| <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/layerlist" />
|
效果呢,如下,但是请注意上述注释里面所说的情况
4、transition TransitionDrawable
这是一个LayerDrawable子类,那么它涉及到的也将会是图层的知识,但是他只涉及到了两个图层的变化,就是把上面和下面的图层进行交替显示,通过改变他们的透明度,为维护了一个平滑的透明度变化的动画效果,开启动画调用startTransition()。可以反向开启调用 reverseTransition()。
1 2 3 4 5 6
| <transition xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/live_color_2"/> <item android:drawable="@drawable/live_color_4"/> </transition>
|
Activity里面去设置一下这个Drawable,比如什么时候开启过度透明度动画,设置动画时长之类的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView image = (ImageView) findViewById(R.id.iv);
Resources res = getResources(); TransitionDrawable transition = (TransitionDrawable)res.getDrawable(R.drawable.transition);
image.setImageDrawable(transition); transition.startTransition(2000);
}
|
效果如下:
5、color ColorDrawable
这个Drawable对象,描述的不像上面的,它描述一个单个Drawable,在其内部定义颜色属性,当要绘制的时候,会读取到里面的颜色值,把这个颜色设置给将要绘制的Paint对象上面,注意这个Drawable一旦绘制,就不好修改了。
1 2 3
| <?xml version="1.0" encoding="utf-8"?> <color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#FF0000"/>
|
颜色是怎么归类的 6位 ARGB 这个楼主就默认大家都会了,也就不浪费时间了
6、shape GradientDrawable
这个Drawable大家可以理解为上述ColorDrawable的加强版,在这个Drawable中维护了很多属性,好方便我们来使用,使用这个Drawable,我们可以实现是一个区域内的颜色渐变,其中主要分为线性渐变,发散渐变,平铺渐变,下面我们来研究一下,在它的内部定义了什么属性来达到我们实现炫酷的效果
- 1、size:定义区域的大小
- 2、gradient:设置区域背景的渐变效果
- 3、solid:设置区域的背景颜色,如果设置了solid会覆盖gradient的效果
- 4、stroke:设置区域的边框效果
- 5、padding:设置区域的内边距,
这个Drawable很常用,同时也很重要,所以楼主会详细一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient android:startColor="#FF0000" android:centerColor="#00FF00" android:endColor="#0000FF" android:type="linear"/>
<corners android:bottomLeftRadius="10dp" android:bottomRightRadius="10dp" android:topLeftRadius="10dp" android:topRightRadius="10dp"/>
<padding android:bottom="5dip" android:left="5dip" android:right="5dip" android:top="15dip"/>
<stroke android:width="5dip" android:color="#0000FF" android:dashGap="2dip" android:dashWidth="1dip"/>
</shape>
|
效果如下:
7、scale ScaleDrawable
这个Drawable主要就是对原有的Drawable去进行一定百分比的缩放,我们可以选取不同的缩放中心对这个Drawable进行控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/press" android:scaleGravity="center_vertical|center_horizontal" android:scaleHeight="50%" android:scaleWidth="50%" />
|
准备好以后,我们来运行一下,但是我们发现并没有显示图片,查看ScaleDrawable的源码,发现draw()方法有判断getLevel() != 0才绘制。而我们在没有setLevel()时,默认getLevel()都是0。
那我们先设置10000吧,我们再次运行,发现没有按照我们50%的比例进行缩放,然后我们去看官方文档
- A Drawable that changes the size of another Drawable based on its current level value,
我们发现ScaleDrawable的缩放,并不是自动的建立在原有Drawable尺寸的基础上的。而是,需要给原有的Drawable指定一个Level,然后ScaleDrawable是在这个Level的基础上进行缩放的!
注意:设置的值是缩小的比例。也就是说,设置0.5,意为缩小50%!!而不是原始大小的50%
那么我们在我们的Activity里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
ScaleDrawable drawable = (ScaleDrawable) getResources().getDrawable(R.drawable.scale);
ImageView image = (ImageView) findViewById(R.id.iv); image.setImageDrawable(drawable); image.setImageLevel(1); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
ScaleDrawable drawable = (ScaleDrawable) getResources().getDrawable(R.drawable.scale);
ImageView image = (ImageView) findViewById(R.id.iv); image.setImageDrawable(drawable); image.setImageLevel(1); }
|
效果呢 如下:
8、clip ClipDrawable
这个Drawable主要就是对原有的Drawable进行一个裁剪,我们会有gravity选择在父容器的对齐方式,然后通过Drawable提供的level去设置我们去裁剪的大小,裁剪是从0到10000,0代表完全不显示,10000代表完全显示。
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/press" android:clipOrientation="horizontal" android:gravity="center"/>
|
定义出了我们自已要的Drawable,然后在我们的Activity进行应用就OK了
下面看Activity的代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); }
private void initView() { final ImageView image = (ImageView) findViewById(R.id.iv); final ClipDrawable drawable = (ClipDrawable) getResources().getDrawable(R.drawable.clip); SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar.setMax(10000); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { drawable.setLevel(progress); image.setImageDrawable(drawable); }
@Override public void onStartTrackingTouch(SeekBar seekBar) { }
@Override public void onStopTrackingTouch(SeekBar seekBar) { } }); }
|
下面贴出效果图
9、rotate RotateDrawable
这个Drawable主要就是对原有的Drawable进行一定角度的旋转,基于当前的level,进行旋转的drawable。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/press" android:visible="true" android:fromDegrees="0" android:toDegrees="360" android:pivotX="50%" android:pivotY="50%"> </rotate>
|
在这里我们对旋转度数做一个详细的介绍,
图片将从0到360进行旋转。level值为10000,也就是说level每加1000,即顺时针旋转360/10000*1000=36度。
可以根据显示看出来效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); }
private void initView() { final ImageView image = (ImageView) findViewById(R.id.iv); final RotateDrawable drawable = (RotateDrawable) getResources().getDrawable(R.drawable.rotate); SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar.setMax(10000); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { drawable.setLevel(progress); image.setImageDrawable(drawable); }
@Override public void onStartTrackingTouch(SeekBar seekBar) { }
@Override public void onStopTrackingTouch(SeekBar seekBar) { } }); }
|
效果呢,如下:
10、animation-list AnimationDrawable
这个Drawable里面维护了一组Drawable,通过一定的顺序,一定的显示时长去做一个Drawable的逐个显示,那么这么就会形成一个动画,也就是说每一个Drawable就是这个动画当中的一个帧,你可以通过oneshot属性来控制该动画播放完毕以后是否重新播放,主动调用AnimationDrawable的start播放动画,关于AnimationDrawable这个动画大家可以去看一下我的关于动画专题的博客。
11、inset InsetDrawable
这个Drawable主要是说可以把一个Drawable插入到另外一个Drawable的内部,并且在内部留一些间距
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/press" android:insetBottom="30dp" android:insetLeft="30dp" android:insetRight="30dp" android:insetTop="30dp" > </inset>
|
layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?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:clipChildren="false" android:gravity="center">
<ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#aaa" android:src="@drawable/insert" /> </RelativeLayout>
|
效果如下
12、bitmap BitmapDrawable
这个Drawable主要是对Bitmap的一种封装,可以设置它包装的bitmap在BitmapDrawable区域内的绘制方式,如平铺填充、拉伸填充或者保持图片原始大小,也可以在BitmapDrawable区域内部使用gravity指定的对齐方式。
BitmapDrawable就是封装了一个位图。以文件的方式,封装一个原始的位图。以Xml方式,可以对原始的位图进行一系列的处理,比如说抗锯齿,拉伸,对齐等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="1" android:antialias="true" android:dither="true" android:filter="true" android:gravity="clip_vertical" android:src="@drawable/press" android:tileMode="disabled" />
|
layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" >
<ImageView android:id="@+id/iv" android:layout_width="400dip" android:layout_height="400dip" android:src="@drawable/bitmap" />
</RelativeLayout>
|
效果:
13、nine-patch
NinePatchDrawable:这个就是我们知道的点九图片,也就是.9.png,会根据内容的大小对其内容进行拉伸,注意:但是不能压缩,这也就是我们看到的切图为什么一开始都会做得很小,
1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <nine-patch xmlns:android="http://schemas.android.com/apk/res/android" android:src="@drawable/list_tag_clear" android:dither="true"/>
|
我们要明白src对应的应该是一个.9图片,
dither 是否图像抖动处理,当每个颜色值以低于8位表示时,对应图像做抖动处理可以实现在可显示颜色总数比较低(比如256色)时还保持较好的显示效果。
layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?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:clipChildren="false" >
<Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="assssssssssssssssssssssssssssssssssssssssss" android:background="@drawable/nine" />
</RelativeLayout>
|
注意:有可能会出Error:java.lang.RuntimeException: Some file crunching failed, see logs for details
意思就是说
- 1.构建Gradle的时候,Gradle会去检查一下是否修改过文件的后缀名;
- 2.一般大多数是出现在图片上,.jpg修改成了.png就会出现这个问题;
- 3.9patch图片也可能出现这个问题。
很明显我们可能是第三种情况