在android开发中,有很多时候使用系统自带的控件很难达到我们想要呈现给用户的效果,举个例子来说,我们知道,如果要将图片填充在整个ImageView中,可以使用fixXY,centercrop和fitcenter三个属性,其中fitXY可能将图片进行缩放或者拉伸,会使图片失真,而centercrop和fitcenter则可能会进行裁剪,都不是我们想要得到的效果,我们需要的是,将图片完整呈现在ImageView中,即可以按照其图片原有比例进行缩放或者拉伸,此时,我们该如何去实现呢?
那么,自定义一个控件就是必须的方法了,作为一名android开发者,自定义控件是一项必须掌握的技能,所以说,这很重要。
开发自定义控件的步骤:
1、了解View的工作原理
2、 编写继承自View的子类
3、 为自定义View类增加属性
4、 绘制控件
5、 响应用户消息
6 、自定义回调函数
首先,我们必须了解的是android中View的工作原理,因为android所有控件都是继承于View的,View的继承模式,很像是一棵树,View就是根,Android系统的视图结构的设计采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。
在View中定义了绘图的基本操作,View就相当于一块画板,你需要做的就是绘图:
绘图的步骤就是,首先你得对要绘制物体有个精确的了解,它的长宽是多少,所以就有了measure方法;其次,你得决定将这个物体画在画板的什么位置,layout方法由此诞生;
最后你就要开始绘制物体了,那么就有了draw方法,
基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下:
1、measure操作
measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用下面的函数:
(1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。
2、layout操作
layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;
(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
3、draw操作
draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:
(1)绘制背景;
(2)如果要视图显示渐变框,这里会做一些准备工作;
(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
(6)绘制滚动条;
从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。
那么怎样去实现自定义控件呢?
创建自定义控件的3种主要实现方式:
1)继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
2)通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。
注意此时不用onDraw方法,在构造广告中通过inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。
3)通过继承view类来实现自定义控件,使用GDI绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。
同时,在自定义控件中,你可能需要自定义一些属性,
1) 添加自定义属性<declare-styleable>到xml文件中
2) 在xml的<declare-styleable>中,指定属性的值
- <resources>
- <declare-styleable name="PieChart">
- <attr name="showText" format="boolean" />
- <attr name="labelPosition" format="enum">
- <enum name="left" value="0"/>
- <enum name="right" value="1"/>
- </attr>
- </declare-styleable>
- </resources>
3) 在view中获取xml中的值
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
- <com.example.customviews.charting.PieChart
- custom:showText="true"
- custom:labelPosition="left" />
- </LinearLayout>
这里需要注意:
要和下面的控件的定义中的字符要保持一致。
可以看到和标准的Android的组件一样,唯一的差别在他们属于不同的命名空间,标准的组件的命名空间一般是http://schemas.android.com/apk/res/android,
而我们自定义的命名空间是http://schemas.android.com/apk/res/[your package name]。注意到xmlns:custom中的custom了吗?你可以使用任意的字符,但是要和下面的控件的定义中的字符要保持一致。
4) 将获取的值应用到view中
- public PieChart(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.getTheme().obtainStyledAttributes(
- attrs,
- R.styleable.PieChart,
- 0, 0);
- try {
- mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
- mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
- } finally {
- a.recycle();
- }
- }