0.4 View 工作流程

news/2026/1/20 23:09:42/文章来源:https://www.cnblogs.com/a867686201/p/19509030

自定义 View 核心要点总结与代码注释

一、核心知识点总结

1. View 的工作流程

  • measure:确定 View 的测量宽高(onMeasure
  • layout:确定 View 的最终位置(onLayout,对单一 View 无作用)
  • draw:将内容绘制到屏幕上(onDraw

2. MeasureSpec 说明

  • EXACTLY:对应 match_parent 或具体数值
  • AT_MOST:对应 wrap_content
  • UNSPECIFIED:父容器不对子 View 限制大小(多用于系统内部)

3. 自定义 View 注意事项

  • 必须处理 wrap_content:否则其行为等同于 match_parent
  • 必须手动处理 padding:在 onDraw 中考虑 padding 值
  • 背景影响最小尺寸getSuggestedMinimumWidth() 会结合 minWidth 与背景 drawable 的最小宽度
  • 推荐使用 resolveSize():统一处理 MeasureSpec 逻辑

4. 自定义 View 分类

  • 继承 View:需自行处理 wrap_contentpadding
  • 继承已有控件(如 TextView):无需处理测量和 padding
  • 继承 ViewGroup:需实现子 View 的测量与布局
  • 组合控件:继承 ViewGroup 封装多个子 View

二、代码文件(带完整注释)

CircleView.kt

package com.shakespace.artofandroid.chapter04import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import com.shakespace.artofandroid.R/*** 自定义 CircleView,演示如何正确处理:* - 自定义属性(circle_color)* - wrap_content 的默认尺寸* - padding 的绘制偏移*/
class CircleView @JvmOverloads constructor(context: Context?,private val attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {// 默认颜色为红色var color = Color.RED// 开启抗锯齿的画笔val paint = Paint(Paint.ANTI_ALIAS_FLAG)init {initView()}/*** 初始化自定义属性*/private fun initView() {// 从 XML 中读取自定义属性val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView)color = typedArray.getColor(R.styleable.CircleView_circle_color, Color.RED)// radius 属性被注释,实际使用 view 宽高决定paint.color = colortypedArray.recycle() // 必须回收}/*** 重写 onMeasure 以支持 wrap_content* 若不重写,wrap_content 行为等同于 match_parent*/override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {// 调用父类无实际作用,但保留良好习惯super.onMeasure(widthMeasureSpec, heightMeasureSpec)// 使用 resolveSize 处理 AT_MOST (wrap_content) 情况// 默认期望大小为 200pxval measuredWidth = Math.max(this.suggestedMinimumWidth,resolveSize(200, widthMeasureSpec))val measuredHeight = Math.max(this.suggestedMinimumHeight,resolveSize(200, heightMeasureSpec))// 设置最终测量尺寸this.setMeasuredDimension(measuredWidth, measuredHeight)}/*** 绘制圆形,正确处理 padding*/override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)// 减去 padding 得到可绘制区域val width = width - paddingLeft - paddingRightval height = height - paddingTop - paddingBottom// 取可绘制区域的内切圆半径val radius = Math.min(width, height) / 2// 圆心位置需加上 paddingLeft / paddingTopval centerX = (paddingLeft + width / 2).toFloat()val centerY = (paddingTop + height / 2).toFloat()canvas?.drawCircle(centerX, centerY, radius.toFloat(), paint)}
}

三、 .txt 内容

z_memo01_view.txt

1. ViewRoot -- ViewRootImplstart with performTraversalsperformMeasure -- measure -- onMeasure ----- (View) measureperformLayout -- layout --onLayout     -----(View ) layoutperformDraw -- draw -- onDraw          ----- (View ) draw2. when measure finished , can use getMeasurdWidth/getMeasureHeight to get width/height (usually it is  equivalent to final width/height)3. when layout finished , can use getTop/getBottom/getLeft/getRight to get top/bottom/left/right position4. MeasureSpecUNSPECIFIED     usually use in system interiorEXACTLY         final width/height == SpecSize , match to match_parent or exact value in xml.AT_MOST         width/height <= SpecSize , match to wrap_content5. ViewRootImpl.javaprivate static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;}6. for View , it's MeasureSpec depends on Parental MeasureSpec and child's LayoutParamsexact value  -- > EXACTLYmatch_parent ---> parentSize  , if parent is UNSPECIFIED  child is 0wrap_content --->EXACTLY/AT_MOST --> AT_MOST and not more than parentSizeUNSPECIFIED  ----> child is 07.protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;getChildMeasureSpecbreak;}return result;}So , Both AT_MOST and EXACTLY , will return specSize in MeasureSpec.When UNSPECIFIED , size would be getSuggestedMinimumWidth() or getSuggestdMinimumHeight()protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}public int getMinimumWidth() {final int intrinsicWidth = getIntrinsicWidth();return intrinsicWidth > 0 ? intrinsicWidth : 0;}如果view没有背景, 那么宽度是mMinWidth , 对应属性 android:minWidth 的值,如果没有设置,则为0 。如果设置了背景,宽度为max(mMinWidth,mBackground.getMinimumWidth())getMinimumWidth 返回的是drawable的原始宽度,如果没有,返回0。 (ShapeDrawable没有原始宽度,BitmapDrawable有原始宽度)note : 直接继承View, 需要重写onMeasure处理wrap_content时的自身大小(通常是一个默认size), 否则wrap_content和match_parent相同solution: set a width/height when wrap_contente.g:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int size = this.dp2px(150.0F);this.setMeasuredDimension(Math.max(this.getSuggestedMinimumWidth(),resolveSize(size, widthMeasureSpec)),Math.max(this.getSuggestedMinimumHeight(),resolveSize(size, heightMeasureSpec)));}8.

z_memo02_viewgroup.txt

ViewGroup1. ViewGroup is a abstract class , it will call measureChildren to measure child viewprotected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < size; ++i) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}create childWidthMeasureSpec by getLayoutParams and parentWidthMeasureSpecgetChildMeasureSpec2. LinearLayout@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);will call child.measure(childWidthMeasureSpec, childHeightMeasureSpec);use mTotalLength to store the preliminary height// Add in our paddingmTotalLength += mPaddingTop + mPaddingBottom;int heightSize = mTotalLength;// Check against our minimum heightheightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightMeasureSpecint heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);heightSize = heightSizeAndState & MEASURED_SIZE_MASK;LinearLayout will measure itself once children's measurement finished在Vertical模式下, 如果是match_parent, 则高度为specSize,如果是wrap_conent,则高度是所有子元素高度总和,但不能超过父容器剩余空间3. 通常在measure完成之后就可以通过getMeasuredWidth/Height来获取测量的宽高,但是某些情况下,系统可能需要多次measure,所以最好在onLayout中再去获取测量的宽高。4. 在onCreate\onStart\onResume 都无法正确获得测量的宽高,可以使用以下四种方式Activity/View -- onWindowFocusChanged (会被调用多次)view.post(runnable)ViewTreeObserver . onGlobalLayoutListener   (调用多次)view.measure(int widthMeasureSpec,int heightMeasureSpec)需要根据LayoutParams判断,如果是match_parent 无法测量。如果是具体值:View.MeasureSpec.makeMeasureSpec(100,View.MeasureSpec.EXACTLY) 得到spec如果是wrap_content:通过 View.MeasureSpec.makeMeasureSpec((1 shl 30)-1,View.MeasureSpec.AT_MOST) 得到spec* 1 shl 30 是 1 << 30 的Kotlin写法5. two incorrect wayView.MeasureSpec.makeMeasureSpec(-1,View.MeasureSpec.UNSPECIFIED)view.measure(WindowManager.LayoutParams.WRAP_CONTENT,indowManager.LayoutParams.WRAP_CONTEN)6. Layout通过setFrame 设定view的四个顶点,初始化mLeft、mRight、mTop、mBottom再通过onLayout确定子View的位置在LinearLayout中的VerticalLayout方法childTop += lp.topMargin;setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);childTop会不断增加。private void setChildFrame(View child, int left, int top, int width, int height) {child.layout(left, top, left + width, top + height);}其中的width和height ,就是子元素的测量宽高。7. getMeasuredWidth 和 getWidth 区别1.在默认实现中, 两者是相同的2.两者的赋值实际不同。3. 如果重写layout方法public void layout(int l , int t , int r, int b ){super.layout(l,t,r+100,b+100)}没有实际意义,但是会导致最终宽高比测量宽高大100px4. 此外,通常测量会进行多次,前几次测量的结果可能不准确。8. onDraw()1. draw background      background.draw(canvas)2. draw itself          onDraw()3. draw children        dispatchDraw4. draw decoration      onDrawScrollBarscomments in ViewGroup.java/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      1. Draw the background*      2. If necessary, save the canvas' layers to prepare for fading*      3. Draw view's content*      4. Draw children*      5. If necessary, draw the fading edges and restore layers*      6. Draw decorations (scrollbars for instance)*/9. View has a special  method "setWillNotDraw" , if a view not need draw anything , set true.in default , this flag in View is false , and is true in ViewGroup , so if need draw something in ViewGroupneed set this in false .public void setWillNotDraw(boolean willNotDraw) {setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

z_memo03_custom_view.txt

自定义view分类
1. 继承View重写onDraw()通常用于实现一些不规则的效果,通过绘制的方式实现。需要自己支持 wrap_content和padding
2. 继承ViewGroup 派生特殊的Layout需要处理ViewGroup的测量和布局过程,并同时处理子元素的测量和布局过程。
3. 继承特定的View用于扩展已有View的功能, 例如TextView, 不需要自己支持wrap_content和padding
4. 继承特定的ViewGroup当需要将某几种View组合在一起时常用,可以将多个view放在一个ViewGroup中。,不需要处理ViewGroup的测量和布局过程。自定义View须知1、支持wrap_content需要在onMeasure时对wrap_content做处理,给一个最小宽高。2、如果有必要,需要支持padding如果不在onDraw时计算padding,那么padding将不起作用。ViewGroup的measure和layout中也需要考虑padding的影响。3、 尽量不要在View中使用handler,使用post完全可以替代handler4、 有动画和线程需要及时停止,可以在onDetachedFromWindow中操作此方法对应onAttachedToWindow当View不可见时也需要及时处理线程和动画。5、 带有滑动嵌套时,需要处理滑动冲突。

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

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

相关文章

双向隔离DCDC仿真之Simulink探索

双向隔离DCDC仿真simulink在电力电子领域&#xff0c;双向隔离DC - DC变换器因其能实现能量双向流动且电气隔离的特性&#xff0c;在众多场合如电动汽车、分布式能源存储系统中有着广泛应用。而Simulink作为一款强大的系统级建模与仿真工具&#xff0c;为双向隔离DC - DC变换器…

西门子s7 200smart与3台力士乐变频器通讯程序 原创可直接用于生产的程序,程序带注释...

西门子s7 200smart与3台力士乐变频器通讯程序 原创可直接用于生产的程序&#xff0c;程序带注释&#xff0c;并附送触摸屏程序&#xff0c;有接线方式和设置&#xff0c;通讯地址说明等。 程序采用轮询&#xff0c;可以后续根据要求适当修改后扩展。 器件&#xff1a;西门子s7 …

AI 智能体全攻略:从入门到落地的实战指南

大家好我是菲菲~~如果你关注 2025 年的 AI 领域动态&#xff0c;想必会发现 “智能体&#xff08;Agents&#xff09;” 已成行业热词。这种具备自主工作能力的 AI 形态&#xff0c;既能处理日常琐事&#xff0c;也能驾驭企业级复杂多智能体工作流&#xff0c;其发展潜力不可限…

0.3 view 滑动冲突

Android 触摸事件分发与滑动冲突处理 — 完整文档 一、核心知识点总结 1. 事件分发流程(Event Flow)事件传递路径:Activity → Window (DecorView) → ViewGroup → View 若所有子 View 均未消费事件,最终会回传到…

OIFC 2026省选 0120

胜兵必骄 wars 称 \(a=1\) 为黑色,否则为白色。 注意到一次战斗本质是交换颜色,一条边被操作两次不会对颜色产生影响。最初的想法是找到一个黑点 \(u\),与白色儿子交换颜色,递归到子树处理;同色的儿子提前递归,回…

流量累计程序 博途v15编写的西门子流量累计程序,封装好的FB块直接可以拿来用,并且配有视频解说

流量累计程序 博途v15编写的西门子流量累计程序&#xff0c;封装好的FB块直接可以拿来用&#xff0c;并且配有视频解说&#xff0c;轻松学会最近在调试现场流量计项目&#xff0c;发现不少新手对累计流量功能实现总是卡壳。今天就给大家拆解一个我封装好的博途V15流量累计FB块&…

2026年广东比较好的刀塔机定制需要多少钱,Y轴/尾顶机/排刀机/数控4+4/正交Y/动力刀塔/直Y,刀塔机厂家推荐排行

近年来,随着中国制造业向高端化、智能化转型,刀塔机作为数控机床领域的核心设备,其技术迭代与定制化需求持续攀升。据行业统计,2025年国内刀塔机市场规模突破120亿元,广东作为制造业大省,占据全国35%以上的市场份…

qt之实现截图效果

以下是修改后的代码,实现鼠标滑动矩形区域截图功能: #include <QGuiApplication> #include <QScreen> #include <QPixmap> #include

【毕业设计】springboot基于大数据技术的诗词信息系统(源码+文档+远程调试,全bao定制等)

java毕业设计-基于springboot的(源码LW部署文档全bao远程调试代码讲解等) 博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、…

【Python】解决 Windows 下 pip 安装报错 OSError: [Errno 2] No such file or directory (路径过长问题)

目录 【Python】解决 Windows 下 pip 安装报错 OSError: [Errno 2] No such file or directory (路径过长问题) 1. 问题描述 2. 原因分析 3. 解决方案 方案一&#xff1a;修改临时目录路径&#xff08;最快&#xff0c;无需重启&#xff09; 方案二&#xff1a;解除 Wind…

