Android View绘制流程

一、基础版回答

核心三阶段:

  1. Measure(测量)

    • 自顶向下递归测量所有View的宽高

    • 关键方法:onMeasure() → setMeasuredDimension()

    • 注意:MeasureSpec的三种模式(EXACTLY/AT_MOST/UNSPECIFIED)

  2. Layout(布局)

    • 根据测量结果确定View的最终位置

    • 关键方法:onLayout()

    • 父View通过layout()确定子View的四个顶点坐标

  3. Draw(绘制)

    • 将View绘制到屏幕上

    • 关键方法:onDraw()

    • 绘制顺序:背景 → 自身内容 → 子View → 装饰(如滚动条)

示例回答:
"View绘制就像装修房子:先量尺寸(Measure),再摆家具(Layout),最后粉刷墙面(Draw)。父View负责协调子View的测量和布局,而onDraw()是每个View自己完成的。"

二、进阶版回答

六个技术要点:

  1. 硬件加速原理

    • 通过RenderThreadRenderNode将绘制指令转为OpenGL/D3D调用

    • 使用DisplayList记录绘制命令,避免重复执行onDraw

  2. 性能优化关键点

    java

    复制

    // 避免触发不必要的绘制
    view.setWillNotDraw(true);  // 默认无自定义绘制时设置
    canvas.clipRect();         // 限制绘制区域
  3. 层级优化策略

    • 使用merge标签减少布局层级

    • 通过ViewStub延迟加载复杂布局

    • ConstraintLayout替代多层嵌套

  4. 自定义View实践

    kotlin

    复制

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 处理wrap_content默认行为问题
        val minWidth = resolveSize(minWidth, widthMeasureSpec)
        setMeasuredDimension(minWidth, calculateHeight())
    }
  5. 屏幕刷新机制

    • Choreographer协调VSYNC信号与绘制流程

    • Invalidate()触发重绘的线程安全机制

  6. Compose对比

    传统View体系Jetpack Compose
    命令式绘制声明式重组
    基于View树遍历基于状态快照差异
    手动优化绘制区域自动跳过未变化内容

自定义View的注意点

1. 测量阶段注意事项

  • 正确处理wrap_content

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 处理wrap_content情况
        int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST ? 
            defaultWidth : MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST ? 
            defaultHeight : MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
  • 考虑padding

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        
        // 考虑padding
        width = getPaddingLeft() + getPaddingRight() + contentWidth;
        height = getPaddingTop() + getPaddingBottom() + contentHeight;
        
        setMeasuredDimension(width, height);
    }

2. 布局阶段注意事项

  • 处理子View的位置(针对ViewGroup):

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            // 计算子View的位置并布局
            child.layout(childLeft, childTop, childRight, childBottom);
        }
    }

3. 绘制阶段注意事项

  • 优化绘制性能

    @Override
    protected void onDraw(Canvas canvas) {
        // 避免在onDraw中分配对象,会导致频繁GC
        // 使用成员变量替代局部变量
        paint.setColor(mColor);
        canvas.drawRect(rect, paint);
    }
  • 正确处理Canvas变换

    @Override
    protected void onDraw(Canvas canvas) {
        int saveCount = canvas.save();
        // 进行Canvas变换
        canvas.rotate(degrees, pivotX, pivotY);
        // 绘制内容
        canvas.drawRect(rect, paint);
        // 恢复Canvas状态
        canvas.restoreToCount(saveCount);
    }

4. 其他重要注意事项

  • 处理触摸事件

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 处理按下事件
                return true;
            case MotionEvent.ACTION_MOVE:
                // 处理移动事件
                invalidate(); // 请求重绘
                return true;
            case MotionEvent.ACTION_UP:
                // 处理抬起事件
                return true;
        }
        return super.onTouchEvent(event);
    }
  • 支持自定义属性

    1. 在res/values/attrs.xml中定义属性:

      <declare-styleable name="CustomView">
          <attr name="customColor" format="color"/>
          <attr name="customSize" format="dimension"/>
      </declare-styleable>
    2. 在构造方法中读取属性:

      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
      mColor = a.getColor(R.styleable.CustomView_customColor, Color.BLACK);
      mSize = a.getDimension(R.styleable.CustomView_customSize, 10);
      a.recycle();
  • 内存泄漏预防

    • 避免在View中持有Activity的长期引用

    • 及时取消Handler、动画等可能造成泄漏的操作

  • 性能优化

    • 使用View.isInEditMode()处理IDE预览时的特殊逻辑

    • 对于复杂View,考虑使用setWillNotDraw(true)优化

    • 合理使用clipRect()限制绘制区域

      自定义View的最佳实践

    • 优先考虑组合现有View而非完全自定义

    • 合理使用View的缓存机制(如Bitmap缓存)

    • 避免过度绘制:使用canvas.clipRect()限制绘制区域

    • 考虑硬件加速:了解不同API级别硬件加速的限制

    • 正确处理生命周期:在适当时机释放资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值