当前位置: 首页 > 工具软件 > Custom Drop > 使用案例 >

Custom Components(自定义组件)

阎英朗
2023-12-01

概述

安卓为构建你自己的UI提供了一个成熟且强大的自定义模型,基于基本的布局类: View  和  ViewGroup . 开始使用它之前,平台包含了许多预先构建好了的view和viewgroup的子类——分别叫做组件(widgets )和布局(layouts )——你可以使用它们来构建你的UI。
一部分可用组件清单包括 Button TextView EditText ListView CheckBox , RadioButton Gallery Spinner和更有特殊用途的   AutoCompleteTextView , ImageSwitcher , and  TextSwitcher .
可用的布局 LinearLayout FrameLayout RelativeLayout ,和其它。更多例子请查看   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.
Layout onMeasure(int, int) 在确定view及其所有子节点的大小时 调用
onLayout(boolean, int, int, int, int) 当view需要为其所用的子节点分配位置和大小时 调用
onSizeChanged(int, int, int, int) 当view的大小改变时 调用
Drawing onDraw(android.graphics.Canvas) 当view需要绘制其内容时 调用
Event processing onKeyDown(int, KeyEvent) 当新的按键按下事件发生时 调用
onKeyUp(int, KeyEvent) 当按键弹起事件发生时 调用
onTrackballEvent(MotionEvent) 当轨迹球移动事件发生 调用
onTouchEvent(MotionEvent) 当屏幕点击事件发生 调用
Focus onFocusChanged(boolean, int, android.graphics.Rect) 当view获得或失去焦点时 调用
onWindowFocusChanged(boolean) 当包含view的window获得或失去焦点时 调用
Attaching onAttachedToWindow() 当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...事件并且引入一些自己的帮助类,大体上,自定义它的属性和行为。唯一限制你的就是你的想象力和你期望该组件要做什么。
 类似资料: