重学Android:自定义View基础(一)

Android中的View绘制流程 Android中的View绘制流程可以概括为三个主要阶段:测量(Measure)、布局(Layout)和绘制(Draw)。这三个阶段依次进行,共同决定了View的最终呈现效果。测量(Measure)测量阶段的主要任务是确定View的大小。Android系统会遍历View树,对每个View进行测量,以确定其宽度和高度。这个过程是通过调用View的measure()方法实现的。在测量过程中,View会根据其布局参数(如等)和父容器的约束条件来计算自己的测量宽度和高度。 阅读详情

前言

作为一名安卓开发,也被称为大前端,做一个美观的界面,是我们必备的基础技能,可能在开发中我们最常用的是系统自带的View,因为他能满足绝大部分需求,难一点的我们也可以上Github上找个三方库使用,少数情况下会让我们进行自定义View,当然这不代表着我们可以不去掌握其原理,因为它是通往中高级程序员的必经之路,也是大厂面试的热门知识,只有熟练掌握其核心原理,才能让我们在后续的开发中游刃有余。

由于这是开篇文章,说的有点多,笔者是想借着写博客的机会,把那些最不经意的基础打牢一下,并且加上自己的拙见与大家分享,共同进步。

自定义View简介

自定义View是Android开发中的一种常见需求,它允许开发者创建复杂的用户界面组件,以满足特定的设计需求。自定义View的好处在于可以完全控制View的外观和行为。常见的是 extend View 和 extend ViewGroup 以及系统自带的View。

1. onMeasure


onMeasure方法用于测量View的尺寸。它的主要任务是决定View的宽度和高度。以下是一个简单的自定义View示例,它在onMeasure中实现了固定大小的测量逻辑。

示例代码

public class CustomView extends View {
    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 期望的宽高
        int desiredWidth = 200;
        int desiredHeight = 200;

        // 获取父View提供的宽高
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        // 测量宽度
        width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
                width : desiredWidth;

        // 测量高度
        height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
                height : desiredHeight;

        // 设置测量后的宽高
        setMeasuredDimension(width, height);
    }
}

2. onDraw


onDraw方法用于绘制View的内容。在此方法中,使用Canvas绘制图形或文字。

示例代码

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint(); // 创建画笔
    paint.setColor(Color.BLUE); // 设置颜色为蓝色

    // 在中心绘制一个半径为100的圆
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint);
}

3. onTouch


onTouch方法用于处理触摸事件,使View能够响应用户的触摸操作。

示例代码

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 处理按下事件
            // 可以在这里改变View的状态或外观
            break;
        case MotionEvent.ACTION_MOVE:
            // 处理移动事件
            // 例如,移动View或改变某些属性
            break;
        case MotionEvent.ACTION_UP:
            // 处理抬起事件
            // 可以在这里完成某个操作,比如动画结束
            break;
    }
    return true; // 返回true表示事件已被处理
}

4. 自定义属性


通过自定义属性,可以使自定义View在XML中更加灵活。如果你想写一个自定义View的三方库,自定义属性是必须掌握的。

定义

在res/values/attrs.xml中添加自定义属性:

<declare-styleable name="CustomView">
    <attr name="customColor" format="color" />
    <attr name="customSize" format="dimension" />
</declare-styleable>

使用

在自定义View的构造函数中读取这些属性:

public CustomView(Context context, AttributeSet attrs) {
    super(context, attrs);
    
    TypedArray a = context.getTheme().obtainStyledAttributes(
            attrs,
            R.styleable.CustomView,
            0, 0);

    try {
        // 读取自定义颜色属性,默认为黑色
        int customColor = a.getColor(R.styleable.CustomView_customColor, Color.BLACK);
        // 读取自定义尺寸属性,默认为50dp
        float customSize = a.getDimension(R.styleable.CustomView_customSize, 50);
        
        // 使用customColor和customSize进行后续逻辑
    } finally {
        a.recycle(); // 释放TypedArray资源!!!
    }
}

5. 测量模式


在Android中,自定义View的测量过程由三种测量模式决定:EXACTLY、AT_MOST和UNSPECIFIED。

三种布局模式

在 Android 布局中,父 View 可以通过不同的模式给子 View 传递尺寸限制,常见的有以下三种模式:

