这次的总结主要涉及到Dev Guide/User Interface中的Building Custom Components和How Android Draws Views内容和部分Graphics内容。围绕实现一个自定义圆角的ImageView控件(我将它叫做RoundedImageView)展开。
首先说明How自定义控件。
在Android中用于人机交互的组件叫做widget,比如Button,TextView之类,都直接或间接继承自View。
说到这,自定义组件的方法就明朗了,就是使用继承,具体继承什么类根据你的需求定,当然直接继承自View类的话,这个就属于完全自定义了。这里有一个快速一点入门的方法,看Android提供的widget的源码。关于Android提供的类库的源码都在platform/frameworks/base.git这个工程下(至于git的使用方法不在本文范围内,所以略去不写了)。至于自定义控件的,更为详细内容可以查看官方文档Building Custom Components。
此外,对于在xml定义圆角属性,这个我建议查看一些widget的源码,主要思想是在你的View的构造函数中通过解析AttributeSet参数获得相应的值来获取参数。还有一些命名控件的细节,可以查看API Demos里的LabelView这个Demos的代码获得细节,或者Google。
那么对于这个圆角ImageView具体问题,应该怎么做呢?
由于需求是做圆角的ImageView,所以我们自然而然的想到扩展ImageView类了。所以,首先必须先简介一下ImageView。
Displays an arbitrary image, such as an icon. The ImageView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the image so that it can be used in any layout manager, and provides various display options such as scaling and tinting.
顾名思义,就是一显示图片的控件。图片源提供了丰富的选项,可以使Bitmap,Drawable,ResouceId,Uri,Matrix。
方法的想法我想到了两个:
重写所有设定图片源的方法,将传入的图片改成圆角,在传给父类相应方法。重写onDraw()方法,当该RoundedImageView由于某些原因触发这个回调函数时,Draw一个圆角的图。
第一个方法显然过于麻烦。而且维护代价高,十分不优雅。并且需要很多类型之间相互转换,性能上也带来一定额外的开销。
第二个方法是理想中的不错的想法。但是对于我这个新手,由于涉及到很多东西,所以技术上带来了一定难度。
首先,这就涉及到How Android Draws Views。我摘录了几段话,合并起来如下。
When an Activity receives focus, it will be requested to draw its layout. You can force a View to draw, by calling invalidate(). If the view is visible, onDraw(Canvas) will be called at some point in the future.
得知三点:
当Activity获得焦点是,Activity会请求画它的布局。可以通过调用invalidate()方法强制一个View重绘。如果View可见那么invalidate()会回调onDraw(Canvas)方法。
此外,我通过查看ImageView的源码,得知4. 每一个set一种类型的图片资源的时候,最后都会调用invalidate()方法来请求重绘。比如说当你调用setBitmap()方法后,onDraw()方法就会回调。
还有,5. ImageView每一种setXX()图片的方法调用后,最终都会将图像资源它转成Drawable类型。(A Drawable is a general abstraction for "something that can be drawn. 更进一步信息可以查看Dev Guide中Graphics )
6. 并且ImageView类提供了getDrawable()获取这个Drawable对象的方法。
根据以上6点,于是,我们可以在继承自ImageView的RoundedImageView方法中先获取这个Drawable对象,然后把它圆角化,然后再通过一些方法画出来。
新问题又来了,怎么画?
一直未提到的一个事情是onDraw()回调函数会传入这个Canvas对象,Draw with a Canvas。
When you're writing an application in which you would like to perform specialized drawing and/or control the animation of graphics, you should do so by drawing through a Canvas. A Canvas works for you as a pretense, or interface, to the actual surface upon which your graphics will be drawn — it holds all of your "draw" calls. Via the Canvas, your drawing is actually performed upon an underlying Bitmap, which is placed into the window.
还是StackOverflow,有人给出了个一个pretty awesome的方法,地址在这里。(但是我说的这个方法是没有成为提问人的答案的那个方法)
题外话:似乎最近一些问题的关键解答,每次都是StackOverflow上求助解决的,悲剧(还是喜剧)。
最后,对于图形Graphics这个话题,我所知甚少,也许以后会写一个详细一点的总结吧。今天就先到这里了。
补注:按照StackOverflow上的做法可能会有性能上的多余开销,可以考虑将圆角图片cache到本类的一个私有成员上,这样不用每次重绘都进行圆角转换。但是还有一个问题,就是如果客户调用更新Bitmap或者Drawable的方法,那么如何判断Cache失效也是一个问题,暂时还没有想好。
注:本人初学,可能一些细节上会有纰漏,如果你有什么迷惑可以留言或者可以Google,谢谢!