likes
comments
collection
share

Android面试 | ViewGroup的绘制流程

作者站长头像
站长
· 阅读数 5

ViewGroup的绘制流程

view 与 ViewGroup绘制流程基本相同,在ViewGroup中不仅要绘制自己,还需绘制其中的子控件,而view只需绘制自己。

以下是使用伪代码形式描述的ViewGroup绘制流程,简洁而清晰地展示了各个阶段的关键方法和回调,结合图片来看,你很容易就理解了这个流程

Android面试 | ViewGroup的绘制流程

class ViewGroup {
        // 测量过程
       function measure(int widthMeasureSpec, int heightMeasureSpec) {
            for (each child in this.children) {
                child.measure(
                    // 根据父布局的MeasureSpec计算出的宽度测量规格
                    resolveSizeAndState(child.getLayoutParams().width, widthMeasureSpec),
                    // 根据父布局的MeasureSpec计算出的高度测量规格
                    resolveSizeAndState(child.getLayoutParams().height, heightMeasureSpec)
                );
            }
            // 确定自己的尺寸
            setMeasuredDimension(width, height);
        }
    // 布局过程
    function layout() {
        for (each child in this.children) {
            // 确定子View的位置和大小
            child.layout(
                child.getLeft(), // 子View左边缘位置
                child.getTop(), // 子View顶边缘位置
                child.getRight(), // 子View右边缘位置
                child.getBottom() // 子View底边缘位置
            );
        }
    }

    // 绘制过程
    function draw(Canvas canvas) {
        // 绘制背景和装饰
        drawBackground(canvas);

        for (each child in this.children) {
            // 绘制子View
            child.draw(canvas);
        }
    }

    // 事件分发过程
    function dispatchTouchEvent(MotionEvent event) {
        // 决定是否拦截事件
        if (onInterceptTouchEvent(event)) {
            handleTouchEvent(event); // 处理事件
            return true;
        } else {
            for (each child in this.children) {
                // 将事件传递给子View
                if (child.dispatchTouchEvent(event)) {
                    return true;
                }
            }
        }
        return false;
    }
}
// 子View的回调方法
class View {
    function measure(int widthMeasureSpec, int heightMeasureSpec) {
        // 子View测量自己的尺寸
    }
    function layout(int left, int top, int right, int bottom) {
        // 子View确定自己在父布局中的位置
    }

    function draw(Canvas canvas) {
        // 子View绘制自己
    }

    function onInterceptTouchEvent(MotionEvent event) {
        // 子View决定是否拦截触摸事件
    }

    function handleTouchEvent(MotionEvent event) {
        // 子View处理触摸事件
}

View的几个重要回调

onMeasure() : 测量当前控件的大小,为正式布局提供建议 (仅是建议,用不用要看onLayout() 函数)

onLayout() : 使用Layout()函数为所有子控件进行布局

onDraw():根据布局的位置绘图


获取View宽高的时机

Measure过程完成,就可通过getMeasuredWidth()、getMeasuredHeight()获取测量宽高。

但某些极端情况 需要多次Measure才能确定最终宽高。所以在onLayout方法中获取测量宽高是真正OK的。 我们知道,Activity的onCreate中无法获取到View的宽高,实际onCreate、onStart、onResume都不能保证View已完成测量,所以可能获取的都是0,因为View的measure和Activity生命周期不是同步的。

以下是保证可以获取view测量宽高的方法

1、Activity/View # onWindowFocusChanged

onWindowFocusChanged:这个回调执行的时候View已初始化完毕,宽高已经确定下来显示的。 有个确定就是会被多次调用,获取焦点、失去焦点都回调用。

(这个回调是ViewRootIml中分发到DecorView,接着到Activity、到各级View。)

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int measuredWidth = scoreView.getMeasuredWidth();
            int measuredHeight = scoreView.getMeasuredHeight();
        }
    }

2、view.post(runnable)

view.post可以把runnable放入消息队列,等待looper到此runnable是view已经初始化完成。

@Override
    protected void onStart() {
        super.onStart();
        scoreView.post(new Runnable() {
            @Override
            public void run() {
                int measuredWidth = scoreView.getMeasuredWidth();
                int measuredHeight = scoreView.getMeasuredHeight();
            }
        });
    }

以后每两天之内更新一道Android常见的面试题,以自己的角度去通俗简单易懂形式去说,不深入源码但讲基本原理,以伪代码为核心;如果你比较想问什么问题,可以评论。