1. EXACTLY(确切模式)
  • 定义:父 View 给子 View 传递了一个确切的尺寸,子 View 应该使用这个尺寸。
  • 适用场景:一般用于设置为 match_parent 或具体的尺寸值时。

例如,设置子 View 的宽度为父 View 的 100dp,子 View 必须遵循这一具体尺寸。


2. AT_MOST(最多模式)
  • 定义:父 View 给子 View 传递了一个最大尺寸,子 View 可以选择小于或等于这个尺寸。
  • 适用场景:一般用于设置为 wrap_content,子 View 根据内容大小自适应,但不能超过父 View 的最大限制。

例如,当子 View 选择包裹内容时,它会根据内容大小自适应,但不能超过父 View 给定的最大限制。


3. UNSPECIFIED(未指定模式)
  • 定义:父 View 没有给子 View 限制尺寸,子 View 可以根据自身需求决定尺寸。
  • 适用场景:一般用于需要自由尺寸的情况,例如 ListView 或 ScrollView 中的子 View。

这种模式一般在自定义控件或特定场景下使用,较少应用于常规布局。

源码解析

在自定义View的onMeasure方法中,我们可以通过MeasureSpec类来解析这三种模式。MeasureSpec包含两个主要信息:Mode(模式) 和 Size(大小)

MeasureSpec的源代码

public static final int UNSPECIFIED = 0;
public static final int EXACTLY = 1;
public static final int AT_MOST = 2;

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

//使用掩码(mask)来提取高2位。
public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);  // MODE_MASK = 0x3
}

//通过掩码去除高2位,获取低30位的值。
public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);  // ~MODE_MASK = 0xFFFFFFFC
}

测量模式存储形式

MeasureSpec是一个32位的整数(int 值 4个字节,32bit),其中包含模式和大小信息。

  • 高位(位31到位30):用于存储模式。
  • 低位(位29到位0):用于存储大小。

二进制表示

EXACTLY:0b01000000000000000000000000000000(只关心高两位)

public static final int EXACTLY     = 1 << MODE_SHIFT;

AT_MOST:0b10000000000000000000000000000000

public static final int AT_MOST     = 2 << MODE_SHIFT;

UNSPECIFIED:0b00000000000000000000000000000000

public static final int UNSPECIFIED = 0 << MODE_SHIFT;

各模式示例代码

  1. EXACTLY(确切模式)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 获取父View提供的确切宽高
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    // 使用父View提供的尺寸
    setMeasuredDimension(width, height);
}

应用场景:当父布局设置为match_parent时,子View的宽高将完全匹配父布局。

  1. AT_MOST(最多模式)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    // 处理宽度
    if (widthMode == MeasureSpec.AT_MOST) {
        // 计算子View的宽度,最大不超过widthSize
        width = Math.min(desiredWidth, widthSize);
    } else {
        width = desiredWidth; // 使用期望宽度
    }

    // 处理高度
    if (heightMode == MeasureSpec.AT_MOST) {
        // 计算子View的高度,最大不超过heightSize
        height = Math.min(desiredHeight, heightSize);
    } else {
        height = desiredHeight; // 使用期望高度
    }

    setMeasuredDimension(width, height);
}

应用场景:父布局使用wrap_content,子View可以根据内容自适应,但不会超过父布局的最大值。

  1. UNSPECIFIED(未指定模式)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 在此模式下,子View可以自由选择尺寸
    setMeasuredDimension(desiredWidth, desiredHeight);
}

应用场景:适用于需要灵活大小的场景,例如在ScrollView中,子View的尺寸可以根据内容进行扩展。

6. 安卓自定义View刷新调用顺序图

    ┌────────────────────┐
    │    调用invalidate() │
    └────────────────────┘
              ↓
    ┌────────────────────────────┐
    │  调用View.invalidate()     │
    └────────────────────────────┘
              ↓
   ┌──────────────────────────────┐
   │   调用ViewParent.invalidate() │ (若有父视图,向上请求刷新)
   └──────────────────────────────┘
              ↓
   ┌──────────────────────────────┐
   │   调用请求重绘机制:UI线程刷新 │
   └──────────────────────────────┘
              ↓
     ┌────────────────────────┐
     │ 调用requestLayout()    │  (如果布局变化,调用此方法会触发onMeasure)
     └────────────────────────┘
              ↓
    ┌────────────────────────────┐
    │  调用onMeasure()           │
    └────────────────────────────┘
              ↓
    ┌────────────────────────────┐
    │   调用setMeasuredDimension │  (设置最终宽高)
    └────────────────────────────┘
              ↓
     ┌────────────────────────┐
     │ 调用onLayout()         │ (进行视图的布局)
     └────────────────────────┘
              ↓
   ┌──────────────────────────────┐
   │ 调用onDraw()                 │  (进行绘制操作)
   └──────────────────────────────┘
              ↓
   ┌──────────────────────────────┐
   │    更新显示,重新渲染视图      │
   └──────────────────────────────┘

