Android探索之路(四)—View的使用
前言
在这篇文章之前已经总结学习了View的工作流程、事件分发机制。这里总结一下在工作过程中使用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方法
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的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 @Overridepublic 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的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的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线程。
更多相关文章
- Google 菜市场(Android Market)上不去的解决方法
- Android高手进阶教程(二十一)之---Android中创建与几种解析xml的
- Android 应用程序查找设备的方法——以串口为例
- android中使用httpclient方法获得网页内容并对json对象解析
- Android Studio3.0开发JNI流程------在Android原程序添加自己类
- 利用Android的Matrix类实现J2ME的drawRegion的镜像方法