少年意气强不羁,虎胁插翼白日飞。这篇文章主要讲述Android requestLayout 和 invalidata , postInvalidate 比较相关的知识,希望能为你提供帮助。
android 中的View更新方法
invalidate
在UI线程中使用。postInvalidate
在非UI线程中通知重绘。
- 【Android requestLayout 和 invalidata , postInvalidate 比较】View 确定自身已经不适合现有区域时,调用
requestLayout()
,通知父View重新测量和绘制此View的位置。
当View的LayoutParams发生改变时,也应该调用这个方法。
当调用requestLayout 时,会逐层向上进行传递,直到ViewRootImpl进行处理,如果子view调用了这个方法,会通知View树重新进行一次测量,布局,绘制的过程.
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* <
p>
Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.<
/p>
*/
//从源码注释可以看出,如果当前View在请求布局的时候,View树正在进行布局流程的话,
//该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行。@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null &
&
mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null &
&
viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}//为当前view设置标记位 PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null &
&
!mParent.isLayoutRequested()) {
//向父容器请求布局
mParent.requestLayout();
}
if (mAttachInfo != null &
&
mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
mParent.requestLayout
方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout
方法,为父容器添加PFLAG_FORCE_LAYOUT
标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView
,即根View,而根View又会传递给ViewRootImpl
,也即是说子View的requestLayou
t事件,最终会被ViewRootImpl
接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl
能够处理requestLayout
事件。在ViewRootImpl中,重写了
RequestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// view的核心工作方法
// 内部会分别调用View的三大工作流程
scheduleTraversals();
}
}
View的测量流程View# measure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
// 根据标志位进行判断,如果有PFLAG_FORCE_LAYOUT, 进行测量
if ((mPrivateFlags &
PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
//...
if (cacheIndex <
0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &
= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
// 置标志位
}
}
public void layout(int l, int t, int r, int b) {
...
//判断标记位是否为PFLAG_LAYOUT_REQUIRED,如果有,则对该View进行布局
if (changed || (mPrivateFlags &
PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
//onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED标记位
mPrivateFlags &
= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null &
&
li.mOnLayoutChangeListeners != null) {
ArrayList<
OnLayoutChangeListener>
listenersCopy =
(ArrayList<
OnLayoutChangeListener>
)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0;
i <
numListeners;
++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}//最后清除PFLAG_FORCE_LAYOUT标记位
mPrivateFlags &
= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
小结:子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。
invalidate方法详解
该方法会引起View树的重绘,通常在内部调用或者刷新界面时进行调用
View#invalidate()
public void invalidate() {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
// 最终会调用此方法
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}//这里判断该子View是否可见或者是否处于动画中
if (skipInvalidate()) {
return;
}//根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
if ((mPrivateFlags &
(PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache &
&
(mPrivateFlags &
PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags &
PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate &
&
isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &
= ~PFLAG_DRAWN;
}//设置PFLAG_DIRTY标记位
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
// 置标志位,为不加载绘图缓存
mPrivateFlags &
= ~PFLAG_DRAWING_CACHE_VALID;
}// Propagate the damage rectangle to the parent view.
//把需要重绘的区域传递给父容器
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null &
&
ai != null &
&
l <
r &
&
t <
b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//调用父容器的方法,向上传递事件
p.invalidateChild(this, damage);
}
...
}
}
可以看到,在该方法内部,先设置当前视图的标记位,接着有一个do…while…循环,该循环的作用主要是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty)。当父容器不是ViewRootImpl的时候,调用的是ViewGroup的
invalidateChildInParent
方法ViewGroup#invalidateChildInParant()
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags &
PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags &
PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags &
(FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {//将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags &
FLAG_CLIP_CHILDREN) == 0) {
//求并集,结果是把子视图的dirty区域转化为父容器的dirty区域
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}final int left = mLeft;
final int top = mTop;
if ((mGroupFlags &
FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
mPrivateFlags &
= ~PFLAG_DRAWING_CACHE_VALID;
//记录当前视图的mLeft和mTop值,在下一次循环中会把当前值再向父容器的坐标转化
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
//返回当前视图的父容器
return mParent;
}
...
}
return null;
}
- 1
回到上面所说的do…while…循环,由于不断向上调用父容器的方法,到最后会调用到ViewRootImpl的invalidateChildInParent方法
ViewRootImpl#invalidateChildInParent:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() &
&
!mIsAnimating) {
return null;
}if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}final Rect localDirty = mDirty;
if (!localDirty.isEmpty() &
&
!localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon &
&
(intersected || mIsAnimating)) {
scheduleTraversals();
}
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
好了,现在总结一下invalidate方法,当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
postInvalidate()
public void postInvalidate() {
postInvalidateDelayed(0);
}public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there‘s no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
- 1
- 2
- 3
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
这里用了Handler,发送了一个异步消息到主线程,显然这里发送的是MSG_INVALIDATE,即通知主线程刷新视图,具体的实现逻辑我们可以看看该mHandler的实现:
final ViewRootHandler mHandler = new ViewRootHandler();
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
....
}@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
...
}
}
}
可以看出,参数message传递过来的正是View视图的实例,然后直接调用了invalidate方法,然后继续invalidate流程。
推荐阅读
- android Service中多线程交互
- Android 获取虚拟按键的高度
- Android studio 报错 gradel project sync failed Error:Cause: peer not authenticated
- Android 获取Activity当前view
- Android面试过程描写叙述
- 设计模式 - 模板方法模式(template method pattern) Applet 具体解释
- Android 关于软键盘
- Memo1.Lines.Add(s) 与 Memo1.Lines.Append(s) 的区别是什么?
- 如何正确的对安卓手机进行数据恢复()