简单来说就是三步走,onMeasure,onLayout,onDraw,其中onLayout一般情况下,普通视图不需要重写此方法,除非视图具有子视图并需要自己进行布局。比如你如果想自定义一个某东搜索框下面的历史搜索记录布局的时候,就必须重写onLayout了。

总结

方法作用何时重写
onMeasure()测量视图的大小当视图的尺寸依赖于父视图的MeasureSpec或动态计算时
onLayout()布局子视图的位置当视图是布局容器或需要动态布局子视图时
onDraw()绘制视图的内容当需要自定义视图内容的绘制时,几乎所有自定义视图都需要重写

7. 最后


基础只是理论概念,要想牢记,还得在实战中运用,当然上面都会了,就能和面试官吹牛逼了。再会!

另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用:wolai

_ 蓝 橙
Android自定义View系列之详解View的绘制流程 一、开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout、FrameLayout、RelativeLayout...都是系统定义实现的布局,我们想要自定义一个FlowLayout流式布局实现我们自己要的效果(自定义不就是按照自己想要的效果实现的一种布局),流式布局目前Google官方还没帮我们提供,但网上有很多自定义的流式布局,也都是开发者们自己继承ViewGroup实现的,今天以自定义ViewGroup实现流式布局FlowLayout为大前提讲解View的绘. 阅读详情

相关推荐

《重学安卓》两周年,kotlin协程处理并发

但十分令人遗憾的是,本是从 “实事求是” 出发概括的术语,却频繁被人拿去 “点缀文章” 和 “挂羊头卖狗肉”,这些滥用行为,无形中扭曲和破坏了术语 “实事求是” 的形象, 所以过去两年里,我们积极地举报 “培训机构的洗稿软文”,以及在相关文章的评论区补充参考文献来源, 非常感谢小伙伴们的主动反馈。 本质概括一览 基于深度思考,我们确立下来并广泛传播的 “本质概括” 包括但不限于: Jetpack 架构组件本质: Lifecycle 的本质是解决 “生命周期管理” 的一致性问题 LiveData 的本质是解决

m0_64383736的博客 1772

在 Android 中,自定义 View 的绘制流程

目录1. 测量阶段 (onMeasure())2. 布局阶段 (onLayout())3. 绘制阶段 (onDraw())总体绘制流程注意事项示例总结参考资料在 Android 中,自定义 View 的绘制流程主要包括测量、布局、绘制三个关键步骤。具体来说,自定义 View 的绘制涉及重写系统的 、 和 方法。下面详细介绍这个流程: 方法用于确定 View 的尺寸。系统会调用该方法来让自定义 View 计算其宽度和高度。你可以根据父布局给定的测量模式和尺寸对 View 进行自适应处理。示例: 2. 布

LLZjiayou的博客 2025

推翻自己和过往,重学自定义View

关于自定义View以前看了很多资料看,从博客园到优快云,从stackoverflow到EOE论坛,从百草园到三味书屋,搜了一大筐,沮丧的发现这些文章大同小异:只举个简单的例子,很少研究为什么;人云亦云,文章里的内容根本没有去验证和深究就发出来了;或者避重就轻展示简单的Demo直接把难点给绕过去了;文章零零散散不成体系……每次看完这些文章觉得还是晕头转向,稀里糊涂,原本就不多的学习动力和勇气也消失殆尽了。

谷哥的小弟 4万+

【Android】之【View绘制】

Android Activity加载、View绘制流程解析。

Crazy程序猿 4710

告别云端推理!用llama.cpp+树莓派5搭建本地AI问答机器人(完整配置流程)

