Android 自定义View 加 lifecycle 简单使用
前言
本文是自定义view中最简单的使用方法,分别进行 ‘onMeasure’、‘onDraw’、‘自定义样式’、‘lifecycle’的简单使用,了解自定义view的使用。
通过lifecycle来控制 动画的状态
一、onMeasure做了什么?
在onMeasure中获取view 的宽和高 是 ‘0’
测量View
的宽 / 高
- 在某些情况下,需要多次测量
(measure)
才能确定View
最终的宽/高; - 该情况下,
measure
过程后得到的宽 / 高可能不准确; - 此处建议:在
layout
过程中onLayout()
去获取最终的宽 / 高
必须要了解 MeasureSpec 作用
测量规格(MeasureSpec)是由测量模式(mode)和测量大小(size)组成,共32位(int类型),其中:
- 测量模式(mode):占测量规格(MeasureSpec)的高2位;
- 测量大小(size):占测量规格(MeasureSpec)的低30位。
MeasureSpec类用一个变量封装了测量模式(mode)和测量大小(size):通过使用二进制,将测量模式(mode)和测量大小(size)打包成一个int值,并提供了打包和解包的方法,这样的做法是为了减少对象内存分配和提高存取效率。具体使用如下所示:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthModel = MeasureSpec.getMode(widthMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightModel = MeasureSpec.getMode(heightMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// @TODO 在 onMeasure 中获取view的 宽高 获取到是 0Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}")val defWidth = 400val defHeight = 400
// @TODO MeasureSpec.AT_MOST:wrap_content ; MeasureSpec.EXACTLY:match_parent ;if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, defHeight)} else if (widthModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, heightSize)} else if (heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, defHeight)}}
2、onLayout 做了什么
计算位置,里面包含子view 的情况下才会用到这个函数
一般继承自viewGroup或者重新写layout布局
3、onDraw 做了什么
绘制View
自身,设置padding 时要在onDraw中计算
1. 绘制view背景 2. 绘制view内容 3. 绘制子View 4. 绘制装饰(渐变框,滑动条等等)
override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.let {val pL = paddingLeftval pR = paddingRightval pT = paddingTopval pB = paddingBottomvar mHeight = height - pT - pBvar mWidth = width - pL - pRval cy = pT.plus(pB).div(2) + mHeight.div(2).toFloat()val cx = pL.plus(pR).div(2) + mWidth.div(2).toFloat()val cc = Math.min(mHeight, mWidth).div(2).toFloat()it.drawCircle(cx,cy,cc,mPaint)}}
4、lifecycle控制动画的状态
自定义view 继承 DefaultLifecycleObserver 类 然后实现 生命周期=中的方法override fun onStart(owner: LifecycleOwner) {super.onStart(owner)animSetColor.start()}override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)animSetColor.cancel()}override fun onPause(owner: LifecycleOwner) {super.onPause(owner)animSetColor.pause()}override fun onResume(owner: LifecycleOwner) {super.onResume(owner)animSetColor.resume()}在Act中 进行生命周期监听的绑定lifecycle.addObserver(customView)
5、代码示例
自定义View代码
/*** @TODO 自定义view***/
class MyView(context: Context?, attrs: AttributeSet?) :View(context, attrs), DefaultLifecycleObserver {private val mPaint by lazy { Paint() }private val TAG = "MyView"private var i = 0// @TODO 动画实现改变颜色 然后 通过 lifecycle 控制动画的状态:开始、暂停、恢复、取消private val animSetColor by lazy {ValueAnimator.ofInt(0, 100).apply {addListener(object : AnimatorListener {override fun onAnimationStart(animation: Animator) {}override fun onAnimationEnd(animation: Animator) {}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {i++if (i % 2 == 0) {mPaint.color = android.graphics.Color.BLUE}mPaint.color = when (i % 5) {0 -> android.graphics.Color.BLUE1 -> android.graphics.Color.YELLOW2 -> android.graphics.Color.CYAN3 -> android.graphics.Color.MAGENTA4 -> android.graphics.Color.LTGRAYelse -> android.graphics.Color.TRANSPARENT}
// @TODO 每次设置颜色后 调用postInvalidate 重新绘制ViewpostInvalidate()}})
// 动画无线循环执行repeatCount = ValueAnimator.INFINITE
// 间隔一秒执行一次duration = 1000}}init {mPaint.color = Color.Blue.hashCode()mPaint.style = Paint.Style.FILLmPaint.strokeWidth = 20fcontext?.obtainStyledAttributes(attrs, R.styleable.MyView)?.apply {mPaint.color = getColor(R.styleable.MyView_circlr_color, android.graphics.Color.GREEN)recycle()}}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthModel = MeasureSpec.getMode(widthMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightModel = MeasureSpec.getMode(heightMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// @TODO 在 onMeasure 中获取view的 宽高 获取到是 0Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}")val defWidth = 400val defHeight = 400
// @TODO MeasureSpec.AT_MOST:wrap_content ; MeasureSpec.EXACTLY:match_parent ;if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, defHeight)} else if (widthModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, heightSize)} else if (heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, defHeight)}}
//挂在到Act上时
// override fun onAttachedToWindow() {
// super.onAttachedToWindow()
// Log.e(TAG, "onAttachedToWindow: ")
// anim.start()
// }//在Act 销毁时
// override fun onDetachedFromWindow() {
// super.onDetachedFromWindow()
// Log.e(TAG, "onDetachedFromWindow: ")
// anim.cancel()
//
// }override fun onStart(owner: LifecycleOwner) {super.onStart(owner)animSetColor.start()}override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)animSetColor.cancel()}override fun onPause(owner: LifecycleOwner) {super.onPause(owner)animSetColor.pause()}override fun onResume(owner: LifecycleOwner) {super.onResume(owner)animSetColor.resume()}override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)Log.e(TAG, "onLayout: ")}/*** 作用:根据给定的 Canvas 自动渲染View包括其所有子 View)。* 绘制过程:* 1. 绘制view背景* 2. 绘制view内容* 3. 绘制子View* 4. 绘制装饰(渐变框,滑动条等等)* 注:* a. 在调用该方法之前必须要完成 layout 过程* b. 所有的视图最终都是调用 View 的 draw()绘制视图( ViewGroup 没有复写此方法)* c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制* d. 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制*/override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.let {val pL = paddingLeftval pR = paddingRightval pT = paddingTopval pB = paddingBottomvar mHeight = height - pT - pBvar mWidth = width - pL - pRval cy = pT.plus(pB).div(2) + mHeight.div(2).toFloat()val cx = pL.plus(pR).div(2) + mWidth.div(2).toFloat()val cc = Math.min(mHeight, mWidth).div(2).toFloat()it.drawCircle(cx,cy,cc,mPaint)}}
}
自定义View的xml样式文件
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="MyView"><attr name="circlr_color" format="color"/></declare-styleable>
</resources>
layout布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"android:layout_height="match_parent"android:background="#11008811"tools:context=".CustomViewActivity"><com.andriod.police.view.MyViewandroid:id="@+id/customView"android:layout_width="wrap_content"android:layout_height="130dp"android:background="#11f08811"app:circlr_color="@color/cardview_light_background"android:padding="20dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
Act
class CustomViewActivity : AppCompatActivity() {private val customView: MyView by lazy { findViewById(R.id.customView) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_custom_view)
// @TODO 通过 lifecycle 控制动画的状态:开始、暂停、恢复、取消lifecycle.addObserver(customView)}
}
总结
在自定义View中了解在 onMeasure中进行view 的测量,在onLayout中进行对view位置的控制,在onDraw中进行view的绘制。
通过 lifecycle控制view的生命周期,防止出现内存泄露问题如在相应的生命周期中操作动画的执行状态
相关文章:

