Custom Components 翻译

基于基本布局类:View和ViewGroup,android提供了一个先进而又强大的组件模型,方便你构建UI。首先,该平台预制了很多View 和ViewGroup的子类——就是那些widget和Layout,你可以用这些预制的子类构建你的UI。

可用widgets比如有ButtonTextView,EditTextListViewCheckBoxRadioButtonGallerySpinner, 具有特定用途的有AutoCompleteTextViewImageSwitcher, 和TextSwitcher.

可用的布局有LinearLayoutFrameLayoutRelativeLayout等等,Common Layout Objects章节有更多的示例。

如果这些预制的widget或Layout类都不能满足你的需求,你可以创建自己的View子类。如果你仅仅需要对已有的wigget或者Layout做些微调,那么你简单的继承这个widget或者布局类并覆写它的方法就可以了。

创建自己的View子类可以让你对屏幕中元素的外观和功能进行精确的控制。你可以参照下面的示例,这些示例对你自定义View有一些建议:

  • 你可以创建一个完全自定义绘制的View,例如用2D渲染一个音量控制按钮,绘制出外观像模拟电子控制的按钮。
  • 你可以把多个View组合为一个全新的单个View,比如做出一个像组合框的View(一个弹出列表和文字输入框的结合体),或者是一个双重选择控制器(左右两边都有一个列表,你可以重新分配列表中的item在哪个列表中),等等……
  • 你可以覆写EditText组件在屏幕上绘制的方法(Notepad Tutorial教程就是用这种方法创建了一个有条纹效果的页面)。
  • 你可以捕获更多的事件,例如key presses,并用你自定义的方法来处理这个事件(比如在游戏设计中)。

下面的部分是展开介绍如何创建一个自定义View,如何用在你的程序用使用这些自定义View。参看View类以获取更多的详细信息。

The Basic Approach

下面是对如何创建自己的View组件的一个简要概括:

  1. 创建自己的类,让它继承一个既有的View类或其子类
  2. 覆写父类的某些方法。被覆写的方法都以“on”开头,例如,onDraw()onMeasure(), 和onKeyDown().这和你处理生命周期时覆写Activity或者ListAtivity的onXXX方法或者其它回调方法一样。
  3. 使用这新的扩展类。这些完成后,你这个新的类就可以用在其父类出现的任何地方。

Tip:子类可以作为内部类定义在使用它的Activity类里面。这有利于控制外部对它们的访问,但是并不是一定要这么干(或许你想创建一个public View,可以再你的APP中广泛使用它)。

Fully Customized Components

完全自定义控件可以创建出你想要的图形。或许是一个像老式模拟仪表的音量显示器,或者是一个伴唱字幕,上面有一个跟随字幕文字移动的球便于你通过卡拉OK机伴唱。也就是说无论你怎么组合内置的那些控件都无法满足你的要求。

还好,你可以很容易地创建出你想要的控件,你可以控制它的外观和行为,这只受限于你的思维、屏幕的尺寸、可用电量(要知道,你的程序最终要在电量比桌面电脑少的多的设备上运行)。

要完全创建一个自定义控件:

  1. 毫无疑问,你最可能继承的类就是View,所以通常需要从继承这个类来开始创建你那牛B的控件。
  2. 你要提供一个能从XML文件获取属性和参数的构造函数,并且你也要使用这些属性和参数(或许是颜色和音量仪表所占的空间,或者是指针的宽度和振幅,等等……)
  3. 也许你还要创建自己的时间监听器,属性访问器和修改器,或许你的控件中还有更多复杂的操作。
  4. 你很可能要去覆写onMeasure(),如果想让你的控件显示一些东西,同样要覆写onDraw() 。虽然这两个方法都有缺省操作,但是缺省onDraw什么都不干,缺省onMeasure()通常设置View的Size为你不想要的100×100大小。
  5. 如果需要,你还要覆写其它onXXX()方法。

Extend onDraw() and onMeasure()