本文详细介绍了如何在树莓派5上利用llama.cpp部署本地AI问答机器人,实现完全离线的智能对话。通过模型量化与格式转换技术,成功将MiniCPM-2B等大模型压缩并运行于资源受限的边缘设备,解决了隐私、延迟和成本问题,为物联网和硬件开发者提供了完整的配置流程与实践指南。

t1u2v的博客 507

构建高可用CephFS NFS网关:NFS-Ganesha与RADOS集群的深度整合

本文详细介绍了如何构建高可用的CephFS NFS网关,通过NFS-Ganesha与RADOS集群的深度整合,实现高效文件共享。文章涵盖核心架构、部署实战、性能调优及避坑指南,帮助用户解决Ceph客户端兼容性问题,提升存储访问效率。特别强调高可用设计,确保服务稳定性,适用于金融、电商等高要求场景。

weixin_26976635的博客 254

重学 Android 自定义 View 系列(十):带指针的渐变环形进度条

前言该篇文章根据前面 重学 Android 自定义 View 系列(六):环形进度条 拓展而来。最终效果如下:1. 扩展功能支持进度顺时针或逆时针显示在进度条末尾添加自定义指针图片使用线性渐变为进度条添加颜色效果2. 关键技术点解析2.1 进度方向控制的实现通过添加一个 direction 属性,设置角度的正负,决定进度...

cnzzs的博客 1134

AB脉冲详解(编码器,PLC,上位机)

编码器线有些是用不到的。Z脉冲:360度中,只有1度是输出,输出能力是。编码器实际就是发光二极管,所以电流超过20ma会缩短寿命或损坏。

cfqq1989的博客 1万+

重学 Android 自定义 View 系列:动手实现专属 TextView

前面一篇介绍了自定义View的基础概念(皮毛),接下来全部是自定义View实战,让我们一起开启自定义View之旅吧!在中,我们定义了三个自定义属性:xText(文本内容)、xTextColor(文本颜色)、xTextSize(文本大小)。

WriteBug001的博客 1365

Nengo神经工程框架:脉冲神经网络的实时编译与硬件部署

脉冲神经网络(SNN)作为类脑计算的核心范式,正从生物模拟走向工程落地。其本质是用离散脉冲建模神经动力学,关键在于实现可预测、可复现的神经行为而非单纯拟合函数。Nengo基于神经工程学框架(NEF),将动态系统理论与硬件约束深度融合,通过解析式权重计算、确定性时间步长(dt)和多后端编译能力,支撑从CPU仿真到Loihi/FPGA的端到端部署。它不替代深度学习框架,而是提供面向神经形态芯片的‘神经编译器’能力,使LIF神经元参数、突触动力学与硬件时序严格对齐,显著提升实时性、能效比与物理可解释性。

weixin_32533659的博客 636

微信数据库读取解密脚本 聊天记录查看工具,支持所有微信版本,后期将支持企业微信[Windows]

(1)获取微信昵称、微信账号、微信手机号、微信邮箱、微信KEY的基址偏移(2)获取微信的微信昵称、微信账号、微信手机号、微信邮箱、微信KEY、微信原始ID(wxid_******)(3)获取微信文件夹路径(4)支持查看聊天记录查看(5)根据key解密微信数据库(6)提供数据库部分字段说明(7)支持微信多开场景,获取多用户信息等(8)微信需要登录状态才能获取数据库密钥(9)支持导出聊天记录为html,备份微信聊天记录,方便查看(10)合并多个数据库,方便查看。

weixin_43101935的博客 1万+

重学 Android 自定义 View 系列(四):文字变色

定义两种颜色:文本的原始颜色,和改变的颜色。这些属性可以通过

WriteBug001的博客 1388

超详细!思岚S2激光雷达hector_mapping建图

使用思岚S2激光雷达,在ros平台上利用hector_mapping功能包实现建图功能

m0_50965539的博客 5591

重学 Android 自定义 View 系列(五):文字变色的应用

上一篇介绍了文字变色的原理,本篇文章就简单介绍下其应用。原理都掌握了,应用还不是手拿把掐😁重学 Android 自定义 View 系列(四):文字变色结束,睡觉!wolai。

WriteBug001的博客 520

C#使用OpenCvSharp+微信模型识别多个二维码

