Android---自定义View

news2025/7/19 2:43:02

 当 Android SDK 中提供的系统 UI 控件无法满足业务需求时,需要考虑自己实现 UI 控件。掌握自定义控件,是理解整套 Android 渲染体系的基础。自定义 UI 控件有2种方式:

\bullet 继承系统提供的成熟控件(比如 LinearLayout、RelativeLayout、ImageView等)

\bullet 直接继承自系统 View 或者 ViewGroup,并自绘显示内容。

继承现有控件

相对而言,这是一种比较简单的实现方式。因为大部分工作,比如核心控件的测量、控制位置的摆放等相关计算,在系统中都已经实现并封装好了。开发人员只要在其继承上进行扩展,并按照自己的意图显示相关元素。比如下面的代码

CustomToolBar 继承自 RelativeLayout,构造函数中通过 addView() 方式,分别添加两个 ImageView 和 一个 TextView。显示效果如下

自定义属性

想在 XML 布局中使用 CustomToolBar 时,希望能在 XML 文件中直接指定 title 的显示内容,字体颜色,leftImage 和 rightImage 的显示图片等,就需要使用自定义属性

自定义属性步骤:

步骤1:在 attrs.xml 中声明自定义属性

在 res 目录的 values 目录下的 attrs.xml 文件中(如果没有就新建一个),使用 <declare-styleable> 标签自定义属性。

<declare-styleable> 标签代表定义一个自定义属性集合,一般会与自定义控件结合使用。<attr>标签是某一条具体是属性,name 是属性名称,format 代表属性格式。 

在 XML 中使用自定义属性

需要先添加命名空间,然后通过命名空间 app 引入自定义属性。

在 CustomToolBar 中,获取自定义属性的引用值

如上图所示,主要通过 context.obtainStyleAttributes() 方法获取到自定义属性集合。然后从从这个集合中取出相应的自定义属性。

直接继承自 View 或 ViewGroup

使用这种方式可以解决更加复杂的 UI 界面。使用这种实现方式需要解决以下介个问题:

\bullet 如何根据相应的属性将 UI 元素绘制到界面(onDraw 方法解决)

\bullet 自定义控件的大小,也就是宽和高分别设置多少(onMeasure 方法实现)

\bullet 如果是 ViewGroup,如何合理安排其内部子 View 的摆放位置(onLayout)

因此,自定义 View 的重点工作就是复写并合理的实现 onDraw()、onMeasure()、onLayout() 这3个方法。注意:并不是每个自定义 view 都需要同时实现这3个方法。大多数情况下,只需要实现2个或其中一个方法也能满足需求。

onDraw() 方法

onDraw 方法接收一个 Canvas 类型的参数。Canvas 可以理解为一个画布,在这个画布上可以绘制各自类型的 UI 元素。系统提供了一些列 canvas 的操作方法,如下:

 从上图中可以看出,每一个操作方法都需要传入一个 Paint 对象。Paint 为画笔,可以通过设置相关属性,来实现不同的绘制效果,比如绘制图像的颜色、线条的粗细等

实例代码

 定义 PieImageView 继承自 View。在 onDraw() 方法中分别使用 canvas 的 drawArs() 和 drawCircle() 方法来绘制弧度和圆形。

在 xml 中直接使用自定义的控件 PieImageView,并设置宽高。如下图所示

也可在 Activity 中设置 PieImageView 的相关内容 

PieImageView pieImageView = findViewById(R.id.pieImageView);
pieImageView.setProgress(45);

setProgress 为 PieImageView 内定义的方法。

运行显示效果,如下

如果在上面代码中的布局文件中,将 PieImageView 的宽高设置为 wrap_content,重新运行则显示效果:

很显然,PieImageView 并没有正常显示。问题的原因就是,PieImageView 并没在 onMeasure() 方法中重新测量,并重新设置宽高。

onMeasure() 方法