这个onDraw()方法传进一个Canvas参数,你可以在它上面实现任何你想要的2D图形,还有标准的或者自定义的控件(译注:这里指标准图形:直线、点圆……和自定义图形:利用标准图形或者图片绘制自己想要的图形),有样式的文本,或者你能想的到的任何东西。

注意:这并不适用于3D图形绘制。你如果你想绘制3D图形,你必须继承SurfaceView类而不是View类,而且在一个单独的线程中进行绘制动作。详情参看GLSurfaceViewActivity示例。

onMeasure()方法用的多一点。onMeasure()方法是子控件与其父控件之间绘制规则的关键环节。应该覆写onMeasure()以高效、精确的报告它所占用的尺寸。这有点小复杂,因为父控件会对子控件有一个限制(作为onMeasure的参数传递进来的),并且一旦计算好measured width和measured height后,必须调用setMeasuredDimension()方法,如果你在覆写onMeasure()方法的时候没有调用这个方法,就会在测量过程中抛出一个异常。

简而言之,执行onMeasure()的过程如下:

  1. 调用被覆写的onMeasure()方法,并用宽度和高度的测量限制条件(就是widthMeasureSpec和 heightMeasureSpec这两个参数,它们都是代表测量尺寸的限制条件的整形编码)作为参数传入,它们都是对你计算measuredWidth和measuredHeight的限制条件,可用限制条件种类的完整说明在View.onMeasure(int,int)的说明文档中(该文档还对整个测量过程做出了非常好的描述)。
  2. 你的控件的onMeasure()方法要预测出一个测量宽度和测量高度,这两个尺寸将用于绘制这个控件。尽管它可以选择无视测量限制条件(在这种情况下,父控件有权选择如何做,包括裁剪,滚动,抛出异常或者再次调用该onMeasure()方法),但是测量值最好符合传入的限制条件。
  3. 一旦计算出宽和高,必须调用setMeasuredDimension(int width,int height),并传入测量结果。否则将会抛出异常。

下面是View中一些标准方法的摘要,这些方法都会被系统框架调用:

种类 方法 描述
构造 构造函数

一种构造函数是在从代码里创建View时被调用,另一种是从一个布局文件中inflate视图时被调用。第二种应该解析并运用任何定义在布局文件中的属性。

onFinishInflate() 一个View及其子View被inflated后,就调用此方法。
布局 onMeasure(int, int) 调用该方法来计算该View及其子View的尺寸需求。
onLayout(boolean, int, int, int, int) 当该View给它的所有子View分配大小和位置时调用该方法。
onSizeChanged(int, int, int, int) 当该View的大小发生改变后调用该方法。
绘制 onDraw(Canvas) 当该View绘制其内容时调用该方法。
事件处理 onKeyDown(int, KeyEvent) 当有新的按键事件时调用该方法。
onKeyUp(int, KeyEvent) 当有按键释放事件时调用该方法。
onTrackballEvent(MotionEvent) 当有轨迹球事件时调用该方法。
onTouchEvent(MotionEvent) 当有触屏事件时调用该方法。
焦点 onFocusChanged(boolean, int, Rect) 当该View获得或失去焦点是调用该方法。
onWindowFocusChanged(boolean)

当包含该View的window获得或是去焦点是调用该方法 。

注意:这和View的焦点是分开的:要能接收按键事件,View和它的window必须都是已经获得焦点的。如果有一个window显示在你的window之上那么它就获取了焦点,而你的window就失去了焦点,但是(你的window中的)View的焦点还是没有变。

Attaching onAttachedToWindow() 当该view被附加到一个window上时调用该方法。
onDetachedFromWindow() 当该View从window分离时调用该方法。
onWindowVisibilityChanged(int) 当包含该View的window显示或者隐藏时调用该方法。

 

A Custom View Example

API Demos中的 CustomView 示例提供了一个自定义控件的例子。自定义的控件创建在 LabelVie 类中。