本文介绍了一个基于OpenCvSharp和微信开源二维码检测器的多二维码识别项目。该项目通过C# WinForm实现,主要功能包括:加载图片、识别图片中的多个二维码并显示识别结果。核心代码使用了OpenCvSharp.WeChatQRCode类,通过预训练的caffe模型(detect.prototxt/detect.caffemodel和sr.prototxt/sr.caffemodel)进行二维码检测和解码。识别结果包括每个二维码的编号、内容及位置信息。项目适用于批量二维码识别场景,部署简单,识别效果良

遇见--专栏 123

微信小程序 - 解决支付报错 requestPayment:fail access denied (微信支付提示错误requestPayment:拒绝访问失败,真机运行测试或打包编译上线后报错问题)

微信小程序,微信支付,wx.requestPayment,微信小程序支付报错requestPayment:fail access denied,调不起微信,无法唤起支付弹窗,支付失败,支付权限没问题,小程序上线后支付报错,小程序真机测试支付报错,微信小程序苹果ios系统手机无法支付,小程序发布上线后支付报错,本地测试没问题,拒绝访问失败,小程序调起支付API requestPayment:fail:access denied微信开发者工具正常支付,权限没问题,微信公众平台,依旧报错怎么办,检查了appid都

🏆 前端领域优质创作者 1万+

重学 Android 自定义 View 系列(三):自定义步数进度条

前言本篇文章主要是实现仿QQ步数View,很老的一个View了,但技术永不落后,开搂!最终效果如下:1. 结构分析QQStepView 主要由三个元素组成:显示一个圆环进度条,通过外环和内环的角度变化来表示进度。支持自定义外环颜色、内环颜色、进度文本颜色和文本大小,这些都可以通过 XML 属性来配置。支持动态更新进度,通过...

cnzzs的博客 982

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

重学 Android 自定义 View 系列(六):环形进度条

- 进度条的最大值 --> < attr name = " maxProgress " format = " integer " /> < attr name = " progress " format = " integer " /> < attr name = " circleBackgroundColor " format = " color " />

WriteBug001的博客 2040

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

重学 Android 自定义 View 系列(七):仿58同城加载动画

本文将实现早期58同城一个带有弹跳效果的加载动画,且结合图形变换(圆形变正方形、正方形变三角形等)实现一种动态、富有表现力的加载效果。一个很有意思的自定义View,在这篇文章中你可以学到到路径使用,安卓动画组合使用等知识。再会!

WriteBug001的博客 1105

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

重学 Android 自定义 View 系列(十一):文字跑马灯剖析

一个可以横向滚动和纵向滚动的自定义文字跑马灯View,支持水平和垂直滚动、多段文本展示、点击事件回调等功能。该View 由是基于的自定义视图,功能包括:1.1水平滚动文本从右向左滚动,支持多段文本循环展示。每段文本滚动结束后可以设置停留时间。1.2垂直滚动文本从下向上滚动,支持多段文本循环展示。如果文本长度超过视图宽度,会自动触发水平滚动,直到文本完全显示。1.3点击事件支持点击暂停/恢复滚动。提供点击事件回调,返回当前滚动文本的下标和内容。1.4自定义属性。

WriteBug001的博客 1410

重学 Android 自定义 View 系列(十二):环形SeekBar剖析

一个自定义的圆形 SeekBar,类似于传统的 SeekBar 但采用了圆形轨迹。最近被一个网友私信问有没有类似效果的View,因为前面做过几个环形进度条,这个不就加个触摸效果么,以为不算很难,但深入了解后,才发现事情并没有那么简单…你需要具备的知识:三角函数正弦余弦计算、反三角函数、角度弧度区别…该View 由绘制圆形轨道(进度条):支持背景轨道和进度轨道两层绘制和背景圆环缓存。支持触摸交互:用户可以通过手指拖动控制进度。自定义进度范围:允许设置最小值、最大值以及当前进度。进度变化监听。

WriteBug001的博客 949

重学Android——View的事件分发

本文源码Api28 View的事件分发 我们都知道,点击事件最重要的三个方法 //用来进行事件分发 public boolean dispatchTouchEvent(MotionEvent ev); //判断是否拦截某个事件 public boolean onInterceptTouchEvent(MotionEvent ev); //处理touch事件 public boolean onT...

ApkCore的专栏 277
上一篇: 重学Android:从位运算到二进制表示(零)
下一篇: 重学 Android 自定义 View 系列:动手实现专属 TextView
_蓝橙
博客等级 码龄7年 1179粉丝 174原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值