在这篇文章之前已经总结学习了View的工作流程、事件分发机制。这里总结一下在工作过程中使用View的一些感想,主要从常用的View中的一些方法以及View的一些基础知识两方面来进行介绍。
在Android系统中存在着坐标系用来确定位置。分为两种:一种是Android坐标系(是整个Android设备的坐标系),还有一种是View的坐标系(是一个视图的坐标系)。这两种坐标系都是以左上角为原点,原点向右是X正半轴,原点向下是Y正半轴。
如图中所示,我们可以看到获取各个位置的方法。其中getX()、getY()、getRawX()、getRawY()是MotionEvent中的方法,用来获取点击事件的位置信息。getTop()、getLeft()、getRight()、getBottom()是View中的方法,用来获取View在其父容器中的位置信息。
getX():获取点击事件到View左边的距离。
getY():获取点击事件到View上边的距离。
getRawX():获取点击事件距离整个屏幕左边的距离,绝对坐标。
getRawY():获取点击事件距离整个屏幕上边的距离,绝对坐标。
getTop():获取View顶部到其父容器顶部的距离。
getLeft():获取View左边到其父容器左边的距离。
getRight():获取View右边到其父容器右边的距离。
getBottom():获取View底部到其父容器顶部部的距离。
View中post方法的作用是将一个Runnable加入到UI线程的消息队列中。
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
//已经调用过onAttachedToWindow方法才将Runnable加入到UI线程的消息队列中
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
从代码中可以看出,只有View已经attach到Window时才将Runnable加入到UI线程的消息队列中。View的attach window过程发生在View绘制之前。在ViewRootImpl中performTraversals()方法中,在View绘制前调用了View的dispatchAttachToWindow方法。这个方法中设置View中的mAttachInfo,并且会执行没有attach window时Runnable加入的队列。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info; //赋值mAttachInfo
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
//......
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// Transfer all pending runnables.
//这里执行了没有attach window时加入到的队列中
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
//调用onAttachedToWindow方法
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
onVisibilityAggregated(vis == VISIBLE);
}
}
}
View的invalidate方法的作用是使整个View无效,然后调用View的onDraw方法,引起View的重绘。
public void invalidate() {
invalidate(true);
}
public 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) {
//......
//判断是否跳过,如果View在执行动画或者不可见则跳过
if (skipInvalidate()) {
return;
}
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; //这里只添加了绘制标志位
}
mPrivateFlags |= PFLAG_DIRTY; //设置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); //调用父容器的方法,传递invalidate事件
}
//......
}
}
View的invalidate方法有多个重载方法,最终会调用invalidateInternal方法。在此方法中会执行一系列的操作。先判断View是否在执行动画或者不可见,如果是则跳过。然后添加绘制标志位,这说明invalidate方法只会引起View的onDraw,不会发起measure以及layout。最后获取父容器,调用父容器的invalidateChild方法。
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
* draw state in descendants.
*/
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
//该方法已被启用,如果开启了硬件加速,就调用onDescendantInvalidated
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
if (attachInfo != null) {
//判断子View是否在进行动画
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
// be invalidating their old position and need the parent to paint behind them.
Matrix childMatrix = child.getMatrix();
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
//......
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
parent = parent.invalidateChildInParent(location, dirty); //最终parent = ViewRootImpl
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
//确定脏区域
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
invalidateChild方法最终会回溯到ViewRootImpl中。
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
// 直接调用了 invalidate 方法
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);
}
}
// 调用了 invalidateRectOnScreen 方法, 刷新区域内的视图
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
//脏区域的并集
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals(); //这个方法出发View的工作流程
}
}
从本质上来讲View的postInvalidate方法最终调用了View的Invalidate方法。
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidate(int left, int top, int right, int bottom) {
postInvalidateDelayed(0, left, top, right, bottom);
}
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) {
//调用ViewRootImpl的dispatchInvalidateDelayed方法
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
// 在 mHandler 绑定的线程中调用了 View 的 invalidate
((View) msg.obj).invalidate();
break;
}
View的requestLayout方法则作用是发起View的工作流程。
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
// 判断当前 ViewRootImpl 是否正在 Layout
if (viewRoot != null && viewRoot.isInLayout()) {// isInLayout() 为 true 的条件是 mInLayout = true
// 判断当前 View 是否可以在 ViewRootImpl 正在进行 Layout 时, 继续执行发起 requestLayout
if (!viewRoot.requestLayoutDuringLayout(this)) {
// 返回 false , 则不允许, 直接让此次 View.requestLayout() 返回
return;
}
}
// 将自己的状态标记为正在 requestLayout
mAttachInfo.mViewRequestingLayout = this;
}
// 给当前 View 设置标志位
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 调用父容器的 requestLayout
if (mParent != null && !mParent.isLayoutRequested()) {
// 此方法最终调用当前 Window 的 ViewRootImpl 中的 requestLayout 中去
mParent.requestLayout();
}
}
boolean requestLayoutDuringLayout(final View view) {
// 将请求 Layout 的 View 添加到 ViewRootImpl 中维护的集合 mLayoutRequesters 中
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
if (!mHandlingLayoutInLayoutRequest) {
// mHandlingLayoutInLayoutRequest 为 false
// 说明当前 ViewRootImpl 的 performLayout() 没有进行 second layout, 它将会在第二次 layout 中执行
return true;
} else {
// 说明当前 ViewRootImpl 的 performLayout() 正在进行 second layout, 此时 view 的 requestLayout 会被 post 到下一帧
// return false; 由上面代码可知 View 的 requestLayout() 请求会被直接 return;
return false;
}
}
/**
* ViewRootImpl.requestLayout
* View.requestLayout() 最终的走向
*/
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
// 这个标志使View进行measure 和 layout
mLayoutRequested = true;
// 这里开始View的工作流程
scheduleTraversals();
}
}
上面就是在使用View的过程中经常用到的内容,这个方法以及知识点可以帮助我们更加有效的使用View。
从View的位置信息中,可以获取到View的确切位置并进行操作。
View的post方法只有当View执行Attach Window时才将任务发送到UI线程。如果没有执行Attach Window,则将任务加入到一个队列管理中,这个队列在dispatchWindow时会再次调用,但不能保证正确执行。
invalidate、postInvalidate、requestLayout都会引起View的工作流程。但invalidate、postInvalidate不会进行measure和layout。
invalidate和postInvalidate本质是没有区别。postInvalidate可以用于非UI线程。