Android 自定义View 加 lifecycle 简单使用
前言 本文是自定义view中最简单的使用方法,分别进行 ‘onMeasure’、‘onDraw’、‘自定义样式’、‘lifecycle’的简单使用,了解自定义view的使用。 通过lifecycle来控制 动画的状态 一、onMeasure做了什么? 在onMeasure中获取view 的宽和…...

在K8S中,svc底层是如何实现的?
在Kubernetes中,Service是集群内部的一个抽象层,用于定义一组Pod的逻辑分组,并提供统一的访问入口点,同时还可以对这些Pod提供负载均衡和网络代理功能。Service底层的实现主要包括以下几个关键组件和技术: 标签选择器…...

Python pyqt小技巧:默认打开某文件(即自动加载某文件)
文章目录 前言 前言 有的时候需要界面自动加载某文件。不需要人为在打开选择。 import os #自带 import sys # 获取该程序当前文件目录dir_name os.path.dirname(os.path.realpath(sys.argv[0])) f1 os.path.join(dir_name, 题目调度规程.xls) # 拼接路径 文件必须和程序在…...

vue2实现组件库的自动按需引入,unplugin-auto-import,unplugin-vue-components
1.使用ant-design-vue或者element-ui时,如何每个组件都去import导入组件,大大降低了开发效率,如果全局一次性注册会增加项目体积,那么如何实现既不局部引入,也不全局注册? 2.在element-plus官网看到有说明…...