深夜调模型的工程师都懂,燃油车和电动车之间总得有个“和事佬“——增程器。今天咱们聊的这个Cruise仿真模型,就是要把这个中间商做出价值

cruise模型&#xff0c;增程汽车仿真模型&#xff0c;串联混动&#xff0c;基于cruise/simulink联合仿真。 实现增程器多点控制策略&#xff0c;及电制动优先的能量回收策略。 提供cruise模型、控制策略文件以及策略说明文档&#xff0c;方便您在模型基础上进行扩展。先看底盘架…

《把脉行业与技术趋势》-72-伟大的组织,不只是会收割,更要会培育土壤。“春天开荒播种是为了秋天收获果实”。

导入期&#xff08;春&#xff09;&#xff1a;开荒播种&#xff08;技术商业可行&#xff09; 成长期&#xff08;夏&#xff09;&#xff1a;快速成长&#xff08;抢占市场份额&#xff09; 成熟期&#xff08;秋&#xff09;&#xff1a;收获果实&#xff08;获取利润收益&a…

【python实用小脚本-336】HR如何用Python改造敏感信息传递流程?信息安全×代码的化学反应,轻松实现音频隐写术

一、那个差点让我丢饭碗的"保密文件"事件 去年做HR总监助理时&#xff0c;我负责向15位高管同步一份未公布的组织架构调整方案。按照"传统流程"&#xff0c;我加密了PDF&#xff0c;单独发送了解压密码&#xff0c;还特意微信叮嘱"阅后即焚"。结…

【2026开年巨献】Gemini 3.0全面解析:从技术原理到商业落地,开发者不可错过的AI革命指南

&#x1f680; 一、开篇&#xff1a;当Gemini 3.0重塑AI格局&#xff0c;开发者如何把握机遇&#xff1f;&#x1f3af;&#x1f525; 2026年1月20日重磅&#xff1a;Google最新财报披露&#xff0c;Gemini已成公司增长新引擎&#xff01;全球10亿设备搭载Gemini&#xff0c;企…

【GoFrame (GF) 】高性能、模块化、企业级的 Go 语言开发框架

文章目录前言一、核心架构1. 基础层&#xff08;Core Foundation&#xff09;2. 核心层&#xff08;Core Modules&#xff09;3. 扩展层&#xff08;Extended Modules&#xff09;4. 基础设施层&#xff08;Infrastructure&#xff09;二、关键技术特性解析1. 高性能设计2. 易用…

【计算机毕业设计案例】基于springboot+大数据技术旅游商品管理系统大数据毕设选题推荐:基于大数据技术旅游商品管理系统基于springboot+大数据技术旅游商品管理系(程序+文档+讲解+定制)

java毕业设计-基于springboot的(源码LW部署文档全bao远程调试代码讲解等) 博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、…

【2026 深度观察】大模型战国时代:中美双极、四强争霸与生态分化

目录 【2026 深度观察】大模型战国时代&#xff1a;中美双极、四强争霸与生态分化 前言&#xff1a;告别“单体为王”&#xff0c;拥抱“各司其职” 一、 Anthropic&#xff1a;代码与 Agent 的“无冕之王” 二、 OpenAI&#xff1a;逻辑推理与数学的“象牙塔尖” 三、 Go…

同步FIFO的三种写法各有特点。计数器法直接用读写计数器差值判断空满,适合小深度场景。举个例子,当depth=1时可以直接用寄存器存储数据

FIFO verilogIP 包括深度为1的fifo 包括普通同步FIFO和异步FIFO&#xff0c;均为first word fall through模式&#xff0c;同步fifo三种写法&#xff0c;异步fifo三种写法&#xff0c;可参数化配置&#xff0c;接口为fifo的基础接口 提供基础的testbench&#xff0c;两种fif…

大数据领域 Elasticsearch 集群搭建全流程

大数据领域Elasticsearch集群搭建全流程&#xff1a;从环境准备到高可用优化 摘要/引言 在大数据场景下&#xff0c;单独的Elasticsearch节点无法应对海量数据存储、高并发查询和单点故障问题。集群化是解决这些问题的核心方案——通过将多个节点组成集群&#xff0c;可以实现…

自动聊天工具尝试一(寻找方向)

目录 一句话结论&#xff08;先给你定心&#xff09; 一、先整体看清这条完整路线&#xff08;你现在在第 0 步&#xff09; 二、真正的「第一步」&#xff1a;明确你要识别什么&#xff08;非常关键&#xff09; 场景 A&#xff08;最简单&#xff0c;强烈推荐新手&#xf…