首先,我们需要弄明白自定义 View 为什么需要重新测量。正常情况下,我们直接在 XML 文件中定义好 View 宽高,然后让自定义 View 在此区域内显示即可。但是,为了更好的兼容不同尺寸的屏幕,Android 提供了 wrap_content 和 match_parent 属性来规范控件的显示规则。

wrap_content代表自适应大小match_parent代表填充父视图的大小,但是这两个属性并没有指定具体的大小,因此,需要在 onMeasure() 方法中过滤出这两种情况。真正的测量出自定义 View 应该显示的宽高大小,都是在 onMeasure 方法中完成。方法定义如下

方法传入两个参数 widthMeasureSpec 和 heightMeasureSpec。这两个参数是从父视图传个子 view 的两个参数,看起来很像宽高。但是,它们表示的不仅仅是宽和高,还有一个非常重要的测量模式

3种测量模式:

\bullet EXACTLY:表示在 XML 布局文件中宽高使用 match_parent 或者固定大小的宽高。

\bullet AT_MOST:表示在 XML 布局文件中宽高使用 wrap_content

\bullet UNSPECIFIED:父容器没有对当前 View 有任何限制,当前 View 可以取任意尺寸,比如 ListView 中的 item。

具体值和测量模式都可以通过 Android SDK 中提供的 MeasureSpec 类获取

为什么 widthMeasureSpec/heightMeasureSpec 这种 int 类型数据可以代表 2 种意义呢?

实际上,widthMeasureSpec/heightMeasureSpec 都是使用二进制高2位表示测量模式低30位表示宽高具体大小

重新回到 PieImageView,在 PieImageView 中并没有复写 onMeasure() 方法,因此,默认使用父类(View)中的 onMeasure 方法,代码如下

蓝色框中的 setMeasureDimension 是一个非常重要的方法。这个方法传入的值直接决定了 View 的宽高。也就是说,如果直接调用 setMeasureDimension(100, 200),最终 View 显示的宽100 * 高200 的矩形范围。

getDefaultSize() 返回的是默认大小,默认为父视图的剩余可用空间。这也是 PieImageView 显示异常的原因。虽然我们在 xml 中指定的是 wrap_content,但是实际使用的宽高却是父视图剩余的可用空间。从代码中可用看出是整个屏幕的宽高。

问题原因找到了,解决方法就是复写 onMeasure 方法,过滤出 wrap_content 的情况,并主动调用 setMeasureDimension() 方法设置正确的宽高即可。

ViewGroup 中的 onMeasure

如果我们自定义的控件是一个容器,onMeasure 方法会更加复杂一些。因为 ViewGroup 在测量自己宽高时需要先确定其内部子 view 的大小,然后才能确定自己的大小。比如如下一段代码

LinearLayou 的宽高为 wrap_content,表示由子控件大小确定。而三个子控件的宽度分别为300、200、100,最终LinearLayout 的宽度显示如下

可用看出 LinearLayout 的最终宽度,由其内部最大的子 View 确定。

当我们定义一个 ViewGroup 时,也需要在 onMeasure 方法中综合考虑子 view 的宽度。比如,要实现一个流式布局,效果如下

在大多数 App 搜索界面通常会使用流式布局来展示历史搜索记录或热门搜索事件。FlowLayout 每一行上的 item 个数不确定,当每一行的 item 累计宽度操作了屏幕的总宽度,则需重起一行来放 item 项。

因此,我们需要在 onMeasure 方法中主动分行,计算出 FlowLayout 最终高度。

onLayout()

上面的 onMeasure 方法只是测量出 ViewGroup 的最终显示宽高,但是并没有规定一个子 View 应该显示在何处位置。要定义 ViewGroup 内部子 View 的显示规则,则需要复写并实现 onLayout 方法。ViewGroup 中的 onLayout 方法声明如下:

