Toolbox/custom-components

From Android中文网

Android中文网(androidcn.net) 版权申明 : creativecommons licenses
Jump to: navigation, search

目录

[编辑] 创建自定义android组件

Android提供了一套可靠的可视化组件(View components,视图组件?)集合,你可以在此基础上构建自己的应用,这个集合中包括的组件有:Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner等等,甚至还包括很多高级的、有着特殊用途的可视化组件,例如AutoCompleteTextView, ImageSwitcher 和 TextSwitcher. 各种布局管理器,如LinearLayout, FrameLayout 等等,也派生自View类结构(译注:原文中意指,各种布局管理器以及控制器也可以看作是可视化组件).

在你的应用程序中,你可以综合运用(combine,联合)这些布局和控制器,让他们展现在屏幕上. 在大多数情况下,这对于开发者来说已经足够用了,但是你应该意识到,其实你是可以通过继承来自己扩展这些Views,Layouts 甚至是高级的控制器的,而且你也可以创建自己的自定义组件. 通常需要你这样去做的原因可能是:

  • 创建一个完全定制的(经过个性化渲染的)组件,例如一个利用2D graphics绘制出的"音量调节旋钮",类似真实电器上的那种.
  • 将多个可视化组件合并为一个新的单一组件, 例如 开发一个类似ComboBox的组件(ComboBox组件就是结合了弹出试下拉列表组件 和可自由输入的文本框), 或者是一个 双重选择列表(左右各一个列表选择框,你可以将某选项分配到某个列表之中)...
  • 创建你自己的布局方式.SDK提供的布局提供了一些出色的配置项,可以帮助你很好的设计自己的应用程序,但是高级的开发人员也许会需要对现有的布局进行,或者是创建一个全新的布局.
  • 重写现有组件的展现方式或行为, 例如改变一个EditText 组件在屏幕上的渲染方式( Notepad示例就是通过这种方式提供了一个很好的效果,给文本编辑器提供了显示格线的效果)
  • 捕捉一些事件,如 按键事件,并对他们进行一些自定义的处理(如 在一些游戏中的做法)

还有很多的理由促使你去扩展已经存在的View 以达到你的目的.本章节将会介绍一些基础的知识以及一些示例,来告诉你如何去做.

[编辑] 基本步骤

以下步骤从宏观上概括了创建自定义组件所需要做的事情:

  • 1 创建一个自己的视图类: 它继承自现有的View 类,或View类的子类.
  • 2 重写(覆盖)父类里的一些方法: 需要重写的父类方法是一以"on"开头的, 例如 onDraw(), onMeasure(), onKeyDown() 等等.
  这就好像Activity或ListActivity 中的那些被你重写的、与Activity的生命周期相关的on... 事件,
  以及其他钩子函数(functionality hooks).
  • 3 使用自己扩展的类: 一旦完成, 你新扩展的、带有新功能的类就可以代替它的父类来使用.
  扩展的类可以以内联类(inner class)的方式在使用它们的Activity类里定义.
  这种做法通常是很有用处的,因为Activity可以更方便的操作你所扩展的类,当然这不是必须的.
  如果你想创建一个公共的、有着更广阔的使用场景的组件时,你完全可以不选择内联类这种方式.

[编辑] 完全自定义组件

