自定义视图组件

Android提供了一个复杂而强大的组件化模型,用于构建UI,基于基本布局类:ViewViewGroup
首先,该平台包括各种预构建的ViewViewGroup子类 - 分别称为小部件和布局 - 可用于构建UI

如果预构建的小部件或布局都不符合您的需求,您可以创建自己的View子类。如果您只需要对现有窗口小部件或布局进行小的调整,则可以简单地对窗口小部件或布局进行子类化并覆盖其方法。

创建自己的View子类可以精确控制屏幕元素的外观和功能。要了解自定义视图所获得的控件,下面是一些可以使用它们执行操作的示例:

  • 您可以创建一个完全自定义渲染的视图类型,例如使用2D图形渲染的“音量控制”旋钮,类似于模拟电子控件。

  • 您可以将一组View组件组合成一个新的单个组件,可能是为了制作类似ComboBox(弹出列表和自由输入文本字段的组合),双窗格选择器控件(左侧和右侧窗格,其中包含一个列表) 每个你可以重新分配哪个项目在哪个列表中),等等。

  • 您可以覆盖EditText组件在屏幕上呈现的方式(Notepad Tutorial使用它可以很好地创建带衬里的记事本页面)。

  • 您可以捕获其他事件,如按键,并以某种自定义方式处理它们(例如游戏)。

以下部分介绍了如何创建自定义视图并在应用程序中使用它们。 有关详细参考信息,请参阅View类。

一、基本方法

以下是开始创建自己的View组件时需要了解的内容的高级概述:

  1. 使用您自己的类扩展现有的View类或子类。

  2. 覆盖超类中的一些方法。 要覆盖的超类方法以'on'开头,例如onDraw()onMeasure()onKeyDown()。 这类似于您为生命周期和其他功能挂钩覆盖的ActivityListActivity中的on ...事件。

3、使用新的扩展类。 完成后,可以使用新的扩展类来代替它所基于的视图。

提示:扩展类可以定义为使用它们的活动内部的内部类。 这很有用,因为它控制对它们的访问但不是必需的(可能您希望创建一个新的公共视图以便在您的应用程序中更广泛地使用)。

二、完全定制的组件

完全自定义的组件可用于创建您想要的图形组件。 也许是一个图形VU表,看起来像一个旧的模拟仪表,或一个长长的文本视图,其中弹跳球沿着单词移动,所以你可以与卡拉OK机一起唱歌。 无论哪种方式,无论你如何组合它们,你都需要内置组件不会做的事情。

幸运的是,您可以轻松地以您喜欢的任何方式创建外观和行为的组件,可能仅限于您的想象力,屏幕大小和可用处理能力(请记住,最终您的应用程序可能必须运行在显着更少的东西上 功率比您的桌面工作站)。

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

  1. 您可以扩展的最通用的视图,不出所料,View,所以您通常会首先扩展它以创建新的超级组件。

  2. 您可以提供一个可以从XML获取属性和参数的构造函数,您也可以使用自己的属性和参数(可能是VU表的颜色和范围,或者针的宽度和阻尼等)。

  3. 您可能希望在组件类中创建自己的事件侦听器,属性访问器和修改器,以及可能更复杂的行为。

  4. 您几乎肯定想要覆盖onMeasure(),如果您希望组件显示某些东西,也可能需要覆盖onDraw()。 虽然两者都有默认行为,但默认的onDraw()将不执行任何操作,默认的onMeasure()将始终设置为100x100的大小 - 这可能不是您想要的。

  5. 其他on ...方法也可以根据需要覆盖。

扩展onDraw()和onMeasure()

onDraw()方法为您提供了一个Canvas,您可以在其上实现任何您想要的东西:2D图形,其他标准或自定义组件,样式文本或您能想到的任何其他内容。

注意:这不适用于3D图形。 如果要使用3D图形,则必须扩展SurfaceView而不是View,并从单独的线程中绘制。 有关详细信息,请参阅GLSurfaceViewActivity示例。

