Custom Components(自定义组件)

来源:互联网 发布:微商城怎么查询数据库 编辑:程序博客网 时间:2024/05/17 03:50

概述

安卓为构建你自己的UI提供了一个成熟且强大的自定义模型,基于基本的布局类:View 和 ViewGroup. 开始使用它之前,平台包含了许多预先构建好了的view和viewgroup的子类——分别叫做组件(widgets )和布局(layouts )——你可以使用它们来构建你的UI。
一部分可用组件清单包括ButtonTextViewEditTextListViewCheckBox,RadioButtonGallerySpinner和更有特殊用途的 AutoCompleteTextView,ImageSwitcher, and TextSwitcher.
可用的布局LinearLayoutFrameLayoutRelativeLayout,和其它。更多例子请查看 Common Layout Objects.
如果这之中没有你所需要的组件或布局,你可以创建你自己的view子类。如果你只需要小范围的调整一个已经存在的组件或布局,你可以简单的继承该组件或布局,并且覆盖它的方法。
创建你自己的view子类让你精确的控制屏幕的外观和功能元素。你在自定义view上给定的一些控制想法,这里有一些例子来说明你可以用他们来做什么:
  • 你可以创建一个完整的自定义显示的view类型,例如使用2D绘图显示一个“音量控制”按钮,和类似的模拟电子控制。
  • 你可以组合一组view组件成为一个新的单一组件,或许像一个ComboBox (一个组合了浮动列表和自由输入文本框的控件),一个双面选择控制器(dual-pane selector contro,左和右窗格都有一个列表,你可以重新组织哪个item在哪个列表中),等等。
  • 你可以通过覆盖的方法让一个EditText组件呈现在屏幕上( Notepad Tutorial 是该方法的一个成功案例,创建了一个线性的记事本页)。
  • 你可以捕获其它事件并且用一下自定义方法来处理他们(如游戏一样)
接下去的章节展示了如何创建自定义view 并且在你的app中使用它们。更多有用的消息,请查看View 类中的介绍。

基本方法( Basic Approach)

 这你是一个抽象的概述,在开始创建视图组件时你需要了解的东西:
为你自己的类继承一个已经存在的view类或者子类
覆盖一下父类的方法。父类可覆盖的方法以"on "开头,例如,onDraw()onMeasure(), 和onKeyDown(). 这与在Activity 或 ListActivity中的on...事件相似,你可以覆盖它的生命周期和其它的功能钩子。
使用你新继承的类。一旦完成,你新继承的类可以被用来替代基类的view 。
技巧:扩展类可以作为一个activity 中的内部类被定义并且使用它。这是很有用的,因为它控制访问权限,但是不是必须的(或许你想为你的应用创建一个公共的view)

 完全的自定义控件(Fully Customized Components)

完全自定义控件可创建你希望如何显示的生动的组件。或许一个图形音量表( graphical VU meter)看起来像一个老式的模拟仪表,或者一个一起唱歌的长文本视图(sing-a-long text view),当一个光标沿着歌词移动时你可以跟着卡拉ok机器一起唱。无论哪种方式,都是让你内置组件做他不会做的事,不论你是怎么组合他们的。
幸运的是,你可以很容易的以任何方式创建一个你喜欢的外观和行为的组件。限制它的或许仅仅只有你的想象力,屏幕的大小和可用的处理能力(记住,你的应用最终或许运行在一些低性能的平台上)
为创建一个完全的自定义控件:
  • 你可以继承的最平常的类,view,这样你通常会以继承该类来创建你的新的上级组件开始。
  • 你可以提供一个构造器,该构造器可以从xml文件中取得属性和参数,并且你可以使用你自定义的属性和参数(也许是声量器的颜色和范围,或者是宽度和针的电阻,等等)
  • 你或许想要在你的自定义类中创建你自己的事件监听者,属性访问器和修改器,和更多成熟的行为。
  • 如果你想要这个组件显示一些什么东西,你几乎肯定想覆盖onMeasure() onDraw()。 当这些都没有被覆盖时,默认的onDraw() 将不什么都不做,默认的onMeasure() 将大小设置为100*100——这可能不是你想要的。
  • 其它on...方法可以在需要时被覆盖

Extend onDraw() and onMeasure()

