欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 自定义控件详解及实例代码

程序员文章站 2023-11-21 10:34:16
开发自定义控件的步骤: 1、了解view的工作原理 2、 编写继承自view的子类 3、 为自定义view类增加属性 4、 绘制控件 5、 响应用户消息...

开发自定义控件的步骤:

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): 当包含该组件的窗口的可见性发生改变时触发的方法

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!