• Android 自定义View(一):View是什么?如何创建自定义view,自定义属性等


    目录

    1)View是什么?
    2)View分类
    3)View的知识点
    4)View的工作流程是怎么样的?
    5)案例:如何自定义View?比如我们要实现一个输入框带有清除按钮的view
    6)疑问:宽高怎么样理解,xy这些?
    7)如何开发自定义属性?比如X图标,我想更换成其他图标

    一、View是什么?


    在 Android 中,View 是用户界面基本构建块之一,View类是Android中各种组件的基类,如View是ViewGroup基类。它是一个可见的矩形区域,用于显示应用程序中的内容和与用户进行交互。View 可以包含其他 View 或 ViewGroup,并且可以具有自己的属性、样式和行。

    Android中的UI组件都由View、ViewGroup组成。

    Android 提供了许多内置的 View 类,如 TextView(文本视图)、Button(按钮)、ImageView视图)等。

    二、View分类


    单一视图: 即一个View,如TextView, 不包含子View
    视图组: 即多个View组成的ViewGroup,如LinearLayout, 包含子View

    三、View的知识点


    1. 交互:触摸(TouchEvent)、动画(Animation)

    2. 封装:尺寸(measure)、属性(attributes)

    3. 绘图:Canvas、Paint

    4. 性能:OnDraw,Sufaceview(副线程绘图)


    四、View的工作流程是怎么样的?


    1. 调用 setContentView() 方法设置布局或在 XML 布局文件中定义视图层次结构。
    2. 系统会解析布局文件并创建相应的 View 对象,形成一个视图树(View Hierarchy)。
    3. 在 Activity 或 Fragment 的生命周期方法中调用 onCreate()onStart 和 onResume()` 方法后,系统会开始绘制视图。
    4. 系统从根视图开始遍历整个视图树,并依次调用每个 View 的 draw() 方法进行绘制。
    5. draw() 方法中,View 会先绘制自身的内容,然后递归地绘制它的子视图。
    6. 绘制过中,系统会将每个 View 的绘制结果保存到一个称为 Canvas 的画布对象中。
    7. 最后,系统将 Canvas 中的绘制结果显示在屏幕上。

    注意:在绘制过程中,系统还会对视图进行测量(measure)和布局(layout)操作,以确定每个视图的大小和位置。这些操作通常在 onMeasure()onLayout() 方法中完成。

    请注意,以上流程仅涵盖了基本的 View 绘制流程,实际情况可能因特定需求而有所不同。

    五、如何自定义View?比如我们要实现一个输入框带有清除按钮的view


    如下是我们想要实现的一个view
    在这里插入图片描述
    当android内置view无法满足我们的时候,我们需要自定义View。那么我们应该如何自定义?如何绘制呢?接下来,我们会通过这个案例进行讲解。

    package com.example.mymediaplayer.myview
    
    import android.content.Context
    import android.content.ContextParams
    import android.graphics.Rect
    import android.util.AttributeSet
    import android.util.Log
    import android.view.MotionEvent
    import android.widget.EditText
    import androidx.core.content.ContextCompat
    import com.example.mymediaplayer.R
    
    class MyEditText @JvmOverloads constructor(//第一步:继承EditText
        context: Context, attrs: AttributeSet? = null
    ) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {
        private val TAG = "MyEditText"
        private val iconDrawable = ContextCompat.getDrawable(context, R.drawable.baseline_clear_24)
        init {
            //直接设置显示出来x。drawableRight
        }
    
        override fun onTextChanged(
            text: CharSequence?,
            start: Int,
            lengthBefore: Int,
            lengthAfter: Int
        ) {
            super.onTextChanged(text, start, lengthBefore, lengthAfter)
            //第二步:当文字发生改变的时候,则调用这个方法,进行图标的显示
            toggleCleanIcon()
        }
    
    override fun onTouchEvent(event: MotionEvent?): Boolean {
    //第四步:触摸的时候,并且坐标是在图标上面,才进行文字的删除。
        event?.let {e->
            iconDrawable?.let {
                if (e.action ==MotionEvent.ACTION_UP
                    && e.x>width -it.intrinsicWidth
                    && e.x<width
                    && e.y >height/2 -it.intrinsicHeight/2
                    && e.y <height/2 +it.intrinsicHeight/2){
                    text?.clear()//直接调用清除了,不需要什么点击事件去监听。使用触摸事件。
    
                }
            }
        }
    
        return super.onTouchEvent(event)
    }
    
        override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
            super.onFocusChanged(focused, direction, previouslyFocusedRect)
            //第三步:获得焦点的时候才需要显示
            toggleCleanIcon()
        }
    
        //是否显示
        private fun toggleCleanIcon(){
            val icon = if(isFocused && text?.isNotEmpty()==true)iconDrawable else null
            Log.d(TAG, "toggleCleanIcon: "+icon)
            //用于设置视图(EditText)的四个方向上的可绘制对象(Drawable)。如下就是通过传入右侧的 Drawable 对象来显示在文本视图的右边。
            setCompoundDrawablesRelativeWithIntrinsicBounds(null,null,icon,null)
        }
    }
    

    代码解释:
    1)因为我们做的是一个EditText,所以我们需要继承EditText来自定义一个新的view,那么继承EditText就需要重写他的工作构造方法。
    2)当输入框有文字的时候,我们想显示一个清理图标在输入框的右边,所以我们需要重写onTextChanged方法去监听,当有文字的时候则调用setCompoundDrawablesRelativeWithIntrinsicBounds方法去设置一个drawable。因为我们本身是在EditText里面,所以我们可以直接调用setCompoundDrawablesRelativeWithIntrinsicBounds方法。
    3)并且我们想,输入框获得焦点的时候才显示清理图标,如果失去焦点,那么就消失。
    4)在触摸离开清理图标并且触摸的坐标是在icon的时候,则将文本清空。

    疑问:宽高怎么样理解,xy这些?

    在这里插入图片描述

    override fun onTouchEvent(event: MotionEvent?): Boolean {
    //第四步:触摸的时候,并且坐标是在图标上面,才进行文字的删除。
        event?.let {e->
            iconDrawable?.let {
                if (e.action ==MotionEvent.ACTION_UP
                    && e.x>width -it.intrinsicWidth
                    && e.x<width
                    && e.y >height/2 -it.intrinsicHeight/2
                    && e.y <height/2 +it.intrinsicHeight/2){
                    text?.clear()//直接调用清除了,不需要什么点击事件去监听。使用触摸事件。
    
                }
            }
        }
    
        return super.onTouchEvent(event)
    }
    

    比如这一段代码,e.x就是起点位置,width是获取控件的宽度,intrinsicWidth则是获取控件的固有宽度,例如,一个ImageView的固有宽度可能是其图片的实际宽度,而一个TextView的固有宽度则可能取决于其文本内容的长度和字体大小。

    比如一个输入框的宽度是300,而清除图标的宽度是40,那么e.x>width -it.intrinsicWidth的范围则是图标开始的位置,再加上e.x 在这里插入图片描述
    e.y >height/2 -it.intrinsicHeight/2,为什么要除以2呢,105除以2=52.5,36除以2=13,52.5-13=39,就在图标的顶部,e.y

    除以2的操作是为了进行中心对齐的考量,确保比较的是中心点而不是边缘或其他位置。
    在这里插入图片描述

    七、如何开发自定义属性?比如X图标,我想更换成其他图标

    (1)首先我们要创建一个attrs.xml文件

    在这里插入图片描述

    (2)然后我们为自定义控件创建自定义属性

    
    <resources>
        <declare-styleable name="MyEditText">
            <attr name="clenIcon" format="reference"/>
        declare-styleable>
    resources>
    

    (3)在自定义控件里面读取这个属性并进行设置。

    class MyEditText @JvmOverloads constructor(//实现EditText
        context: Context, attrs: AttributeSet? = null
    ) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {
        private val TAG = "MyEditText"
        private var iconDrawable = ContextCompat.getDrawable(context, R.drawable.baseline_clear_24)
        
        init {
            context.theme.obtainStyledAttributes(attrs,R.styleable.MyEditText,0,0)
                .apply {
                    try {
                        val iconId = getResourceId(R.styleable.MyEditText_clenIcon,0)
                        if (iconId!=0) {
                            iconDrawable = ContextCompat.getDrawable(context,iconId)
                        }
                    }finally {
                        recycle()
                    }
                }
        }
        ...
    
    1. context.theme.obtainStyledAttributes(…):
      (1)这行代码通过context(通常是当前视图所在的上下文,如Activity或Fragment)和theme(当前主题的引用)来获取一个TypedArray对象。这个对象允许你访问在XML布局文件中为该视图指定的自定义属性。
      (2)attrs参数是一个AttributeSet对象,它包含了视图在XML中定义的所有属性。
      (3)R.styleable.MyEditText是一个指向资源文件中定义的自定义属性集的引用。这个资源文件(通常位于res/values/attrs.xml)定义了可以在XML布局文件中使用的自定义属性。
      (4)后面的两个0参数分别是主题资源的ID和布尔值,用于指定是否强制使用默认样式。这里都设置为0,表示不使用特定的主题资源,并且不强制使用默认样式。

    2. val iconId = getResourceId(R.styleable.MyEditText_clenIcon, 0)尝试从自定义属性集中获取名为clenIcon的属性值

    3. finally { recycle() }:无论是否成功读取和设置了图标,finally块中的recycle()方法都会被调用。这是为了释放TypedArray对象占用的资源,避免内存泄漏。recycle()方法是TypedArray类的一部分,用于在不再需要该对象时释放其资源。

    (4)使用自定义属性

    
        <com.example.mymediaplayer.myview.MyEditText
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="212dp"
            android:hint="请输入"
            app:clenIcon="@android:drawable/ic_delete"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    一文彻底弄懂Linux-Shell编程
    P1996 约瑟夫问题 题解
    手把手教你使用Vue3指定状态管理库--Pinia
    K8S部署时IP问题
    URP渲染管线场景优化实战 2.2静态资源导入及优化——Model
    如何找到 docker 容器中的网卡外联的 veth pair 的另一张网卡
    问题解决:MapReduce输出结果乱码(Eclipse)
    51LA网站访问统计使用【图文教程】
    数学建模学习(108):帮助小白快速实现批量机器学习建模训练和批量的数据可视化
    【Python】Windows微信清理工具v.3.0.1
  • 原文地址:https://blog.csdn.net/qq_40853919/article/details/140912734