【CustomView】Android 自定义视图组件

朱运诚
2023-12-01

Android 提供了一个复杂且强大的组件化模型,可帮助您根据基本布局类 ViewViewGroup 构建界面。首先,该平台包含各种预构建的 View 和 ViewGroup 子类,分别称为微件和布局,可供用来构建界面。

  1. 可用的部分微件: Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner,以及具有特殊用途的 AutoCompleteTextView、ImageSwitcher 和 TextSwitcher。
  2. 可用布局: LinearLayout、FrameLayout、RelativeLayout 等

而自定义视图组件又分为:

#1. 完全自定义的组件

完全自定义的组件可用于创建外观完全如您所需的图形组件。要创建完全自定义的组件,请执行以下操作:

  1. 扩展的最通用的视图是 View,因此您通常需要先扩展此视图,以创建新的父组件。如下:
    public class CustomView extends View
  2. 您可以提供一个构造函数(从 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()
    }

     

  3. 您可能需要创建自己的事件监听器、属性存取器和修饰符,以及在组件类中创建可能更为复杂的行为。
  4. 您几乎肯定需要替换 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. 复合控件

整合包含一组现有控件的可再用组件,属于复合组件(或复合控件)。简而言之,这会将许多更原子的控件(或视图)整合到可被视为一件事的项的逻辑分组中。

如要创建复合组件,请执行以下操作:

 

  1. 通常从某种类型的 Layout 入手,因此请创建可扩展 Layout 的类。对于组合框,我们可以使用水平方向的 LinearLayout。请注意,其他布局可以嵌套在其中,因此复合组件可以任意复杂化和结构化。
    public class CustomLayoutView extends FrameLayout
    
    

     

  2. 其布局可以嵌套在其中,因此复合组件可以任意复杂化和结构化。请注意,就像使用 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>

     

  3. 在新类的构造函数中,获取父类所需的任何参数,将它们先传递给父类构造函数。然后,您可以设置其他视图以在新组件中使用;您也可以将自己的属性和参数引入到 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()
    }

     

  4. 您还可以为包含的视图可能生成的事件创建监听器。
  5. 您还可以使用存取器和修饰符创建自己的属性。
  6. 如果要扩展 Layout,您无需替换 onDraw() 和 onMeasure() 方法,因为布局的默认行为会正常发挥作用。不过,您仍然可以根据需要替换这些方法。
  7. 您可以替换其他 on... 方法(如 onKeyDown()),以在按下某个键时从组合框的弹出式列表中选择特定的默认值。

对于它,我们可以非常快速地构建任意复杂化的复合视图,并像使用单个组件一样重复使用它们。

示例:【CustomView】Android SlidingTabLayout 按钮之间切换指示器滑动-简单实现

 

#3. 修改现有 View (扩展现有View)

通过一个更简单的选项来创建自定义 View,这在某些情况下非常实用。如果已经有一个组件非常契合您的需要,则只需扩展该组件并只替换您希望更改的行为即可。您可以使用完全自定义的组件来完成所有操作,但是通过从视图层次结构中更专用的类着手,您还可以免费获得很多可能完全符合您需求的行为。

如要创建扩展现有View,请执行以下操作:

  1. 使用以下行进行定义:
    public static class LinedEditText extends EditText

    1.LinedEditText 定义为 NoteEditor Activity 中的内部类,但它是公开类,因此可以作为 NoteEditor.LinedEditText 从 NoteEditor 类的外部访问(如果需要)。      2.它是 static,这意味着它不会生成允许其从父类访问数据的所谓“合成方法”,而这反过来意味着它的行为方式其实就像单独的类(而不是与 NoteEditor 密切相关的类)。如果内部类不需要从外部类访问状态,那么这是创建内部类的更简洁的方法,可以使生成的类一直比较小,并允许其他类轻松使用。          3.它扩展了 EditText,即我们在这种情况下选择自定义的 View。之后,新类将能够取代普通的 EditText 视图。

  2. 类初始化
  3. 替换方法,如下示例:
    // 替换方法,即在原有的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)- 简单实现

 

 类似资料: