当前位置: 首页 > news >正文

安卓开发如何实现自定义View

自定义View

三种常见自定义控件方式

方式特点举例
① 继承现有控件改造已有控件圆角 ImageView、自定义 Button、带图标的 TextView
② 组合控件在 XML 里组合多个控件搜索框、标题栏
③ 完全自绘控件自己绘制(继承 View)进度条、雷达图、波浪图等

一、继承现有控件

继承已有控件(如 TextView、ImageView)后改造外观或逻辑,下面重写一个给文字自带下划线的TextView为例演示一下如何继承控件。

1.继承对应的控件类

改造VIew需要继承重写类并根据需要写对应VIew的属性集(AttributeSet)

下面是继承TextView类的控件,为这个控件写一个类,构造参数必须要有context和attrs,Android 系统在解析 XML 时,会自动调用自定义控件的「双参数构造函数」(context: Context, attrs: AttributeSet,并传入这两个必要参数

下面的AttributeSet 是一个接口,它直接代表了 XML 布局文件中为某个 View 定义的所有属性的原始集合。TypedArray 是一个封装类,它对 AttributeSet 进行了高级处理,能够自动解析资源引用并返回对应的实际值。

class UnderlineTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {private var showUnderline: Boolean = trueinit {// 读取自定义属性attrs?.let {val typedArray = context.obtainStyledAttributes(it, R.styleable.UnderlineTextView)//解析 AttributeSet (it 即指代 attrs),并返回一个 TypedArray 对象。showUnderline = typedArray.getBoolean(R.styleable.UnderlineTextView_showUnderline, true)typedArray.recycle()//TypedArray 是一个共享资源,使用完毕后必须调用 recycle() 方法来将其回收。}applyUnderline()}private fun applyUnderline() {paint.flags = if (showUnderline) {Paint.UNDERLINE_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG//表示同时启用两者,一个是抗锯齿,一个是下划线} else {Paint.ANTI_ALIAS_FLAG}invalidate() // 通知重绘}fun setShowUnderline(show: Boolean) {showUnderline = showapplyUnderline()}
}

Paint 是 Android 2D 绘图(如自定义 View 绘制文字、图形)的核心类,用于定义绘制的样式(颜色、字体、线条粗细、特效等),相当于现实中的 “画笔” 。

flagsPaint 的一个整数属性,用于通过位运算or/and 等)设置多个绘制标志(flag),每个标志代表一种绘制特性。例如:

  • Paint.UNDERLINE_TEXT_FLAG:文字下划线标志(为文字添加下划线)。
  • Paint.ANTI_ALIAS_FLAG:抗锯齿标志(让绘制的图形 / 文字边缘更平滑,减少锯齿感)。

invalidate()是View类方法,用于触发当前 View 的重绘

2.自定义属性

自定义属性需要单独写一个文件,一般放在res/values目录下,文件名命名为attrs表明是自定义属性集,这个属性就是在xml文件中进行设置的属性,类似TextView的textColortextSize这种属性

<resources><declare-styleable name="UnderlineTextView"><attr name="showUnderline" format="boolean" /></declare-styleable>
</resources>

3.布局文件中使用自定义控件

在布局文件中使用和正常使用控件几乎没区别,不过注意自定义控件要写对应的全限定名app:showUnderline="true"就是我们自定义的属性了

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical"android:gravity="center"android:layout_width="match_parent"android:layout_height="match_parent"><com.example.customview.UnderlineTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="这段文字有下划线"android:textSize="18sp"android:textColor="#000000"app:showUnderline="true"/><com.example.customview.UnderlineTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="这段文字没有下划线"android:textSize="18sp"android:textColor="#000000"app:showUnderline="false"/>
</LinearLayout>

二、组合控件

组合控件相对比较简单,我们先写好想要组合的控件xml文件,这就是正常定义一个xml文件

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"><TextViewandroid:id="@+id/textView"android:layout_width="match_parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"android:layout_marginStart="80dp"android:layout_marginEnd="80dp"android:layout_height="80dp"android:gravity="center"android:textSize="25sp"app:layout_constraintTop_toTopOf="parent"android:text="标题栏" /><ImageViewandroid:id="@+id/imageView"android:src="@drawable/ic_launcher_foreground"android:layout_width="wrap_content"android:layout_height="wrap_content"android:maxWidth="80dp"android:maxHeight="80dp"android:adjustViewBounds="true"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"/><ImageViewandroid:id="@+id/imageView2"android:layout_width="wrap_content"android:layout_height="wrap_content"app:srcCompat="@drawable/ic_launcher_foreground"android:maxWidth="80dp"android:maxHeight="80dp"android:adjustViewBounds="true"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toEndOf="@id/textView" /></androidx.constraintlayout.widget.ConstraintLayout>

然后写一个类继承一个布局(不必和组合控件的布局相同),在构造器里面填充inflate该界面,如果想在活动里动态设置按钮的事件可以像下面一样写一个回调方法,如果想要避免反复写相同的逻辑,可以直接在初始化代码块中写好想要的监听事件。

class TitleLayout@JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defAttr : Int = 0) : LinearLayout(context, attrs, defAttr) {lateinit var exitImage: ImageViewlateinit var toastImage: ImageViewinit {val view = LayoutInflater.from(context).inflate(R.layout.title_layout, this)exitImage = view.findViewById<ImageView>(R.id.imageView)toastImage = view.findViewById<ImageView>(R.id.imageView2)}fun onExit (listener : OnClickListener) {exitImage.setOnClickListener(listener)}fun onToast(listener: OnClickListener) {toastImage.setOnClickListener(listener)}
}

做好之后就正常在布局文件中使用了