onMeasure()更多涉及。onMeasure()是组件与其容器之间的渲染合同的关键部分。 应该重写onMeasure()以高效准确地报告其包含部分的测量值。 根据父级的限制要求(传入onMeasure()方法)以及一旦计算出的测量宽度和高度调用setMeasuredDimension()方法的要求,这会稍微复杂一些。 如果您未能从重写的onMeasure()方法调用此方法,则结果将是测量时的异常。

在较高的层次上,实现onMeasure()看起来像这样:

  1. 使用宽度和高度测量规范(widthMeasureSpecheightMeasureSpec参数,都是表示尺寸的整数代码)调用重写的onMeasure()方法,应将其视为对应生成的宽度和高度测量的限制的要求。 可以在View.onMeasure(int,int)下的参考文档中找到这些规范可能需要的限制的完整参考(此参考文档也非常好地解释了整个测量操作)。

  2. 组件的onMeasure()方法应计算渲染组件所需的测量宽度和高度。 它应该尽量保持在传入的规范内,尽管它可以选择超过它们(在这种情况下,父级可以选择做什么,包括剪切,滚动,抛出异常,或者让onMeasure()再试一次, 也许有不同的测量规格)。

  3. 计算宽度和高度后,必须使用计算的测量值调用setMeasuredDimension(int width,int height)方法。 如果不这样做将导致抛出异常。

三、复合控制

如果您不想创建完全自定义的组件,而是希望将由一组现有控件组成的可重用组件放在一起,那么创建复合组件(或复合控件)可能符合要求。 简而言之,这将更多原子控件(或视图)集合到一个逻辑组项中,这些项可以作为单个事物处理。 例如,组合框可以被认为是单行EditText字段和具有附加PopupList的相邻按钮的组合。 如果按下按钮并从列表中选择一些内容,它会填充EditText字段,但如果用户愿意,用户也可以直接在EditText中输入内容。

Android中,实际上有两个其他视图可供使用:SpinnerAutoCompleteTextView,但无论如何,组合框的概念是一个易于理解的例子。

要创建复合组件:

  1. 通常的起点是某种布局,因此创建一个扩展布局的类。 也许在组合框的情况下,我们可能使用具有水平方向的LinearLayout。 请记住,其他布局可以嵌套在内部,因此复合组件可以是任意复杂和结构化的。 请注意,就像使用Activity一样,您可以使用声明式(基于XML)方法来创建包含的组件,也可以从代码中以编程方式嵌套它们。

  2. 在新类的构造函数中,获取超类所需的任何参数,并首先将它们传递给超类构造函数。 然后,您可以设置其他视图以在新组件中使用; 这是您创建EditText字段和PopupList的位置。 请注意,您还可以在XML中引入自己的属性和参数,这些属性和参数可以由构造函数提取和使用。

  3. 您还可以为包含的视图可能生成的事件创建侦听器,例如,List Item的侦听器方法单击Listener以在创建列表时更新EditText的内容。

  4. 您还可以使用访问器和修饰符创建自己的属性,例如,允许在组件中最初设置EditText值,并在需要时查询其内容。

  5. 在扩展布局的情况下,您不需要覆盖onDraw()onMeasure()方法,因为布局将具有可能正常工作的默认行为。 但是,如果需要,您仍然可以覆盖它们。

  6. 你可以覆盖其他on ...方法,比如onKeyDown(),或者在按下某个键时从组合框的弹出列表中选择某些默认值。

总而言之,使用布局作为自定义控件的基础具有许多优点,包括:

  • 您可以使用声明性XML文件指定布局,就像使用活动屏幕一样,或者您可以以编程方式创建视图并将它们嵌入到代码的布局中。

  • onDraw()onMeasure()方法(以及其他大多数on ......方法)可能具有合适的行为,因此您不必覆盖它们。

  • 最后,您可以非常快速地构造任意复杂的复合视图,并将它们重新使用,就像它们是单个组件一样。