onDraw() method 方法传递了你的Canvas(画布),该画布可以实现任何你想要的:2D绘图,其它标准或者自定义控件,字体样式,或者任何你能想到的东西。
:它不支持3D绘图,如果你想使用3Dh绘图,你必须继承SurfaceView来代替view,并且使用单独的线程来进行绘制操作。查看GLSurfaceViewActivity 示例获取更多信息
onMeasure() 稍微复杂一些。onMeasure() 是你的组件呈现在容器上的关键。onMeasure() 应该被覆盖用来提高效率并且提供准确的测量值。这是一个来自它父视图的稍微复杂的需求限制(该父视图通过onMeasure()方法传递了过来)并且 一旦计算完成 便会根据需求调用 含有测量的宽度和高度的setMeasuredDimension() 方法。如果你不在onMeasure() 方法中调用该方法,结果将会在运行时抛出错误(exception)。
概括的说,实现onMeasure() 方法会看起来像这样:
  1. 覆盖的onMeasure() 方法被调用时需传递宽度和高度两个测量规范(widthMeasureSpec 和heightMeasureSpec 参数,两个都是int 类型来表示尺寸 dp)它应该被当作限制你测量的宽度和高度的要求来对待。一个完整的参考这些规范需要的限制能够在View.onMeasure(int, int)引用文档中找到(该文档很好的说明了整个测量操作流程)
  2. 你组件的onMeasure() method 方法应该计算测量的宽度和高度值,这两个值将会被要求给予该组件。它应该试图保持在传入的规范之中,尽管可以选择超过规范(在该案例中,父视图可以选择这样做,包括裁剪,卷动,抛出一个错误或者请求再次调用 onMeasure(),或许有不同的测量规范 )。
  3. 一旦宽度和高度被计算出来,setMeasuredDimension(int width, int height) 方法一定会被调用,并且传递计算出来的测量值。如果不这么做将会抛出一个错误。
这里总结了一些框架在view 中调用的一些标准方法:
类别方法描述Creation构造函数第一个构造函数在view创建时会被调用。第二个构造函数试图解析并应用任何在布局文件中定义的属性(attributes )onFinishInflate()Called after a view and all of its children has been inflated from XML.LayoutonMeasure(int, int)在确定view及其所有子节点的大小时 调用onLayout(boolean, int, int, int, int)当view需要为其所用的子节点分配位置和大小时 调用onSizeChanged(int, int, int, int)当view的大小改变时 调用DrawingonDraw(android.graphics.Canvas)当view需要绘制其内容时 调用Event processingonKeyDown(int, KeyEvent)当新的按键按下事件发生时 调用onKeyUp(int, KeyEvent)当按键弹起事件发生时 调用onTrackballEvent(MotionEvent)当轨迹球移动事件发生 调用onTouchEvent(MotionEvent)当屏幕点击事件发生 调用FocusonFocusChanged(boolean, int, android.graphics.Rect)当view获得或失去焦点时 调用onWindowFocusChanged(boolean)当包含view的window获得或失去焦点时 调用AttachingonAttachedToWindow()当view附加到window上时 调用onDetachedFromWindow()当view从window上分离时 调用onWindowVisibilityChanged(int)当包含view的window的可见性改变时 调用

一个自定义view的例子

 API Demos 中提供了一个自定义的视图。该视图定义为一个LabelView 类。
