android 自定义流程图,Android进阶之自定义View原理(三)View的绘制流程

本文深入解析Android中View的绘制流程,从draw方法开始,详细阐述背景绘制、内容绘制、子View绘制及装饰元素绘制的过程。同时,介绍了ViewGroup的draw流程,特别是如何遍历并绘制子View。此外,还补充了View的重绘机制,包括invalidate()和requestLayout()方法的作用及其使用场景。理解这些概念对于优化UI性能至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

前面我们讲到自定义View的测量和布局原理,并举例说明了这两个知识点的具体应用,本篇我们继续从源码入手看看View的绘制流程,与测量和布局流程,View的绘制过程要简单一些,主要流程如下:

a849bdf91a8e

View绘制流程图.png

(一)View的draw流程源码分析:

/**

* 作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)。

* 绘制过程:

* 1. 绘制view背景

* 2. 绘制view内容

* 3. 绘制子View

* 4. 绘制装饰(渐变框,滑动条等等)

* 注:

* a. 在调用该方法之前必须要完成 measure和layout 过程

* b. 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)

* c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法

*/

public void draw(Canvas canvas) {

...

// 步骤1: 绘制本身View背景

if (!dirtyOpaque) {

drawBackground(canvas);

}

// 若有必要,则保存图层(还有一个复原图层)

// 优化技巧:当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过,因此在绘制时,节省 layer 可以提高绘制效率

final int viewFlags = mViewFlags;

if (!verticalEdges && !horizontalEdges) {

// 步骤2:绘制本身View内容

if (!dirtyOpaque)

onDraw(canvas);

// View 中:默认为空实现,需复写

// 步骤3:绘制子View

// 由于单一View无子View,故View 中:默认为空实现

// ViewGroup中:系统已经复写好对其子视图进行绘制我们不需要复写

dispatchDraw(canvas);

// 步骤4:绘制装饰,如滑动条、前景色等等

onDrawScrollBars(canvas);

return;

}

...

}

1.绘制背景:

private void drawBackground(Canvas canvas) {

// 拿到背景 drawable

final Drawable background = mBackground;

if (background == null) {

return;

}

// 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界

setBackgroundBounds();

.....

// 获取 mScrollX 和 mScrollY值,即偏移量

final int scrollX = mScrollX;

final int scrollY = mScrollY;

if ((scrollX | scrollY) == 0) {//按位或运算相当于与运算

background.draw(canvas);

} else {

// 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移

canvas.translate(scrollX, scrollY);

// 调用 Drawable 的 draw 方法绘制背景

background.draw(canvas);

canvas.translate(-scrollX, -scrollY);

}

}

2.onDraw()方法是空方法,需要在子类中覆写绘制自己的内容。

由于View没有子View,所以dispatchDraw是空实现.

4.绘制装饰器,如前置景、滚动条等。

public void onDrawForeground(Canvas canvas) {

onDrawScrollIndicators(canvas);

onDrawScrollBars(canvas);

final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;

if (foreground != null) {

if (mForegroundInfo.mBoundsChanged) {

mForegroundInfo.mBoundsChanged = false;

final Rect selfBounds = mForegroundInfo.mSelfBounds;

final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

if (mForegroundInfo.mInsidePadding) {

selfBounds.set(0, 0, getWidth(), getHeight());

} else {

selfBounds.set(getPaddingLeft(), getPaddingTop(),

getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());

}

final int ld = getLayoutDirection();

Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),

foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);

foreground.setBounds(overlayBounds);

}

foreground.draw(canvas);

}

}

(二)ViewGroup的draw流程分析。

ViewGroup的绘制流程与View绘制流程基本一致,不同的是,它覆写了dispatchDraw()方法:

protected void dispatchDraw(Canvas canvas) {

......

// 1. 遍历子View

final int childrenCount = mChildrenCount;

......

for (int i = 0; i < childrenCount; i++) {

......

if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||

transientChild.getAnimation() != null) {

// 2. 绘制子View视图

more |= drawChild(canvas, transientChild, drawingTime);

}

....

}

}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

// 调用子 View 的 draw()进行子View的绘制,如果是ViewGroup,就继续调用dispatchDraw()方法,这样就实现一颗View树的绘制。

return child.draw(canvas, this, drawingTime);

}

(三)细节补充:View的重绘制简单说明。

1.View有两个很重要的方法:invalidate和requestLayout,常用于View重绘和更新;

invalidate()方法会执行onDraw过程,重绘View树,注意它仅仅调用绘制流程,不影响测量和布局;

/**

*invalidate方法会执行onDraw过程,重绘View树

* Invalidate the whole view. If the view is visible,

* {@link #onDraw(android.graphics.Canvas)} will be called at some point in

* the future.

*

* This must be called from a UI thread. To call from a non-UI thread, call

* {@link #postInvalidate()}.

*/

public void invalidate() {

invalidate(true);

}

3.requestLayout()方法:当View的边界,也可以理解为View的宽高,发生了变化,可以调用requestLayout方法重新对View布局;

4.View执行requestLayout方法,会向上递归到顶级父View中,再执行这个顶级父View的requestLayout,所以其他View的onMeasure,onLayout也可能会被调用。

调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值