完全自定义组件可以用来创建你期望的图形化组件. 如果你要创建一个好像传统的仪表盘一样的 VU 仪表 , 或者是一个类似卡拉OK机上的随歌曲而渐变的歌词字幕,那么不论你如何组合那些已有的内置组件,都无法得到所需要的全部功能。 幸运的是你可以轻松的用任何你喜欢的方式来自己实现这些功能特性(外观和行为 that look and behave),唯一的限制是你的想象力,屏幕的大小以及运行所需的电力(一定要记住,你的应用最终是要运行在一个电力持久里远不及桌面电脑的设备上.

创建完全自定义组件需要:

  1. 选择一个在普通不过的view作为扩展的基础,通常会选择View类 ,你继承它(对它进行扩展),是创建一个全新的“超级组件”的第一步。
  2. 你可以编写一个构造方法,用来从xml文件里取得属性和参数,你也可以在里面直接设置类的属性和参数(例如 VU仪表的颜色和范围,或者是指针的宽度和振幅等等).
  3. 你可以在自己的组件类里 创建自己的事件监听器, 创建自己的存取和修改属性的方法, 也可以实现更加复杂、高级的行为(功能).
  4. 通常你应该重写 onMeasure() 方法.如果你希望组件能够在屏幕上绘制一些东西,那么你可以选择重写onDraw()方法. 如果不重写,onDraw()方法默认的行为是什么都不做(do nothing);onMeasure()方法的默认行为是 总是将XXXX的大小设置为100x100 —— 这大概并不是你想要的.
  5. 根据需要,你也可以重写其他的 on... 方法.


[编辑] onDraw() 和 onMeasure() 方法

onDraw()提供了一个 Canvas 对象(画布),在Canvas对象上面你可以"绘制" 2D图形、标准组件、自定义组件、富文本(styled text,有样式信息的文本)等等任何你能想到的。如果你想绘制3D图形,你的View需要继承GLView类,而不是View类,除此之外没有什么不同之处。


onMeasure()方法稍微有点复杂. onMeasure()方法是 the rendering contract between your component and its container 的关键部分. 通过重写onMeasure() 方法可以 to efficiently and accurately report the measurement of its contained parts. This is made slightly more complex by the requirements of limits from the parent (which are passed in to the onMeasure() method) and 一单计算好the measured width and height,要调用setMeasuredDimension() 来设置组件的尺寸,这个过程一定要在onMeasure()方法内来完成. 如果在onMeasure()方法中没有调用setMeasuredDimension()方法,那么系统在measurement时将会抛出异常.

实现onMeasure()方法大致上可以这样做:

  1. The overridden onMeasure() method is called with width and height measure specifications (参数widthMeasureSpec 和 heighMeasureSpec 都是整数型,用来设定组件的尺寸) which should be treated as requirements for the restrictions on the width and height measurements you should produce. 如果你需要一个完整的关于the kind of restrictions these specifications的说明请查看参考文档(指API Doc)的 View.onMeasure(int, int) 章节(这份参考对所有measurement 相关的内容阐述的更好,建议阅读).
  2. Your component's onMeasure() method should calculate a measurement width and height which will be required to render the component. It should try to stay within the specifications passed in, although it can choose to exceed them (in this case, the parent can choose what to do, including clipping, scrolling, throwing an exception or asking the onMeasure() to try again, perhaps with different measurement specifications).
  3. Once the width and height are calculated, the setMeasuredDimension(int width, int height) method must be called with the calculated measurements. Failure to do this will result in an exception being thrown.

[编辑] 一个自定义组件的示例

API Demos中的CustomView示例 提供了一个自定义组件的例子. 它的自定义组件在LabelView类中定义. LabelView示例从不同角度展示了如何自定义组件:

  • 继承View class来实现完全自定义组件.
  • 通过参数化的构造方法(Parameterized constructor) 从xml文件内取得参数(the view inflation parameters).Some of these are passed through to the View superclass, but more importantly, there are some custom attributes defined and used for LabelView.
  • 提供了label组件所需要的标准的方法, 例如 setText(), setTextSize(), setTextColor 等等.
  • 利用一个重写的 onMeasure 方法来设置了组件的大小(the rendering size ), (注意 在 LabelView 类中, 通过私有的measureWidth()方法来计算宽度,这里的做法很巧妙)
  • 重写了onDraw()方法,用来在画布(canvas)上绘制标签(label).

从那些例子中你可以看到LabelView是如何在custom_view_1.xml 中来进行自定义组件的. 尤其是你可以看到android: namespace parameters 以及custom app: namespace parameters 这些app: parameters 使得LabelView 可以识别这些自定义组件,并且可以很好的与它们合作,同时还会在示例的资源定义类R class里定义这些资源信息.

[编辑] 复合组件(复合控制器)

如果你并不想创建一个完全自定义的组件,而是希望通过组合现有的组件(控制器)拼装出一个可不用的组件,那么你可以通过创建一个复合组件(复合控制器)来达到目的.简单的说,把一些原子的组件(控制器 视图)放到一起,编成入一个逻辑的组,那么它们可以被视作一个单一组件.例如一个组合框(Combo Box)可以看作是一个单行的文本编辑组件(EditText)和一个放在旁边的可触发弹出式列表框(PopupList)按钮(button)组成.当你按下按钮,从列表框里选择一些内容,那些内容会自动填入文本编辑框内,使用者也可以直接在文本编辑框内输入内容.


在Android中, 实际上已经提供了两个组件可以完成上面所说的功能:Spinner和AutoCompleteTextView, 这里举Combo Box的例子只是为了便于大家理解.

创建一个复合组件(复合控制器)所要做的是:

  1. The usual starting point is a Layout of some kind so create a class that extends a Layout. Perhaps in the case of a Combo box we might use a LinearLayout with horizontal orientation. Remember that other layouts can be nested inside, so the compound component can be arbitrarily complex and structured. Note that just like with an Activity, you can use either the declarative (xml based) approach to creating the contained components, or you can nest them programmatically from your code.
  2. In the constructor for the new class, take whatever parameters the superclass expects, and pass them through to the superclass constructor first. Then you can set up the other views to use within your new component. This is where you would create the EditText field and the PopupList. Note that you also might introduce your own attributes and parameters into the XML that can be pulled out and used by your constructor.
  3. You can also create listeners for events that your contained views might generate, for example a listener method for the List Item Click Listener to update the contents of the EditText if a list selection is made.

You might also create your own properties with accessors and modifiers, for example allow the EditText value to be set initially in the component, and query for the contents of it when needed.

  1. In the case of extending a Layout, you don't need to override the onDraw() and onMeasure() methods since the layout will have default behavior that will likely work just fine, but you can still override them if you need to.
  2. Likewise you might override other on... methods, like onKeyDown() to perhaps choose certain default values from the popup list of a combo box when a certain key is pressed.

To summarize, the use of a Layout as the basis for a Custom Control has a number of advantages, including:

  • You can specify the layout using the declarative xml files just like with an activity screen, or you can create views programmatically and nest them into the layout from your code.
  • The onDraw() and onMeasure() methods (plus most of the other on... methods) will likely have suitable behavior so you don't have to override them.
  • In the end you can very quickly construct arbitrarily complex compound views and re-use them as if they were a single component.

[编辑] 复合控制器的示例

SDK所带的例子中,例4和例6(在Views/Lists下)演示了一个SpeechView组件,它是一个基于LinearLayout的组件,可以显示Speech quotes。对应的代码可以在List4.java和List6.java中找到。

[编辑] 配置调整现有组件

通过对现有的组件进行配置调整,也可以达到自定义组件的目的,在某些情况下是一种相对简单而有效的做法. 如果有一个现存组件已经和你需要的组件非常接近了,那么你可以直接继承它,并根据你的需求重写它的一些方法(行为). 你依然可以按照开发完全自定义组件时的方式来进行开发,但是当你基于一个有更多特性的View类来进行开发时,你将直接得到很多你需要的功能特性,而无需亲自开发.

例如, SDK包含了一个NotePad应用示例. 这个示例展示了Android平台的很多方面,包括如何扩展一个EditText View,使它成为一个带行分隔线的记事本程序. 这并不是一个完美的示例, 而且具体的API在早期的预览版里很可能会发生变化,但是它展示除了很多基本的原理.

如果你还没有做好准备, 将NotePad示例工程导入Eclipse (也可以只是看看源代码). 着重看一下NoteEditor.java文件中定义的MyEditText.

需要注意的几点:

  • 1 定义:

通过下面的代码来定义类: public static class MyEditText extends EditText

  • 它在NoteEditor activity内部定义了一个内联类(inner class) , 但他是public的,所以能够被NoteEditor类之外的类访问,访问的方式是NoteEditor.MyEditText.
  • 它是一个静态类,这意味着it does not generate the so called "synthetic methods" that allow it to access data from the parent class, which in turn means that it really behaves as a separate class rather than something strongly related to NoteEditor. This is a cleaner way to create inner classes if they do not need access to state from the outer class, 保持生成的类的小巧,同时要保证他能够被其他的类访问.
  • 它扩展了EditText, 在这一章节里,我们曾经对EditText View进行过自定义(扩展). 当我们完成时,我们新开发的类应该可以替换标准的EditText view.
  • 2 类初始化:

父类总是被最先调用,而且没有默认的构造方法,只有一个参数化的构造方法。当从一个XML的布局配置文件(XML layout file)中读取参数,利用这些参数来创建一个EditText时,使用的就是这个参数化的构造方法。and so our constructor needs to both take them, and pass them to the superclass constructor as well.

  • 3 重新方法:

在这个例子中,只有一个方法被重写onDraw() — 但实际上你可以根据需要轻松的去重写其他的方法. 在NotePad例子中, 重写onDraw()方法是为了让我们能够在EditText的画布(canvas)上画上蓝色的行分隔线 ( canvas 是你重写的那个onDraw()方法的传入参数). 在重写onDraw()方法结束之前一定调用super.onDraw(),保证父类里的onDraw()方法被执行, 在这里我们是在绘制完我们需要的行分隔线后,在onDraw()方法的最后调用的super.onDraw() .

  • 4 只用自定义组件:

我们现在拥有了自己的自定义组件,但是我们该如何使用它呢? 在NotePad示例中,自定义组件根据定义的布局信息被直接使用(used directly from the declarative layout), 所以res/layout目录下的note_editor.xml文件看起来像下面这个样子:

  <view xmlns:android="http://schemas.android.com/apk/res/android" 
     class="com.google.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文件中被定义为一个普通的view, 它的class属性为它的完整跑名. 注意,内联类的class属性的写法可以参照NoteEditor$MyEditText的写法, 这是内联类在JAVA语言里的标准表示方法.
  • 定义中的其他属性和参数同样要传入自定义组件的构造方法中, 然后传给EditText构造方法, 所以当你使用EditText 组件时你可以使用相同的参数. 注意,在这里你也可以传递你自己的参数,这些将在后面讲到.


这就是你所要做的全部. 当然这只是一个简单的例子,但却是很很有代表性 — 创建自定义组件的复杂度取决于你的需要.

一个出色的自定义组件通常需要重写很多on... 方法,并且使用一些它们的辅助方法、属性、行为 — 这些没有什么是你做不到 - 唯一的限制就是你的想象力 和 你对组件的需求(你要做的组件的功能).

[编辑] 进阶 (原文 Go Forth and Componentize,发布与整合?这个标题我实在不知道该怎么翻译了,HELP)

如你所见,Android提供了一套成熟而又强大的组件模型,几乎可以完成任何你想完成的工作: 从简单的配置调整现有可视化组件,到利用布局管理器来整合多个控制器,再到创建完全自定义的组件.

综合运用这些元素,你将会为你的应用程序创造出你所需要的界面. 如果要正确的做到这些,需要我们不断的学习,而在这方面最好的建议就是: 关于你想要创造的的东西,一定要在脑中有一个清晰的想法(思路),然后不屈不弃 -- 不停的努力,直到你得到了你期望中的所需的组件(用户界面 用户接口....)

Personal tools