<com.example.myview.TitleLayoutandroid:id="@+id/title"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_constraintTop_toTopOf="parent"/>

三、自绘控件

自定义VIew的重头戏还在于自绘VIew,可以用来实现一些不规则的效果,这种方式最灵活也最麻烦,需要我们直接在一个画布上绘制我们想要的ui,因此需要重写onDraw()方法。在自绘控件情况下,我们需要考虑到该控件的wrap_content模式或者其他更复杂的需求,因此需要重写onMeasure()。下面以一个我们常见的ui的例子写起—天气预报中显示紫外线强度的线性彩虹条。

在这里插入图片描述

linePaintcirclePaint分别是画彩虹条和指示点的画笔, style = Paint.Style.STROKEstyle = Paint.Style.FILL分别是两种不同的绘制样式,前者是只描边,后者是将图形填充完全,由于彩虹条本来只是一条线,因此描边就够了。而指示点我们这里需要一个实心的因此用了FILLonSizeChanged

LinearGradient 是一个着色器(Shader)对象,它定义了颜色如何在一个区域内进行线性过渡,也就是实现上面的渐变效果。setShader则是给我们的画笔设置这个着色器对象,使画笔画出来的对象实现对应的渐变效果。最后让我们的画笔在画布上绘画即可 canvas.drawLine(0f, centerY, w, centerY, linePaint),canvas.drawCircle(x, centerY, circleRadius, circlePaint)

onSizeChanged()方法会在尺寸变化时调用,在尺寸发生变化时(比如旋转了设备或者代码中动态设置了尺寸),我们就需要重新给彩虹条定位。

dp方法是为了适应不同设备的分辨率,否则直接使用dx单位可能因为手机分辨率不同造成更大的差异,这个直接套用即可。

class RainbowBar @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = dp(6f) // 使用 dpstrokeCap = Paint.Cap.BUTT//彩虹条端点设置为直线,可以用ROUND设置为圆形}private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.FILLcolor = Color.parseColor("#FFFFFFFF")}private var gradient: LinearGradient? = nullprivate var dataPoints: FloatArray = floatArrayOf(0.2f)private val circleRadius = dp(7f)init {}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)if (w > 0 && h > 0) {gradient = LinearGradient(0f, h / 2f, w.toFloat(), h / 2f,intArrayOf(Color.parseColor("#009FFB"), Color.parseColor("#55D2CC"), Color.parseColor("#FBD449"), Color.parseColor("#FEA736"), Color.parseColor("#FE3F4F")  ),null,Shader.TileMode.CLAMP)linePaint.shader = gradient}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returnval w = width.toFloat()val centerY = height / 2fif (gradient == null) {gradient = LinearGradient(0f, centerY, w, centerY,intArrayOf(Color.parseColor("#009FFB"),Color.parseColor("#55D2CC"),Color.parseColor("#FBD449"),Color.parseColor("#FEA736"),Color.parseColor("#FE3F4F")),null,Shader.TileMode.CLAMP)linePaint.shader = gradient}canvas.drawLine(0f, centerY, w, centerY, linePaint)for (v in dataPoints) {val clamped = v.coerceIn(0f, 1f)val x = clamped * wcanvas.drawCircle(x, centerY, circleRadius, circlePaint)}}fun setDataPoints(points: FloatArray) {dataPoints = points.map { (it/10f).coerceIn(0f, 1f) }.toFloatArray()invalidate()}private fun dp(v: Float): Float =TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, v, resources.displayMetrics)
}

这样就基本完成了我们的彩虹条,可以直接在布局文件中使用了。

<com.example.weatherforecast.RainbowBarandroid:id="@+id/intensity_bar"android:layout_width="match_parent"android:layout_height="20dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"android:layout_marginStart="20dp"android:layout_marginEnd="20dp"app:layout_constraintTop_toBottomOf="@id/intensity_content"/>
http://www.dtcms.com/a/533172.html

相关文章:

  • 【netty】基于主从Reactor多线程模型|如何解决粘包拆包问题|零拷贝
  • python数据清洗与预处理指南
  • 【模型评测】主流编程大模型QML编程横向对比
  • 网站怎么做团购什么是网络营销网络营销与电商营销有什么区别
  • Go语言:常量设置的注意事项
  • 网络营销导向企业网站建设的一般原则包括徐州网站排名系统
  • 基本魔法语言分支和循环 (二) (C语言)
  • 根目录下两个网站怎么做域名解析科技进步是国防强大的重要的保证
  • 微网站建设c品牌网站设计流程
  • 有哪些cua模型 Computer-Using Agent
  • 网站建设方案模板高校物流信息网站
  • 网工综合知识总结
  • 科技前沿七日谈:从AI普惠到硬件创新,技术正重塑产业边界
  • 初识AES
  • (五)图文结合-详解BLE连接原理及过程
  • 资产管理公司网站建设费用怎么入账电子商务行业发展趋势及前景
  • 机器学习日报05
  • 成都公园城市建设局网站seo诊断分析工具
  • 算法基础 典型题 数学(基础)
  • 网站开发运作wordpress数据库字典
  • 博州住房和城乡建设部网站wordpress开发教程
  • 邢台123网站模板百度推广官方投诉电话
  • 找网站开发人员wordpress ftp附件
  • Xshell 总是在最前端显示
  • 湖北省建设厅官方网站文件网站建设方案书写
  • 网站的成功案例wordpress 登陆白屏
  • 网站开发代码语言网站蜘蛛抓取
  • 斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Lecture 6: Kernels,Triton
  • 【第十九周】自然语言处理的学习笔记04
  • 巴中住房和城乡建设局网站wordpress.exe