LabelView 示例作为一个自定义组件展示了一些不同的方面:
  • 继承自view类
  • 参数化构造器可以拿到view展开时的参数(定义在xml文件中的参数)。一些参数v传递了进来并且给了父view ,但是更重要的是,这里有一些自定义的属性并且在labelview中使用了。
  • 你希望看到一个标签组件的标准公共方法类型,例如setText()setTextSize()setTextColor()之类的。
  • 一个被覆盖的onMeasure 方法用来决定和设置呈现出来的组件大小。(注意,在该例子中,真正工作的是私有的measureWidth()方法
  • 一个被覆盖的onDraw() 方法 来在提供的画布上绘制标签
你可以看到一些使用示例,在labelview 的custom_view_1.xml 中。另外,你可以看到一个混合使用了android: 命名空间参数和自定义的app: 命名空间参数。这些app: 参数是自定义的仅被LableView 识别和起作用的,它在样式(styleable )内部类 中被定义,该内部类 在R.resources 中定义。

复合组件(Compound Controls )

如果你不希望创建一个完全的自定义控件,而是希望放在一起。一个 由一组现有的控件组成一个可用组件,然后创建一个混合组件(Compound Component or Compound Control)或许适合你的要求。在一个极小容器中,汇集了更多的原始控件(或者视图)到一个逻辑上的项目组(group of items),该组可作为一个单一事物来对待。例如,一个组合框(Combo Box)可以被看作是一个简单的线性Edittext和一个相邻的按钮贴在一个弹出列表(PopupList)上。如果你按下按钮并且从列表中选择一些东西,它填充到Edittext 字段中,但是用户仍然可以直接在Edittext中输入东西。
在安卓中,实际上有两个view可以实现这一事情:Spinner 和AutoCompleteTextView, 但是不管怎样,组合框的概念是一个易于理解的示例。
为创建一个组合控件:
  1. 通过从某种类型的布局(Layout)开始,这样,继承一个layout创建一个类。或许在组合框的案例中我们或许使用了水平的线性布局。记住,其它布局可以被嵌套在里面,因此该组合控件可以任意的组合和构造。就像用activity一样,你可以使用声明(基于xml)来创建组件,或者在代码中嵌套它。
  2. 在新类的构造器中,获取父类期望的任何参数,并且首先通过父类的构造器将其传递过去。然后你可以在你新的构件中建立其它视图;这就是你创建edittext和popuplist 的地方。注意,你或许需要引入你自己的属性和参数到xml中,这些属性和参数可以在别处使用 并且被你的构造器使用。
  3. 你也可以为你的view可能发生的事件创建监听者,例如,为一个列表项点击(List Item Click )创建一个监听者来更新edittext 的内容。
  4. 你或许需要创建你自己的属性的存取器和修改器 ,例如,允许edittext 值可以被设置初始值并且在需要时查询内容。
  5. 继承一个layout 时,你不需要覆盖onDraw() 和onMeasure()方法,因为布局会有默认的行为或许会工作得很好。然而,你仍然可以在需要的时候覆盖他们
  6. 你或许会覆盖其它on...方法,例如onKeyDown(),或许时从该组合框的popup list 中选择一个确定的值,当一个确切的按钮被按下时
综上所述,使用layout 作为组合控件的基础有如下优点:
  • 你可以在声明性的xml文件中使用它,就像使用一个activity屏幕一样,或者用代码创建一个视图并且将其嵌套进布局中。
  • onDraw() 和 onMeasure() 方法(加上其他的on...方法)将会有适当的行为,因此你不需要覆盖他们。
  • 最后,你可以十分快速的构建组合控件视图,并且作为一个单一控件重用他们

组合控件实例

在API Demo 工程中,有两个列表示例——在Views/Lists之下的Example 4 and Example 6 展示了一个SpeechView ,它继承自 LinearLayout来组合展示了Speech 引用。在示例代码中的对应代码是List4.java 和List6.java.

更改一个已经存在的视图类型

这是一个创建自定义布局的更为简单的选择,如果有一个与你想要的组件十分相似的组件,你可以直接继承它并且覆盖你想要改变的行为。在完全的自定义控件中,你可以做你任何想做的事情,但是通过直接继承一个指定类开始的方法中,你可以获取到很多行为但或许都不是你想要的。
例如,SDK中的NotePad application 案例。这展示了许多使用安卓平台的许多方面,其中的一个是继承一个Edittext 视图来构造一个线性记事本。这并非一个完美的示例,在早期的api上运行可能会看起来有所不同,但它确实展示了原理。
如果你还没有这么做,将这个按钮添加到Eclipse (或者直接使用记事本查看源码)。尤其要注意 NoteEditor.java 文件中的MyEditText定义。
需要注意的一些点
1.定义
    该类是这么定义的:
    public static class MyEditText extends EditText
  •   它作为NoteEditor activity 的内部类被定义的,但是它是public 这样可以使用NoteEditor.MyEditTextNoteEditor类的外部被访问到。
  •     它是static 意味着它不会产生所谓的“合成方法”来允许它从父类中存取数据,这也叫意味着它是作为一个独立的类而不是依赖于NoteEditor这是一个简洁的方法来创建内部类,如果它不需要从外部类中获取数据。使生成的类更小,并且允许在其它类中更易使用。
  •     它继承自EditText,这是在该案例中我们选择的来进行自定义的view.当我们完成时,这个新的类将可作为传统EditText,视图的替代品。
2. 类的初始化
    同往常一样,首先调用父类。此外,这不是默认的构造函数,但是一个参数化的。该Edittext 使用这些参数被创建,这些参数来自于xml布局文件,至此,我们的构造器需要获取并且将它们传递给父类的构造器。
3.覆盖方法
    在该案例中,只有一个方法被覆盖了:onDraw()—but  但这样很容易被误认为是创建一个你自己的自定义控件。
    对于NotePad 案例,覆盖onDraw() 方法允许我们在Editext 视图画布上绘制蓝色的线条(该画布通过onDraw() 方法传递了过来)。super.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文件中被创建,并且它的class 使用了具体的全部包名。注意我们定义的内部类是使用如下形式引用的NoteEditor$MyEditText ,该标记是在java语言中引用内部类的标准方法。
如果你的自定义视图没有作为内部类定义,你可以选择如下两项,在xml元素名中声明该控件的名称,或者包含class 属性在该属性中指明:
<com.android.notepad.MyEditText
id="@+id/note"
... />
注意,现在的MyEditText 是一个独立的类。当该类嵌套在NoteEditor 类中时,这样做是无效的。
    其它的属性和参数都会传递到该自定义组件的构造函数中,之后传递给EditText的构造器,因此它和你使用EditText视图时有着相似的参数。注意,可以添加你自己的参数,并且将会在下面提及。
这就是所有的关于如何自定义控件的方法。不可否认这是一个简单的情况,但重点是——创建自定义组件只需要这么复杂。
一个更加成熟的组件或许会覆盖更多的on...事件并且引入一些自己的帮助类,大体上,自定义它的属性和行为。唯一限制你的就是你的想象力和你期望该组件要做什么。
0 0
原创粉丝点击