Android探索之路(四)—View的使用

前言

在这篇文章之前已经总结学习了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
    @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的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线程。

你可能感兴趣的:(Android)