这是一个抽象方法,也就是说每一个自定义 ViewGroup 都必须主动实现如何排列子 View。具体就是遍历每一个子 View,调用 child的l、t、r、b 方法来为每一个子 View 设置具体的位置。l、t、r、b分别代表左、山、右、下的坐标位置。

总结

介绍了自定义View的几个知识点,要自定义一个控件主要包含几个方法:

● onDraw:主要负责绘制UI元素;

● onMeasure: 主要负责测量自定义控件具体显示的宽高

● onLayout: 主要是在自定义ViewGroup中复写,并实现子View的显示位置
并在其中介绍了自定义属性的使用方法.

 

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

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

相关文章

美国跨境金融科技公司【Zolve】完成1亿美元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于美国纽约的跨境金融科技公司Zolve近期宣布已经完成1亿美元融资。 本轮融资由CIM投资 该公司打算将这笔资金用于在英国、加拿大和澳大利亚的进一步扩张。 Zolve由创始人Raghunandan G领导&…

自由程序员想接私活?那你还不得知道这几个接单平台!最后一个就是宝藏!!

相信喜欢搞钱的程序员都知道&#xff0c;平常在平台上接点私活&#xff0c;利用闲暇时间接单是搞钱的常用套路&#xff0c;可是你确定你选对平台了吗&#xff1f;不管你是刚准备接单的小白&#xff0c;还是已经干了一段时间的老油条&#xff0c;都建议你看完本期文章&#xff0…

Spring框架(三)

1、代理模式&#xff1a; 二十三种设计模式中的一种&#xff0c;属于结构型模式。它的作用就是通过提供一个代理类&#xff0c;让我们在调用目标方法的时候&#xff0c;不再是直接对目标方法进行调用&#xff0c;而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标…

【译】快速开始 Compose 跨平台项目

原文&#xff1a; Compose Multiplatform application 作者&#xff1a;JetBrains 注意 Compose Multiplatform 中的 iOS 部分目前处于 Alpha 状态。以后可能会有不兼容的更改&#xff0c;届时也许需要手动进行迁移。 你可以使用这个模板来开发同时支持桌面、安卓和 iOS 的跨平…

极品三国新手攻略之进阶篇

尊敬的主公大人您好&#xff0c;首先恭喜您在游戏中取得的不俗成绩&#xff0c;相信您已经熟练掌握了不少玩法。今天&#xff0c;我们给大家奉上一份极品三国新手攻略之进阶篇&#xff0c;希望能为您提供有力的帮助。本篇攻略将为您深入分析游戏中武将、装备、试炼塔以及神兵等…

【微服务 SpringCloud】实用篇 · Ribbon负载均衡

微服务&#xff08;4&#xff09; 文章目录 微服务&#xff08;4&#xff09;1. 负载均衡原理2. 源码跟踪1&#xff09;LoadBalancerIntercepor2&#xff09;LoadBalancerClient3&#xff09;负载均衡策略IRule4&#xff09;总结 3. 负载均衡策略3.1 负载均衡策略3.2 自定义负载…

“升级是找死,不升级是等死”,GitLab CE 的痛苦升级之路

编者按&#xff1a;本文转载自公众号运维识堂&#xff0c;已经联系作者取得转载授权。 GitLab 在发展的十余年中&#xff0c;在国内积累了大量的 CE 用户&#xff0c;但是很多 CE 用户并不会跟随 GitLab 的发版节奏&#xff08;月度发版&#xff09;进行版本升级&#xff0c;在…

基于AT89C52+ADC0809+LCD1602的模数转换实验ptoteus仿真设计

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a; 三、仿真工程&#xff1a; 基于AT89C52ADC0809LCD1602的模数转换实验ptoteus仿真设计资源-CSDN文库

flask实战(问答平台)

问答平台项目结构搭建 先创建一个配置文件config.py&#xff0c;后面有些配置写在这里 #app.py from flask import Flask import configapp Flask(__name__) #绑定配置文件 app.config.from_object(config)app.route(/) def hello_world(): # put applications code herer…

