Android 自定义控件详解及实例代码
开发自定义控件的步骤:
1、了解view的工作原理
2、 编写继承自view的子类
3、 为自定义view类增加属性
4、 绘制控件
5、 响应用户消息
6 、自定义回调函数
一、view结构原理
android系统的视图结构的设计也采用了组合模式,即view作为所有图形的基类,viewgroup对view继承扩展为视图容器类。
view定义了绘图的基本操作
基本操作由三个函数完成: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()两个方法。
二、view类的构造方法
创建自定义控件的3种主要实现方式:
1)继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
2)通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。
注意此时不用ondraw方法,在构造广告中通过inflater加载自定义控件的布局文件,再addview(view),自定义控件的图形界面就加载进来了。
3)通过继承view类来实现自定义控件,使用gdi绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。
三、自定义view增加属性的两种方法:
1)在view类中定义。通过构造函数中引入的attributeset 去查找xml布局的属性名称,然后找到它对应引用的资源id去找值。
案例:实现一个带文字的图片(图片、文字是ondraw方法重绘实现)
public class myview extends view { private string mtext; private int msrc; public myview(context context) { super(context); } public myview(context context, attributeset attrs) { super(context, attrs); int resourceid = 0; int textid = attrs.getattributeresourcevalue(null, "text",0); int srcid = attrs.getattributeresourcevalue(null, "src", 0); mtext = context.getresources().gettext(textid).tostring(); msrc = srcid; } @override protected void ondraw(canvas canvas) { paint paint = new paint(); paint.setcolor(color.red); inputstream is = getresources().openrawresource(msrc); bitmap mbitmap = bitmapfactory.decodestream(is); int bh = mbitmap.getheight(); int bw = mbitmap.getwidth(); canvas.drawbitmap(mbitmap, 0,0, paint); //canvas.drawcircle(40, 90, 15, paint); canvas.drawtext(mtext, bw/2, 30, paint); } }
布局文件:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.myimageview2.myview android:id="@+id/myview1" android:layout_width="wrap_content" android:layout_height="wrap_content" text="@string/hello_world" src="@drawable/xh"/> </linearlayout>
属性text, src在自定义view类的构造方法中读取。
2)通过xml为view注册属性。与android提供的标准属性写法一样。
案例: 实现一个带文字说明的imageview (imageview+textview组合,文字说明,可在布局文件中设置位置)
public class myimageview extends linearlayout { public myimageview(context context) { super(context); } public myimageview(context context, attributeset attrs) { super(context, attrs); int resourceid = -1; typedarray typedarray = context.obtainstyledattributes(attrs, r.styleable.myimageview); imageview iv = new imageview(context); textview tv = new textview(context); int n = typedarray.getindexcount(); for (int i = 0; i < n; i++) { int attr = typedarray.getindex(i); switch (attr) { case r.styleable.myimageview_oriental: resourceid = typedarray.getint( r.styleable.myimageview_oriental, 0); this.setorientation(resourceid == 1 ? linearlayout.horizontal : linearlayout.vertical); break; case r.styleable.myimageview_text: resourceid = typedarray.getresourceid( r.styleable.myimageview_text, 0); tv.settext(resourceid > 0 ? typedarray.getresources().gettext( resourceid) : typedarray .getstring(r.styleable.myimageview_text)); break; case r.styleable.myimageview_src: resourceid = typedarray.getresourceid( r.styleable.myimageview_src, 0); iv.setimageresource(resourceid > 0 ?resourceid:r.drawable.ic_launcher); break; } } addview(iv); addview(tv); typedarray.recycle(); } }
attrs.xml进行属性声明, 文件放在values目录下
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="myimageview"> <attr name="text" format="reference|string"></attr> <attr name="oriental" > <enum name="horizontal" value="1"></enum> <enum name="vertical" value="0"></enum> </attr> <attr name="src" format="reference|integer"></attr> </declare-styleable> </resources>
mainactivity的布局文件:先定义命名空间 xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2" (com.example.myimageview2为你
在manifest中定义的包名)
然后可以像使用系统的属性一样使用:uview:oriental="vertical"
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <com.example.myimageview2.myimageview android:id="@+id/myimageview1" android:layout_width="wrap_content" android:layout_height="wrap_content" uview:text="这是一个图片说明" uview:src="@drawable/tw" uview:oriental="vertical"> </com.example.myimageview2.myimageview> </linearlayout>
四、控件绘制 ondraw()
五、:自定义view的方法
onfinishinflate() 回调方法,当应用从xml加载该组件并用它构建界面之后调用的方法 onmeasure() 检测view组件及其子组件的大小 onlayout() 当该组件需要分配其子组件的位置、大小时 onsizechange() 当该组件的大小被改变时 ondraw() 当组件将要绘制它的内容时 onkeydown 当按下某个键盘时 onkeyup 当松开某个键盘时 ontrackballevent 当发生轨迹球事件时 ontouchevent 当发生触屏事件时 onwindowfocuschanged(boolean) 当该组件得到、失去焦点时 onatrrachedtowindow() 当把该组件放入到某个窗口时 ondetachedfromwindow() 当把该组件从某个窗口上分离时触发的方法 onwindowvisibilitychanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
上一篇: 记录Python脚本的运行日志的方法
下一篇: Axure焦点元件怎么制作?