Android-自定义View的实战学习总结

一、自定义View歌词界面

LrcView 类-->自定义的歌词视图

1. 构造函数和属性初始化

        自定义 View 通常需要提供多个构造函数以支持不同的初始化方式。在 LrcView 中,提供了四个构造函数,最终调用 super 父类构造函数完成初始化, context.obtainStyledAttributes 方法获取自定义属性。

constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context,attrs: AttributeSet?,defStyleAttr: Int
) : this(context, attrs, defStyleAttr, 0)
constructor(context: Context,attrs: AttributeSet?,defStyleAttr: Int,defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {val ta = context.obtainStyledAttributes(attrs, R.styleable.AbstractLrcView)// 获取自定义属性mNormalTextSize = ta.getDimension(R.styleable.AbstractLrcView_lrcTextSize,mResources.getDimension(R.dimen.lrc_text_size))// ... 其他属性获取ta.recycle()// 初始化画笔等mNormalPaint.isAntiAlias = truemNormalPaint.textSize = mNormalTextSize// ... 其他画笔初始化
}
2. 测量和布局

onMeasure 方法:虽然代码中未给出 onMeasure 方法,但在自定义 View 中,通常需要重写该方法来测量 View 的大小,以确定其宽度和高度。

onLayout 方法:重写 onLayout 方法来确定子 View 的位置和大小,或者进行一些初始化操作。在 LrcView 中,当布局发生变化时,会重新设置播放按钮和时间线的边界,并初始化歌词列表。

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)if (changed) {val width = mPlayDrawable.intrinsicWidthval height = mPlayDrawable.intrinsicHeightval l = (mTimeTextWidth - height) / 2val t = getHeight() / 2 - width / 2val r = l + widthval b = t + widthmPlayDrawable.setBounds(l, t, r, b)mTimeLineDrawable.setBounds(mTimeTextWidth,getHeight() / 2 - mTimeLineDrawable.intrinsicHeight / 2,getWidth() - mTimeTextWidth,getHeight() / 2 + mTimeLineDrawable.intrinsicHeight / 2)initEntryList()}
}
 3.绘制

        重写 onDraw 方法来绘制 View 的内容。在 LrcView 中,根据不同的歌词状态(显示歌词、加载歌词、无歌词等)绘制不同的内容,包括播放按钮、时间线、歌词文本等。