C++第十节:map和set的介绍与使用
【本节要点】 1.关联式容器2.键值对3.map介绍与使用4.set介绍与使用5.multimap与multisedd的介绍与使用 一、关联式容器:数据管理的核心利器 关联式容器是STL中用于高效存储和检索键值对(key-value pair)的数据结构,其底层基于红黑…...

线性代数笔记28--奇异值分解(SVD)
1. 奇异值分解 假设矩阵 A A A有 m m m行 n n n列 奇异值分解就是在 A A A的行向量上选取若干对标准正交基,对它作 A A A矩阵变化并投射到了 A A A的列空间上的正交基的若干倍数。 A v → u → σ u → ∈ R m v → ∈ R n A\overrightarrow{v}\overrightarrow{u…...

【从零开始学习计算机科学】硬件设计与FPGA原理
硬件设计 硬件设计流程 在设计硬件电路之前,首先要把大的框架和架构要搞清楚,这要求我们搞清楚要实现什么功能,然后找找有否能实现同样或相似功能的参考电路板(要懂得尽量利用他人的成果,越是有经验的工程师越会懂得借鉴他人的成果)。如果你找到了的参考设计,最好还是…...

项目中同时使用Redis(lettuce)和Redisson的报错
温馨提示:图片有点小,可以放大页面进行查看... 问题1:版本冲突 直接上图,这个错表示依赖版本不匹配问题,我本地SpringBoot用的是2.7,但是Redisson版本用的3.32.5。 我们通过点击 artifactId跟进去 发现它…...

leetcode-数组
26. 删除有序数组中的重复项 已解答 简单 相关标签 相关企业 提示 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 n…...

人工智能里的深度学习指的是什么?
深度学习(Deep Learning, 简称DL)是机器学习领域的一个重要分支,它通过构建和训练深层神经网络模型,从大量数据中自动学习和提取特征,以实现复杂任务的自动化处理和决策。以下是关于深度学习的详细介绍: 一…...

docker本地部署ollama
启动ollama容器 1.使用该命令启动CPU版运行本地AI模型 docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama 2.此命令用于启动GPU版本运行AI模型 前提是笔记本已配置NVIDIA的GPU驱动,可在shell中输入nvidia-smi查看详细情况…...

LangChain构建语言模型驱动应用的强大框架
LangChain 核心功能与组件链(Chains)记忆(Memory)提示模板(Prompts)代理(Agents)数据检索(Indexes) 应用场景文档问答自动化工作流知识管理系统 发展历程总结…...

2025-03-08 学习记录--C/C++-PTA 习题10-2 递归求阶乘和
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 一、题目描述 ⭐️ 二、代码(C语言)⭐️ #include <stdio.h>double fact( int n ); double facts…...

浅谈 DeepSeek 对 DBA 的影响
引言: 在人工智能技术飞速发展的背景下,DeepSeek 作为一款基于混合专家模型(MoE)和强化学习技术的大语言模型,正在重塑传统数据库管理(DBA)的工作模式。通过结合其强大的自然语言处理能力、推理…...

AI如何重塑运维体系
AI大模型的引入正在从被动响应到主动预防、从经验驱动到数据智能全面重构运维体系。 一、颠覆传统运维模式的技术革新 故障预测:从“救火”到“防火” AI大模型通过整合历史日志、硬件状态、网络流量等多模态数据,结合时间序列分析(如LSTM&am…...

linux 内网下载 yum 依赖问题
1.上传系统镜像 创建系统目录,用户存放镜像,如下: mkdir /mnt/iso上传 iso 文件到 /mnt/iso 文件夹下。 2.挂载系统镜像 安装镜像至 /mnt/cdrom 目录中 mount -o loop /mnt/iso/CentOS-7-x86_64-Minimal-xx.iso /mnt/cdrom3.修改yum源配…...

mapbox开发小技巧
自定义图标 // 1、单个图标 const url ./static/assets/symbols/code24x24/VIDEO.png // 图标路径 map.loadImage(url ,(error, image) > {if (error) throw errormap.addImage(video-icon, image) })// 2、雪碧图利用canvas // json和png图片 function getStyleImage(fil…...

DeepSeek×博云AIOS:突破算力桎梏,开启AI普惠新纪元
背景 在全球人工智能技术高速迭代的背景下,算力成本高企、异构资源适配复杂、模型部署效率低下等问题,始终是制约企业AI规模化应用的关键。 DeepSeek以创新技术直击产业痛点,而博云先进算力管理平台AIOS的全面适配,则为这一技术…...

Java高频面试之集合-07
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:ArrayList 和 Vector 的区别是什么? ArrayList 与 Vector 的区别详解 ArrayList 和 Vector 都是 Java 中基于…...

Redis- 切片集群
切片集群 切片集群什么是Redis Cluster吗?为什么需要切片集群?Redis Cluster的数据分片机制是怎样的?哈希槽的算法是什么基本算法流程 待填坑 切片集群 什么是Redis Cluster吗?为什么需要切片集群? Redis Cluster是R…...

【项目日记(十)】瓶颈分析与使用基数树优化
前言 上一期我们对整个项目进行了细节部分的优化,并在最后测试了多线程环境下和malloc的性能对比测试,发现malloc有时候还是更胜一筹的,基于此我们进行对我们的内存池进行瓶颈分析与优化。 目录 前言 一、项目瓶颈分析 VS编译器下性能分…...

后台管理系统比较全面的分析对比
以下是主流的 后台管理系统模板 分类与技术选型指南,涵盖开源、商业及全栈解决方案,可根据项目需求灵活选择: 一、开源免费模板 1. React 技术栈 Ant Design Pro 官网:pro.ant.design特点:阿里出品,内置 R…...

HCIA复习拓扑实验
一.拓扑图 二.需求 1.学校内部的HTTP客户端可以正常通过域名www.baidu.com访问到百度网络中HTTP服务器 2.学校网络内部网段基于192.168.1.0/24划分,PC1可以正常访问3.3.3.0/24网段,但是PC2不允许 3.学校内部路由使用静态路由,R1和R2之间两…...

TI毫米波雷达开发 —— 串口输出数据解析
TI毫米波雷达开发 —— 串口输出解析 TLV协议协议概述HeaderBodyPadding TI 毫米波雷达芯片计算的结果数据都会从UART发出供上位机进行解析并展示。解析和展示是两个不同的概念,解析指提取有效数据并转换成常见的度量值。展示指数据的可视化。 由于雷达这个领域的特…...

Docker Desktop 4.38 安装与配置全流程指南(Windows平台)
一、软件定位与特性 Docker Desktop 是容器化应用开发与部署的一体化工具,支持在本地环境创建、管理和运行Docker容器。4.38版本新增GPU加速支持、WSL 2性能优化和Kubernetes 1.28集群管理功能,适用于微服务开发、CI/CD流水线搭建等场景。 二、安装环境…...

【AD】5-16 泪滴的添加
1.工具—滴泪(快捷键TE)...

聊天服务器分布式改造
目前的聊天室是单节点的,无论是http接口还是socket接口都在同一个进程,无法承受太多人同时在线,容灾性也非常差。因此,一个成熟的IM产品一定是做成分布式的,根据功能分模块,每个模块也使用多个节点并行部署…...

el-table(elementui)表格合计行使用以及滚动条默认样式修改
一、el-table新增合计行以及el-table展示数据出现的问题 1. 使用合计行 el-table的属性show-summary设为true,即可在表格尾部展示合计行。默认情况下,第一列不展示数据,而显示合计二字,可以通过sum-text自己配置,其余…...

Web前端开发——HTML基础下
HTML语法 一表格1.基本格式2.美化表格合并居中属性 二表单1.input2.select3.textarea4.button5.date6.color7.checkbox8.radio9.range10.number 一表格 1.基本格式 HTML表格由<table>标签定义 其中行由<tr>标签定义,单元格由<td>定义。我们先来…...

Python使用入门(一)
初识数据类型 整型(int) print(666) print(2 10) print(2 * 12)字符串(str) 单行字符串 #单行字符串 print("我是小红aaa") print(我是小红aaa)print("中国上海") print(中国上海)# 输出带引号的字符串 print(我是"小红aaa) print("我是\&qu…...