数据结构-----红黑树的删除操作

目录 前言 一、左旋和右旋 左旋&#xff08;Left Rotation&#xff09; 右旋&#xff08;Right Rotation&#xff09; 二、红黑树的查找 三、红黑树的删除 1.删除的是叶子节点 1.1删除节点颜色为红色 1.2删除节点颜色为黑色 1.2-1 要删除节点D为黑色&#xff0c;兄弟节…

git 提交代码

提交代码流程 第一步:git status 第二步&#xff1a;git add . 第三步&#xff1a;git commit -m"xxx" 第四步&#xff1a;git pull origin dev 第五步&#xff1a;git push origin dev

HEIC转jpg

下载imagemagick,安装 https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-20-Q16-HDRI-x64-dll.exe cmd D:\soft\ImageMagick-7.1.1-Q16-HDRI\magick.exe "C:\Users\Gamer\Downloads\iCloud 照片1\iCloud 照片\IMG_3889.HEIC" IMG_3889.jpg

不懂的东西

1、 2、 3、 4、 5、我看到那篇 Peace of mind 论文&#xff0c;有一个疑问&#xff0c;为什么论文里的量表用的频率指标&#xff1f;比如Some of the time&#xff0c; Not at all等&#xff0c;而PANAS用的是程度指标&#xff0c;比如moderately&#xff0c;a little等。…

一、初识 Elasticsearch:概念,安装,设置分词器

文章目录 01、初识 Elasticsearch正向索引和倒排索引索引MySQL与ES的概念映射安装ES分词器分词器的设置 01、初识 Elasticsearch 本次ES基于&#xff1a;7.12.1 版本 学习资源为&#xff1a;https://www.bilibili.com/video/BV1Gh411j7d6 什么是ES&#xff08;Elasticsearch&…

C/C++笔试易错与高频题型图解知识点(二)—— C++部分(持续更新中)

目录 1.构造函数初始化列表 1.1 构造函数初始化列表与函数体内初始化区别 1.2 必须在初始化列表初始化的成员 2 引用&引用与指针的区别 2.1 引用初始化以后不能被改变&#xff0c;指针可以改变所指的对象 2.2 引用和指针的区别 3 构造函数与析构函数系列题 3.1构造函数与析…

力扣环形链表(1)进阶环形链表(2)及环形链表的约瑟夫问题

为了加深对环形链表的理解和掌握&#xff0c;这两道题是很不错的选择。 这里所说环形链表不是一个圈圈的结构&#xff0c;而是带环链表。 链接&#xff1a;环形链表&#xff08;1&#xff09; 注意这里链表的长度 所以要注意链表是否为空 第一种方法&#xff0c;应该是比较容易…

竞赛选题 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

影响多用户商城系统价格的因素有哪些?

多用户商城系统作为电商行业不可或缺的重要工具&#xff0c;其价格是众多商家关注的焦点。下面就影响多用户商城系统价格的因素有哪些作一些简单的介绍&#xff0c;希望对大家有所帮助(仅供参考)。 一、系统规模和功能 多用户商城系统的价格首先由其规模和功能决定。规模包括用…

如何使用RockPlus MES系统帮助SMT行业实现降本增效

SMT&#xff08;Surface Mount Technology&#xff09;是现代电子行业中主要的组装技术&#xff0c;广泛应用于电子产品的生产。SMT工艺涵盖了锡膏印刷、元器件贴装和回流焊接。经过这些关键工序&#xff0c;元器件被精确固定在电路板上&#xff0c;完成一个电子产品组装。 SM…

Java并发面试题:(五)volatile关键字

volatile 是什么 一旦一个共享变量&#xff08;类的成员变量、类的静态成员变量&#xff09;被volatile修饰之后&#xff0c;那么就具备了两层语义&#xff1a; 1&#xff09;保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c…