LabelView 简单演示了自定义控件的方方面面:

  • 为一个完全自定义控件扩展View类。
  • 采用有参数的构造函数,该方法接受View的inflation参数(这些参数定义在XML中)。其中一些参数传给父类(的构造函数处理),但是重点是LabelView定义并使用了几个自定义属性。
  • 定义了一些public方法,这些方法可以让这个控件有你想要的样子,例如 setText(),setTextSize()setTextColor()等等。
  • 覆写onMeasure的方法来测量和保存这个控件的绘制大小。(注意:在LableView中,实际工作是由一个叫measureWidth()的private方法来做的。)
  • 覆写onDraw()方法来在提供的canvas上绘制label。

你可以在示例的custom_view_1.xml中看到自定义LabelView的一些用法,重点是,你可以看到android:命名控件参数和自定义的app:命名控件参数混合使用。那些app:参数是LabelView识别并使用的自定义参数,并且这些参数定义在示例程序的资源定义类R类的一个样式内部类中。

Compound Controls

如果你不想创建一个完全自定义的控件,而是想用现有的控件组装一个可重用的控件,那么做一个Compound Component(或者叫Compund Control)或许正好满足需求。在一个小容器内(nutshell),把一些更小的控件(或者说View)组成一个逻辑分组,这个逻辑分组可以作为一个整体使用。例如,一个Combo Box 可以看作是由一个单行文本编辑框和旁边的一个按钮组成,这个按钮附加了一个弹出列表。如果你点击这个按钮并从列表中做出选择,那么选择的东西就会显示在文本编辑框中,如果用于愿意,也可以直接在文本编辑框中输入。

在Android中,其实另外两个现在成View是这么做的:Spinner 和AutoCompleteTextView,但是,Combo Box的概念更容易理解。

 

创建一个组合控件:

  1. 通常开始于某种布局,所以创建一个继承Layout的类。拿Combo box来说,我们可能要用一个LinearLayout,采用水平排列。记住里面可以嵌套其它的布局,所以一个组合控件可以任意组合构造。注意,就像用Activity一样,你即可以用声明(XML-based)的方法去创建包含的控件,也可以用代码构建它们。
  2. 在新类的构造函数中,接收任何父类想要的参数,并且第一时间把它们传给父类构造函数。接下来你可以安装新控件要用的其它View,就是你要在这里创建文本编辑框和弹出列表。你可能还要在XML中描述自己的属性和参数,这些属性参数都将被提取出来为你的构造函数所用。
  3. 你还可以创建监听器,监听器可以监听你所包含View所产生的事件,例如,当对列表进行选择操作时,一个List Item Click Listener 的监听方法可以用来更新文本编辑器的内容。
  4. 你可能还要用访问器和修改器来创建自己的属性,例如,允许该控件中的文本编辑框的值可以初始化,并且可以在需要是查询其内容。
  5. 在继承了Layout的情况下,你不需要覆写onDraw()和onMeasure()方法,因为Layout的缺省行为已经做的很好了。但是如果需要你还是可以覆写它们。
  6. 你可能还要覆写其它onXXX方法,比如onKeyDown,当某些键被按下时可以取出Combo box中弹出列表的某些默认值。

总而言之,基于Layout创建自定义控件的优势包括:

  • 你可以像Activity屏幕那样用声明性的XML文件来指定布局,也可以用代码创建View,并把它们嵌入到这个布局里面。
  • onDraw()和onMeasure()方法(还有其他很多onXXX方法)很可能已经有了合适的行为,所以你不必覆写它们。
  • 最后,你可以快速、任意地把多个控件组合起来并且把它们当成单个控件那样复用。

 

Examples of Compound Controls

随SDK发布的Demos工程中,在Views/List下有两个List的示例-Example4和Example6,做了一个SpeechView的演示,该控件扩展了LinearLayout显示演讲摘要。演示代码相应的类是List4.java和List6.java。