@Synchronized
override fun onDraw(canvas: Canvas) {val centerY = height / 2when (mLrcStatus) {STATUS_SHOW_LRC -> {val centerLine = getCenterLine()if (mShowTimeline && !isSimpleMode) {// 绘制播放按钮mPlayDrawable.let {it.setBounds(mDrawableMargin,centerY - mPlayDrawable.intrinsicHeight / 2,mDrawableMargin + mPlayDrawable.intrinsicWidth,centerY + mPlayDrawable.intrinsicHeight / 2)it.draw(canvas)}// 绘制时间线mTimePaint.color = mTimelineColorcanvas.drawLine(mTimeTextWidth.toFloat(),centerY.toFloat(),(getWidth() - mTimeTextWidth ).toFloat(),centerY.toFloat(),mTimePaint)mTimeLineDrawable.draw(canvas)// ... 其他绘制操作}// ... 绘制歌词文本}STATUS_LOADING_LRC -> {mNormalPaint.color = mCurrentTextColordrawText(canvas, mResources.getString(R.string.loading_lrc), centerY.toFloat())}STATUS_EMPTY_LRC -> {mNormalPaint.color = mCurrentTextColordrawText(canvas, mResources.getString(R.string.no_lrc), centerY.toFloat())}else -> {mNormalPaint.color = mCurrentTextColordrawText(canvas, mResources.getString(R.string.no_lrc), centerY.toFloat())}}super.onDraw(canvas)
}
4.事件处理

使用 GestureDetector 来处理触摸事件,如滑动、点击等。在 LrcView 中,通过 GestureDetector.SimpleOnGestureListener 监听不同的手势事件,并根据事件类型进行相应的处理。

private val mSimpleOnGestureListener: GestureDetector.SimpleOnGestureListener =object : GestureDetector.SimpleOnGestureListener() {override fun onDown(event: MotionEvent): Boolean {if (hasLrc() && onPlayClickListener != null) {mScroller.forceFinished(true)mTouching = truemSlideing = falseinvalidate()}return true}override fun onScroll(e1: MotionEvent?,e2: MotionEvent,distanceX: Float,distanceY: Float): Boolean {synchronized(this@LrcView) {if (hasLrc() && !isSimpleMode) {// 添加播放按钮点击事件才能显示时间线if (onPlayClickListener != null) {removeCallbacks(hideTimelineRunnable)mShowTimeline = true}if (!mSlideing) {mSlideing = truemSlideListener?.onSlideStart()}mOffset += -distanceYmOffset = Math.min(mOffset, getOffset(0))mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size - 1))invalidate()return true}}return super.onScroll(e1, e2, distanceX, distanceY)}// ... 其他手势事件处理}// 在构造函数中初始化 GestureDetector
mGestureDetector = GestureDetector(context, mSimpleOnGestureListener)
mGestureDetector.setIsLongpressEnabled(false)override fun onTouchEvent(event: MotionEvent): Boolean {return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
}
5.滚动处理

        使用 Scroller 来实现平滑滚动效果。在 onFling 事件中,调用 mScroller.fling 方法启动滚动,并在 computeScroll 方法中更新滚动位置。虽然代码中未给出 computeScroll 方法,但通常的实现如下        :

override fun computeScroll() {if (mScroller.computeScrollOffset()) {mOffset = mScroller.currY.toFloat()invalidate()}
}

SingleLineLrcView类---单行歌词类

 测量方法 onMeasure
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {var widthMeasureSpec = widthMeasureSpecvar heightMeasureSpec = heightMeasureSpecval widthMode = MeasureSpec.getMode(widthMeasureSpec)if (mMaxWidth > 0) {if (widthMode == MeasureSpec.EXACTLY) {widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(mMaxWidth.toInt(), MeasureSpec.getSize(widthMeasureSpec)), MeasureSpec.EXACTLY)} else {widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth.toInt(), MeasureSpec.EXACTLY)}}val heightMode = MeasureSpec.getMode(heightMeasureSpec)if (heightMode == MeasureSpec.EXACTLY) {heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(Math.max(mNormalTextSize, mCurrentTextSize).toInt(), MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.EXACTLY)} else {heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(mNormalTextSize, mCurrentTextSize).toInt(), MeasureSpec.EXACTLY)}setMeasuredDimension(getDefaultSize(suggestedMinimumWidth, widthMeasureSpec),getDefaultSize(suggestedMinimumHeight, heightMeasureSpec))
}
  • 根据 mMaxWidth 和测量模式调整宽度测量规格。
  • 根据文本大小和测量模式调整高度测量规格。
  • 最后调用 setMeasuredDimension 方法设置测量后的宽高
 绘制方法 onDraw
override fun onDraw(canvas: Canvas) {val centerY = (height / 2).toFloat()when (mLrcStatus) {STATUS_SHOW_LRC -> {if (mLastLrcTime >= 0) {updateShowLine()if (mLrcIndex >= 0 && mLrcIndex < mLrcEntryList.size) {val text: Stringif (mLrcIndex > 0 && mLrcEntryList[mLrcIndex - 1].time == mLrcEntryList[mLrcIndex].time) {text = mLrcEntryList[mLrcIndex - 1].text} else {text = mLrcEntryList[mLrcIndex].text}if (!TextUtils.isEmpty(text)) {drawFocusText(canvas, text, mCurrentPercent, centerY)}}}}STATUS_LOADING_LRC -> {mNormalPaint.color = mCurrentTextColordrawText(canvas, mResources.getString(R.string.loading_lrc), centerY)}else -> {mNormalPaint.color = mCurrentTextColordrawText(canvas, mResources.getString(R.string.no_lrc), centerY)}}super.onDraw(canvas)
}
  • 根据 mLrcStatus 的不同状态,绘制不同的内容。
  • 如果状态为 STATUS_SHOW_LRC,则更新显示的歌词行,并调用 drawFocusText 方法绘制高亮的歌词。
  • 如果状态为 STATUS_LOADING_LRC,则绘制 “Loading lyrics” 的提示信息。
  • 其他状态则绘制 “没有歌词” 的提示信息。
 自定义绘制方法
private fun drawFocusText(canvas: Canvas, text: String, percent: Float, y: Float) {// ...
}private fun drawText(canvas: Canvas, text: String, y: Float) {// ...
}
  • drawFocusText 方法用于绘制高亮的歌词,通过 canvas.clipRect 方法实现歌词的渐变效果。
  • drawText 方法用于绘制普通的文本。

歌词自定义View总结:

        LrcView 类继承自 AbstractLrcView,实现了一个功能丰富的自定义歌词视图,综合运用了多个自定义 View 知识点。

        在构造函数和属性初始化方面,提供了四个构造函数,最终调用父类构造函数完成初始化,并通过 context.obtainStyledAttributes 获取自定义属性,同时初始化画笔等。

        测量和布局上,虽未给出 onMeasure 方法,但重写 onLayout 方法,在布局变化时重新设置播放按钮和时间线边界,并初始化歌词列表。

        绘制时重写 onDraw 方法,依据不同歌词状态(显示、加载、无歌词等)绘制不同内容,如播放按钮、时间线、歌词文本等。事件处理使用 GestureDetector 监听不同手势事件并处理,像滑动、点击等。滚动处理借助 Scroller 实现平滑滚动,在 onFling 启动滚动,在 computeScroll 更新位置。还通过 R.styleable.AbstractLrcView 定义自定义属性,在构造函数中获取其值以配置外观和行为。最后,在 onDraw 方法中根据 mLrcStatus 不同状态绘制对应内容,实现状态管理。


        SingleLineLrcView 是一个继承自 AbstractLrcView 的自定义 Android View,用于显示单行歌词。

        它定义了正常和高亮文本的画笔、颜色、大小等属性,在构造函数中调用 init 方法进行初始化,通过 obtainStyledAttributes 获取自定义属性值并设置画笔属性和字体样式。提供了 setNormalColor 和 setCurrentColor 方法来动态改变文本颜色并刷新视图。

        onMeasure 方法根据最大宽度和测量模式调整视图的宽高。onDraw 方法根据歌词状态(显示歌词、加载歌词、无歌词)绘制不同内容,若显示歌词则更新显示行并调用 drawFocusText 方法绘制高亮歌词,该方法通过 canvas.clipRect 实现歌词渐变效果;若加载歌词或无歌词则调用 drawText 方法绘制提示信息。此外,还提供了 getLrcWidth 方法用于计算歌词显示的宽度。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/89802.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/89802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven 在 Eclipse 中的使用指南

Maven 在 Eclipse 中的使用指南 概述 Maven 是一个强大的构建自动化工具,用于项目管理和构建。它简化了项目构建、依赖管理和项目报告等任务。Eclipse 是一个流行的集成开发环境(IDE),支持多种编程语言,包括 Java。本文将详细介绍如何在 Eclipse 中使用 Maven 进行项目管…

zxing去白边

2025年了&#xff0c;可能干不了几年了&#xff0c;还能写这种文章还是有点可笑。 背景 zxing库生成的二维码自带白边 分析 生产二维码主要分两步&#xff1a; 1.用QRCodeWriter生成BitMatrix信息 2.根据信息生成bitmap 问题在1。 生成二维码的尺寸实际是有一些规格的&a…

Linux操作系统之文件(三):缓冲区

前言&#xff1a; 上节课我们讲授重定向的概念时&#xff0c;曾提到了一点缓冲区的概念。本文将会为大家更详细的带来缓冲区的有关内容&#xff1a;用户级缓冲区是什么&#xff0c;以及其与内核级缓冲区的关系&#xff0c;最后&#xff0c;我会为大家模拟实现一下stdio.h的关于…

Linux云计算基础篇(7)

一、< 输入重定向 wc -l < filelist .txt 统计数据&#xff0c;从file这个文件拿结果。 二、tr 转换字符命令 $ tr A-Za-z<.bash_profile 将bash_profile文件中的大写字符全部转成小写字符 三、管道符&#xff08;|&#xff09; com…

【学习笔记】Lean4基础 ing

文章目录 概述参考文档运行程序elan 命令行工具lean 命令行工具lake 命令行工具运行单文件程序Hello, world!验证 Lean4 证明 运行多文件项目 Lean4 基础语法注释表达式求值变量和定义定义类型变量 定义函数命名规则命名空间数据类型结构体构造子模式匹配多态List 列表Option 可…

FPGA实现40G网卡NIC,基于PCIE4C+40G/50G Ethernet subsystem架构,提供工程源码和技术支持

目录 1、前言工程概述免责声明 3、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的以太网方案 4、工程详细设计方案工程设计原理框图测试用电脑PClE4CDMA40G/50G Ethernet subsystem工程源码架构驱动和测试文件 5、Vivado工程详解1详解&a…

SAP从入门到放弃系列之流程管理概述

文章目录前言1.Process Management&#xff08;过程管理&#xff09;2.关键术语2.1Control recipe destination2.2 Process instruction characteristic2.3 Process message characteristic2.4 Process instruction category2.5 Process message category2.6 PI sheet3.关键配置…

RCLAMP0554S.TCT升特Semtech 5通道TVS二极管,0.5pF+20kV防护,超高速接口!

RCLAMP0554S.TCT&#xff08;Semtech&#xff09;产品解析与推广文案 一、产品定位 RCLAMP0554S.TCT是Semtech&#xff08;升特半导体&#xff09;推出的5通道超低电容TVS二极管阵列&#xff0c;专为超高速数据接口&#xff08;USB4/雷电4/HDMI 2.1&#xff09;提供静电放电&a…

【人工智能】DeepSeek的AI实验室:解锁大语言模型的未来

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 DeepSeek作为中国AI领域的先锋,以其开源大语言模型(LLM)DeepSeek-V3和DeepSeek-R1在全球AI研究中掀起波澜。本文深入探讨DeepSeek AI实验…

nacos+nginx动态配置大文件上传限制

前言 今天还要跟大家分享的一个点就是微服务网关gateway用webflux响应式不用servlet后&#xff0c;引发的一个忽略点差点在演示的时候炸锅&#xff0c;也不多讲废话&#xff0c;说说现象&#xff0c;说说处理就了事。 一、上传超过20MB的视频报错 配置在nacos里&#xff0c;读…

mr 任务运行及jar

mainclass如下&#xff1a;LoggingDriver

Python 数据分析:numpy,抽提,整数数组索引与基本索引扩展(元组传参)。听故事学知识点怎么这么容易?

目录1 代码示例2 欢迎纠错3 论文写作/Python 学习智能体------以下关于 Markdown 编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右Sm…

ECU开发工具链1.10版:更强大的测量、校准与数据分析体验.

汽车电子开发与测试领域&#xff0c;高效、精准且安全的工具是成功的基石。DiagRA X 作为一款广受认可的 Windows 平台综合解决方案&#xff0c;持续引领行业标准。其最新发布的 1.10 版本带来了显著的功能增强与用户体验优化&#xff0c;进一步巩固了其在 ECU 测量、校准、刷写…

Qt C++串口SerialPort通讯发送指令读写NFC M1卡

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.52de2c1bVIuGpf&ftt&id18645495882 一、确定已安装Qt Serial Port组件 二、在.pro项目文件声明引用Serialport组件 三、在.h头文件内引用Serialport组件 四、在.cpp程序中实…

Go 语言开发中用户密码加密存储的最佳实践

在现代 Web 应用开发中&#xff0c;用户密码的安全存储是系统安全的重要环节。本文将结合 Go 语言和 GORM 框架&#xff0c;详细介绍用户密码加密存储的完整解决方案&#xff0c;包括数据库模型设计、加密算法选择、盐值加密实现等关键技术点。 一、数据库模型设计与 GORM 实践…

优化Facebook广告投放的五大关键策略

一、精确筛选目标国家用户在Audience的locations设置目标国家时&#xff0c;务必勾选"People living in this location"选项。系统默认会选择"People living in this location or recently in this location"&#xff0c;这会扩大受众范围&#xff0c;包含…

Debian-10-standard用`networking`服务的`/etc/network/interfaces`配置文件设置多网卡多IPv6

Debian-10-buster-standard用networking服务的/etc/network/interfaces配置文件设置多网卡多IPv6 Debian-10-buster-standard用networking服务的/etc/network/interfaces配置文件设置多网卡多IPv6 250703_123456 三块网卡 : enp0s3 , enp0s8 , enp0s9 /etc/network/interfac…

对话式 AI workshop:Voice Agent 全球五城开发实录

过去几个月&#xff0c;TEN Framework 团队与 Agora 和声网围绕 “对话式AI”题&#xff0c;踏上了横跨全球五大城市的精彩旅程——东京、旧金山、巴黎、北京、京都。 五场精心筹备的Workshop 场场爆满&#xff0c; 汇聚了来自当地及全球的开发者、创业者、产品经理与语音技术爱…

算法学习笔记:6.深度优先搜索算法——从原理到实战,涵盖 LeetCode 与考研 408 例题

在计算机科学领域&#xff0c;搜索算法是解决问题的重要工具&#xff0c;其中深度优先搜索&#xff08;Depth-First Search&#xff0c;简称 DFS&#xff09;凭借其简洁高效的特性&#xff0c;在图论、回溯、拓扑排序等众多场景中发挥着关键作用。无论是 LeetCode 算法题&#…

vue create 和npm init 创建项目对比

以下是关于 vue create 和 npm init 的对比分析&#xff1a; 1. 定位与功能 vue create 定位&#xff1a;Vue 官方提供的脚手架工具&#xff0c;基于 Vue CLI&#xff0c;用于快速创建标准化的 Vue 项目&#xff0c;支持 Vue 2 和 Vue 3。功能&#xff1a;提供交互式配置&…