前言

在这篇文章之前已经总结学习了View的工作流程、事件分发机制。这里总结一下在工作过程中使用View的一些感想,主要从常用的View中的一些方法以及View的一些基础知识两方面来进行介绍。

View的位置参数

在Android系统中存在着坐标系用来确定位置。分为两种:一种是Android坐标系(是整个Android设备的坐标系),还有一种是View的坐标系(是一个视图的坐标系)。这两种坐标系都是以左上角为原点,原点向右是X正半轴,原点向下是Y正半轴。

Android探索之路(四)—View的使用_第1张图片

如图中所示,我们可以看到获取各个位置的方法。其中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线程。

更多相关文章

  1. Google 菜市场(Android Market)上不去的解决方法
  2. Android高手进阶教程(二十一)之---Android中创建与几种解析xml的
  3. Android 应用程序查找设备的方法——以串口为例
  4. android中使用httpclient方法获得网页内容并对json对象解析
  5. Android Studio3.0开发JNI流程------在Android原程序添加自己类
  6. 利用Android的Matrix类实现J2ME的drawRegion的镜像方法

随机推荐

  1. Android之Notification
  2. java.net.SocketTimeoutException: conne
  3. Android基础-Android的生命周期
  4. Android 自定义进度条
  5. Android进度条总结
  6. android源码在线查看
  7. Android使用代码模拟HOME键的功能
  8. 35、键盘布局的tableLayout备份
  9. Android(安卓)Gallery子元素无法横向填满
  10. Android植物大战僵尸小游戏