四、修改现有视图类型

创建自定义视图有一个更简单的选项,这在某些情况下很有用。 如果某个组件已经非常类似于您想要的组件,则只需扩展该组件并覆盖您想要更改的行为即可。 您可以使用完全自定义的组件执行所有操作,但是从View层次结构中的更专业的类开始,您还可以免费获得许多可能完全符合您需要的行为。

作为示例,NotePad应用程序演示了使用Android平台的许多方面。 其中包括扩展EditText视图以制作带衬里的记事本。 这不是一个完美的例子,用于这样做的API可能会改变,但它确实证明了这些原则。

如果您还没有这样做,请将NotePad示例导入Android Studio(或者使用提供的链接查看源代码)。 特别要注意NoteEditor.java文件中LinedEditText的定义。

以下是此文件中需要注意的事项:

  1. 定义

该类定义如下:

public static class LinedEditText extends EditText
  • LinedEditText被定义为NoteEditor活动中的内部类,但它是公共的,因此如果需要,它可以从NoteEditor类的外部作为NoteEditor.LinedEditText访问。

  • 它是静态的,这意味着它不会生成允许它从父类访问数据的所谓“合成方法”,这反过来意味着它实际上表现为一个单独的类而不是与NoteEditor强烈相关的东西。 如果它们不需要从外部类访问状态,保持生成的类很小,并允许从其他类中轻松使用它,那么这是创建内部类的更简洁方法。

  • 它扩展了EditText,这是我们在这种情况下选择自定义的View。 完成后,新类将能够替换正常的EditText视图。

  1. 类初始化

与往常一样,超级首先被称为。 此外,这不是默认构造函数,而是参数化构造函数。 当EditTextXML布局文件中膨胀时,会使用这些参数创建EditText,因此,我们的构造函数需要同时接受它们并将它们传递给超类构造函数。

  1. 重写方法

此示例仅覆盖一个方法onDraw(),但您可能需要在创建自己的自定义组件时覆盖其他方法。

对于此示例,重写onDraw()方法允许我们在EditText视图画布上绘制蓝线(画布将传递到重写的onDraw()方法)。 在方法结束之前调用super.onDraw()方法。 应该调用超类方法,在这种情况下,我们在绘制了要包含的行之后最后执行它。

  1. 使用自定义组件

我们现在有自定义组件,但我们如何使用它? 在NotePad示例中,自定义组件直接从声明性布局中使用,因此请查看res / layout文件夹中的note_editor.xml

<view xmlns:android="http://schemas.android.com/apk/res/android"
    class="com.example.android.notepad.NoteEditor$LinedEditText"
    android:id="@+id/note"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:padding="5dp"
    android:scrollbars="vertical"
    android:fadingEdge="vertical"
    android:gravity="top"
    android:textSize="22sp"
    android:capitalize="sentences"
/>
  • 自定义组件在XML中创建为通用视图,并使用完整包指定类。 另请注意,我们定义的内部类是使用NoteEditor $ LinedEditText表示法引用的,这是一种在Java编程语言中引用内部类的标准方法。

如果未将自定义View组件定义为内部类,则可以使用XML元素名称声明View组件,并排除class属性。 例如:

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

请注意,LinedEditText类现在是一个单独的类文件。 当类嵌套在NoteEditor类中时,此技术将不起作用。

  • 定义中的其他属性和参数是传递给自定义组件构造函数的属性和参数,然后传递给EditText构造函数,因此它们与用于EditText视图的参数相同。 请注意,也可以添加自己的参数,我们将在下面再次讨论。

这就是它的全部。 不可否认,这是一个简单的案例,但重点是 - 创建自定义组件只是你需要的那么复杂。

更复杂的组件可能会覆盖更多on ...方法并引入一些自己的辅助方法,从根本上定制其属性和行为。 唯一的限制是你的想象力以及你需要组件做什么。

推荐阅读更多精彩内容