Modifying an Existing View Type

在某些情况下有更简单更实用的方式去创建自定义控件。如果现有控件中有和你的需求非常相近的控件,你可以简单的扩展这个控件并只改变你想改变的行为就行了。通过完全自定义控件你当然可以做任何事,但是从View树种的一个特定类作为起点你同样可以做很多事,并且很可能只用做你想做的事。

例如,SDK的示例中有个NotePad application的工程。该工程演示了android平台很多方面的应用,很多都是扩展EditText View来制作一个有条纹效果的记事本。这并非完美的例子。并且所用的早期API现在可能有变,但是它演示了这种基本思路。

如果你没有这样干过,把这个NotePad示例导入Eclipse吧(或者通过这里的链接看一下源代码)。其中特别留意NoteEditor.java中MyEditText的定义。

一些要注意的要点:

1.定义

 这个类用下面一行代码定义:

 public static class MyEditText extends EditText

  • 它是做为NoteEditor Activity的一个内部类定义的,但是它是publict的,所以如果需要可以在NoteEditor的外部通过NoteEditor.MyEditText来访问。
  • 它是static的,就是说它不会产生访问外部类数据的所谓“synthetic methods”,也就意味着它是一个独立的类而不是和NoteEditor有很深的关联。如果它不需要访问外部类的状态,那么用这种方法创建内部类比较简洁,并且使内部类短小精悍,也可以被其它类放边使用。
  • 它继承自EditText,我们选择基于EditText来定制控件。完成后,新的类将代替普通的EditText View。

2.类的初始化

  通常,要先调用父类(的构造函数)。此外,这里用的不是默认构造函数而是由参数的构造函数。当通过inflat xml文件创建EditText的时候用的是有参数(的构造函数),因此我们的构造函数也需要接收这些参数并把它   们传给父类的构造函数

3.覆写方法

    在这个例子中,只覆写了一个方法:onDraw()-但是你创建自己的控件时很可能需要覆写其它的方法。

  对于NotePad示例,覆写onDraw()方法可以让我们在EditText View的Canvas(这个Canvas是通过被覆写的onDraw()方法传递进来的)绘制蓝色的条纹。父类的onDraw()是在该方法的结尾调用的。这父类的方法是应该调用,但是在这种情况下,我们是在画好蓝色条纹后调用它。

4.使用自定义控件

   现在有了自定义控件,但是我们要如何使用呢?在NotePad示例中,自定义控件时直接在描述性布局文件中使用的,所以我们看看res/layout文件夹下的note_editor.xml文件:

     

<view
  class="com.android.notepad.NoteEditor$MyEditText" 
  id="@+id/note"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="@android:drawable/empty"
  android:padding="10dip"
  android:scrollbars="vertical"
  android:fadingEdge="vertical" />

 

  • 该自定义控件以普通文件一样的方式在XML文件中创建,用完成包名来指定其对应的类。还要注意的是:用NoteEditor$MyEditText来引用我们定义的内部类,这是java编程中引用内部类的标准方法。如果你的自定义控件不是内部类,那么你就可以在xml文件中用element名称来声明控件,不需要class属性。例如:

    

<com.android.notepad.MyEditText
  id="@+id/note"
  ... />

  注意这种情况是MyEditText是一个独立的类文件。如果是嵌套类的话这种写法就不会正常工作。

  • 其它的属性和参数是用来传递给自定义控件的构造函数的,然后再传给EditText的构造函数,所以他们和你使用EditText时传递的参数一样。同样,也可以加入你自己的参数,我们下面会讲到这些。

这就是所有的内容。当然,这是比较简单的case,但是自定义控件的复杂度只和你的需求有关。

更复杂的控件需要覆写更多的onXXX方法,也需要引入它的辅助方法,大量的定制它的属性发方法。这决定于你的思维和你的需求。

    

 

posted @ 2012-09-17 00:02  zhaoke5421  阅读(213)  评论(0编辑  收藏  举报