android 自定义view 详解(二)

一、View 绘制流程

1. View 的绘制流程是什么?measure/layout/draw 分别做什么?

  • measure:测量 View 的宽高,从根 View 递归调用 measure()onMeasure(),结果存在 mMeasuredWidth/Height

  • layout:确定 View 的位置,调用 layout()onLayout(),子 View 一般在这里调用 child.layout()

  • draw:绘制内容,调用 draw() → 依次执行 drawBackgroundonDrawdispatchDrawonDrawForeground

2. MeasureSpec 是什么?三种模式有什么区别?

模式含义
EXACTLY1精确值,如 match_parent 或具体数值
AT_MOST2最大值,如 wrap_content
UNSPECIFIED0无限制,如 ScrollView 中的子 View

3. 为什么 onMeasure 会执行多次?

  • 父 View 可能多次测量子 View 以确定最终尺寸

  • 典型的如 RelativeLayoutLinearLayout 的权重分配

4. getWidth() 和 getMeasuredWidth() 的区别?

  • getMeasuredWidth():measure 阶段获取的原始测量值

  • getWidth():layout 阶段确定的最终宽度(mRight - mLeft

  • 通常两者相等,但在某些自定义布局中可能不同


二、自定义 View 的分类与选择

5. 自定义 View 有哪些实现方式?如何选择?

方式适用场景特点
继承 View完全自定义绘制重写 onDraw,无子 View
继承 ViewGroup自定义布局重写 onLayout,管理子 View 位置
继承已有 View扩展功能如继承 TextView 增加新属性
继承已有 ViewGroup组合控件如继承 FrameLayout 封装复杂 UI

6. 自定义 View 如何支持 wrap_content

  • 必须在 onMeasure 中处理 AT_MOST 模式,否则 wrap_content 效果等同于 match_parent

  • 示例:

java

if (widthMode == MeasureSpec.AT_MOST) {
    widthSize = desiredWidth;
}

三、触摸事件分发(高频考点)

7. 事件分发的三个核心方法?

  • dispatchTouchEvent:分发事件,返回 true 表示消费

  • onInterceptTouchEvent:ViewGroup 专属,是否拦截事件

  • onTouchEvent:处理事件

8. 事件传递顺序?

plain

Activity.dispatchTouchEvent
  → PhoneWindow.DecorView.dispatchTouchEvent
    → ViewGroup.dispatchTouchEvent
      → onInterceptTouchEvent(返回 true 则拦截,调用自己的 onTouchEvent)
      → 不拦截则传给子 View
        → 子 View 的 dispatchTouchEvent → onTouchEvent

9. 如果子 View 不消费事件,事件会怎么处理?

  • 子 View 的 onTouchEvent 返回 false → 事件回传给父 View 的 onTouchEvent

  • 如果所有 View 都不消费,最终回传给 Activity 的 onTouchEvent

10. requestDisallowInterceptTouchEvent 的作用?

  • 子 View 调用此方法可以阻止父 View 拦截事件

  • 常用于解决 ScrollView 嵌套 RecyclerView 的滑动冲突


四、自定义绘制

11. onDraw 中常用的 Canvas API 有哪些?

  • 绘制基础图形:drawLinedrawRectdrawCircledrawOvaldrawArc

  • 绘制文字:drawText

  • 绘制路径:drawPath

  • 绘制图片:drawBitmap

12. Paint 常用属性?

  • setColor/setARGB:颜色

  • setStrokeWidth:描边宽度

  • setStyleFILL(填充)、STROKE(描边)、FILL_AND_STROKE

  • setAntiAlias:抗锯齿

  • setTextSize:文字大小

13. Path 的使用场景?

  • 绘制贝塞尔曲线、复杂形状

  • moveTolineToquadTo(二阶贝塞尔)、cubicTo(三阶贝塞尔)

  • addArcaddCircle 等添加基本图形

14. Canvas.save() 和 Canvas.restore() 的作用?

  • save():保存当前 Canvas 状态(坐标系、裁剪区域等)

  • restore():恢复到上一次 save 的状态

  • 常用于临时变换坐标系后恢复


五、动画

15. Android 动画的分类?

类型实现方式特点
补间动画(View Animation)AlphaAnimationTranslateAnimation只改变视觉效果,不改变实际属性
属性动画(Property Animation)ValueAnimatorObjectAnimator真正改变属性值,更灵活
帧动画(Drawable Animation)AnimationDrawable逐帧播放图片

16. 属性动画的原理?

  • 通过 ValueAnimator 在时间内计算插值

  • 利用反射或 setter 方法不断更新目标属性

  • 底层依赖 ChoreographerVSync 信号驱动

17. 自定义 View 中如何实现属性动画?

  • 定义属性对应的 setter 方法

  • 使用 ObjectAnimator.ofFloat(view, "propertyName", values...)

  • 或者使用 ValueAnimator + AnimatorUpdateListener 手动更新


六、性能优化

18. 自定义 View 有哪些性能优化手段?

  • 避免在 onDraw 中创建对象:应提前初始化 Paint、Path 等

  • 使用 clipRectquickReject:减少不必要的绘制区域

  • 开启硬件加速setLayerType(View.LAYER_TYPE_HARDWARE, null)

  • 使用 invalidate(Rect):局部刷新而非全屏刷新

  • 避免过度绘制:减少重叠区域的绘制次数

19. 什么是双缓冲?自定义 View 中如何利用?

  • 双缓冲:在内存中先绘制到 Bitmap,再一次性绘制到屏幕

  • 自定义 View 中可通过 setLayerType 或手动使用离屏 Bitmap 实现

20. invalidate() 和 requestLayout() 的区别?

  • invalidate():触发重绘,调用 onDraw,不改变尺寸

  • requestLayout():触发重新测量和布局,调用 onMeasureonLayout


七、其他细节问题

21. 自定义属性(attrs)如何定义和使用?

  • res/values/attrs.xml 中定义 <declare-styleable>

  • 在构造方法中通过 TypedArray 获取属性值

  • 记得 typedArray.recycle() 回收

22. 自定义 View 的三个构造方法分别什么时候调用?

  • 一参:代码中 new View(context)

  • 二参:XML 中使用

  • 三参:XML 中使用 + 指定 style

  • 四参(API 21+):XML 中使用 + 指定 theme attribute

23. postInvalidate() 和 invalidate() 的区别?

  • invalidate():必须在 UI 线程调用

  • postInvalidate():内部通过 Handler 投递到 UI 线程,可在子线程调用

24. 如何解决自定义 View 的内存泄漏?

  • 及时移除动画监听和 Handler 回调

  • 避免在 onDraw 中创建大量临时对象

  • 页面销毁时停止动画


八、实战场景题

25. 如何实现一个圆形进度条?

  • 思路:继承 View,在 onDraw 中用 drawArc 绘制背景圆弧和进度圆弧

  • 通过属性动画更新进度值,调用 invalidate 重绘

26. 如何实现一个可拖拽的 View?

  • onTouchEvent 中处理 ACTION_DOWNACTION_MOVEACTION_UP

  • ACTION_MOVE 时计算偏移量,调用 layout()offsetLeftAndRight() 更新位置

  • 或使用 ViewDragHelper(更推荐)

27. 如何实现一个折线图/柱状图?

  • 计算坐标映射关系

  • 使用 Path 连接数据点

  • 考虑边缘 padding、动画过渡效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android-王宁

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值