Android 提供了一个复杂且强大的组件化模型,可帮助您根据基本布局类 View 和 ViewGroup 构建界面。首先,该平台包含各种预构建的 View 和 ViewGroup 子类,分别称为微件和布局,可供用来构建界面。
- 可用的部分微件: Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner,以及具有特殊用途的 AutoCompleteTextView、ImageSwitcher 和 TextSwitcher。
- 可用布局: LinearLayout、FrameLayout、RelativeLayout 等
而自定义视图组件又分为:
#1. 完全自定义的组件
完全自定义的组件可用于创建外观完全如您所需的图形组件。要创建完全自定义的组件,请执行以下操作:
- 扩展的最通用的视图是 View,因此您通常需要先扩展此视图,以创建新的父组件。如下:
public class CustomView extends View
- 您可以提供一个构造函数(从 XML 获取属性和参数),也可以使用您自己的此类属性和参数(可能是声量计的颜色和范围,也可能是指针的宽度和阻尼等)。如下:
<declare-styleable name="CustomViewStyle"> <attr name="inner_color" format="color" /> <attr name="inner_background" format="reference" /> </declare-styleable> // 在代码中调用: init(){ val a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewStyle, 0, 0) val innerColor = a.getColor(R.styleable.CustomViewStyle_inner_color, 0) val layoutBackground = a.getResourceId(R.styleable.CustomViewStyle_inner_background, R.color.transparent) a.recycle() }
- 您可能需要创建自己的事件监听器、属性存取器和修饰符,以及在组件类中创建可能更为复杂的行为。
- 您几乎肯定需要替换 onMeasure();如果您希望组件显示某些内容,也可能需要替换 onDraw()。虽然两者都具有默认行为,但默认的 onDraw() 不会执行任何操作,而默认的 onMeasure() 始终会设置 100x100 的大小,这可能不是您所希望的。也可根据需要替换其他 on... 方法。如下:
// View 布局 onMeasure(int, int) 调用以确定此视图及其所有子级的大小要求。 onLayout(boolean, int, int, int, int) 在此视图应为其所有子级分配大小和位置时调用。 onSizeChanged(int, int, int, int) 在此视图的大小发生变化时调用。 // View 绘制 onDraw(Canvas) 在视图应渲染其内容时调用。 // View 事件处理 onKeyDown(int, KeyEvent) 在发生新的按键事件时调用。 onKeyUp(int, KeyEvent) 在发生 key up 事件时调用。 onTrackballEvent(MotionEvent) 在发生轨迹球动作事件时调用。 onTouchEvent(MotionEvent) 在发生触屏动作事件时调用。 // 焦点 onFocusChanged(boolean, int, Rect) 在视图获得或失去焦点时调用。 onWindowFocusChanged(boolean) 在包含视图的窗口获得或失去焦点时调用。 // 附加 onAttachedToWindow() 在视图附加到窗口时调用。 onDetachedFromWindow() 在视图与其窗口分离时调用。 onWindowVisibilityChanged(int) 在包含视图的窗口的可见性发生变化时调用。
示例:【CustomView】扫码中的扫描框(ViewfinderView)-简单实现
#2. 复合控件
整合包含一组现有控件的可再用组件,属于复合组件(或复合控件)。简而言之,这会将许多更原子的控件(或视图)整合到可被视为一件事的项的逻辑分组中。
如要创建复合组件,请执行以下操作:
- 通常从某种类型的 Layout 入手,因此请创建可扩展 Layout 的类。对于组合框,我们可以使用水平方向的 LinearLayout。请注意,其他布局可以嵌套在其中,因此复合组件可以任意复杂化和结构化。
public class CustomLayoutView extends FrameLayout
- 其布局可以嵌套在其中,因此复合组件可以任意复杂化和结构化。请注意,就像使用 Activity 一样,您可以使用声明式(基于 XML)方法来创建所包含的组件,也可以通过编程方式从代码中嵌套组件。如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/progress_bar_container" android:clickable="false" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/submission_delay_text" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/BodyRegBlack" android:visibility="gone" android:layout_marginBottom="@dimen/dp_80" tools:text="Please wait while we submit your inventory count" /> <ImageView android:id="@+id/progress_image" android:layout_width="@dimen/dp_125" android:layout_height="@dimen/dp_125" android:src="@drawable/gif_load_progress" android:visibility="visible" /> </LinearLayout>
- 在新类的构造函数中,获取父类所需的任何参数,将它们先传递给父类构造函数。然后,您可以设置其他视图以在新组件中使用;您也可以将自己的属性和参数引入到 XML 中,以供构造函数提取和使用。
<declare-styleable name="CustomLayoutView"> <attr name="layout_show" format="boolean" /> <attr name="layout_clickable" format="boolean" /> <attr name="layout_background" format="reference" /> </declare-styleable> // 在代码中调用: init(){ val a = context.obtainStyledAttributes(attrs, R.styleable.CustomLayoutView, 0, 0) val layoutClickable = a.getBoolean(R.styleable.CustomLayoutView_layout_clickable, false) val layoutShow = a.getBoolean(R.styleable.CustomLayoutView_layout_show, false) val layoutBackground = a.getResourceId(R.styleable.CustomLayoutView_layout_background, R.color.transparent) a.recycle() }
- 您还可以为包含的视图可能生成的事件创建监听器。
- 您还可以使用存取器和修饰符创建自己的属性。
- 如果要扩展 Layout,您无需替换
onDraw()
和onMeasure()
方法,因为布局的默认行为会正常发挥作用。不过,您仍然可以根据需要替换这些方法。 - 您可以替换其他
on...
方法(如onKeyDown()
),以在按下某个键时从组合框的弹出式列表中选择特定的默认值。
对于它,我们可以非常快速地构建任意复杂化的复合视图,并像使用单个组件一样重复使用它们。
示例:【CustomView】Android SlidingTabLayout 按钮之间切换指示器滑动-简单实现
#3. 修改现有 View (扩展现有View)
通过一个更简单的选项来创建自定义 View,这在某些情况下非常实用。如果已经有一个组件非常契合您的需要,则只需扩展该组件并只替换您希望更改的行为即可。您可以使用完全自定义的组件来完成所有操作,但是通过从视图层次结构中更专用的类着手,您还可以免费获得很多可能完全符合您需求的行为。
如要创建扩展现有View,请执行以下操作:
- 使用以下行进行定义:
public static class LinedEditText extends EditText
1.LinedEditText
定义为NoteEditor
Activity 中的内部类,但它是公开类,因此可以作为NoteEditor.LinedEditText
从NoteEditor
类的外部访问(如果需要)。 2.它是static
,这意味着它不会生成允许其从父类访问数据的所谓“合成方法”,而这反过来意味着它的行为方式其实就像单独的类(而不是与NoteEditor
密切相关的类)。如果内部类不需要从外部类访问状态,那么这是创建内部类的更简洁的方法,可以使生成的类一直比较小,并允许其他类轻松使用。 3.它扩展了EditText
,即我们在这种情况下选择自定义的 View。之后,新类将能够取代普通的EditText
视图。 - 类初始化
- 替换方法,如下示例:
// 替换方法,即在原有的on..方法中添加额外的处理 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (changed) { paint.shader = LinearGradient(0F, 0F, width.toFloat() / 2, height.toFloat(), ContextCompat.getColor(context, R.color.colorMainGradient), ContextCompat.getColor(context, R.color.colorMain), Shader.TileMode.CLAMP) } }
对于此示例,通过替换
onLayout()
方法,再原View的paint上添加渐变色绘制,是文本以渐变色显示出来。
示例:【CustomView】渐变色文本(GradientTextView)- 简单实现