精华内容
下载资源
问答
  • ViewGroup源码-Touch事件

    2018-11-15 02:30:07
    在Android-27中查看源码: 在看完View源码的触摸事件(View源码-Touch事件)后,我们接着来看看容器类View的触摸事件。因为容器类的View都继承自...同样的先来看ViewGroup源码中的dispatchTouchEvent方法: fi...

    在Android-27中查看源码:

    在看完View源码的触摸事件(View源码-Touch事件)后,我们接着来看看容器类View的触摸事件。因为容器类的View都继承自ViewGroup,所以我们在ViewGroup的源码中来看看是如何处理触摸事件的。

    同样的先来看ViewGroup源码中的dispatchTouchEvent方法:

    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }

    首先,会判断是否不允许拦截,可以通过requestDisallowInterceptTouchEvent进行设置,默认允许拦截。如果允许拦截,会调用onInterceptTouchEvent,看看该View是否拦截了触摸事件,默认不拦截即可以将触摸事件向子View传递。

    接下来,如果触摸事件没有取消并且没有拦截,在手指按下的时候:

    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
    ...
    for (int i = childrenCount - 1; i >= 0; i--){
        ...
        newTouchTarget = getTouchTarget(child);
        if (newTouchTarget != null) {
            // Child is already receiving touch within its bounds.
            // Give it the new pointer in addition to the ones it is handling.
            newTouchTarget.pointerIdBits |= idBitsToAssign;
            break;
        }
        resetCancelNextUpFlag(child);
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            // Child wants to receive touch within its bounds.
            mLastTouchDownTime = ev.getDownTime();
            if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                    if (children[childIndex] == mChildren[j]) {
                        mLastTouchDownIndex = j;
                        break;
                    }
                }
            } else {
                mLastTouchDownIndex = childIndex;
            }
            mLastTouchDownX = ev.getX();
            mLastTouchDownY = ev.getY();
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }
    ...
    }
    1. 按照绘制子View的顺序,找到该ViewGoup的所有子view,并保存到ArrayList中。
    2. 根据视图顺序依次遍历子View。判断当前子view是否是TouchTarget,若是则跳出循环。否则调用dispatchTransformedTouchEvent方法,如果当前子View的dispatchTouchEvent返回为true,找到该子View在ViewGroup中的index,并将该子View作为新的TouchTarget。
    3. 清楚保存了所有子View的ArrayList。

    然后在TouchTarget形成的链式结构中,处理触摸事件:

    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
                    handled = true;
                }
                if (cancelChild) {
                    if (predecessor == null) {
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    target.recycle();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }
    1. 如果子View没有处理触摸事件,则由当前的ViewGroup处理,然后返回处理结果。因为ViewGroup是View的子类,所以还是由View的dispatchTouchEvent处理。
    2. 如果子View中处理了触摸事件,根据TouchTarget生成的链式结构,不断循环,分别调用里面View的dispatchTouchEvent方法。

    从上面的分析可以看出整个ViewGroup的Touch事件的整个传递过程如下:

    1. 是否调用了requestDisallowInterceptTouchEvent方法设置不允许拦截,默认允许拦截。若允许拦截,则再调用onInterceptTouchEvent,看是否拦截。
    2. 如果触摸事件被拦截,则调用ViewGroup的dispatchTransformedTouchEvent方法,其实是调用View的dispatchTouchEvent方法。否则继续向下。
    3. 当触摸事件为MotionEvent.ACTION_DOWN时,首先获取根据绘制顺序保存了所有子View的ArrayLsit,然后再根据视图顺序去遍历子View。

      1. 如果从TouchTarget形成的链表中发现该View,则跳出循环,即找到了TouchTarget。否则继续向下。
      2. 在子View中寻找,当子View的dispatchTouchEvent方法返回为true时,则找到了新的TouchTarget,并将其添加到TouchTarget形成的链表中。
    4. 遍历TouchTarget形成的链表,然后对链表里面的子View分别调用dispatchTouchEvent,最后将处理结果返回。
    展开全文
  • ViewGroup 源码深度分析

    2016-06-17 10:51:19
    ViewGroup源码分析 1 ViewGroup结构  ViewGroup是一种特殊的View,能够包含其他的View。官网上面的地址:ViewGroup api java.lang.Object  ↳ android.view.View    ↳ android.view.ViewGroup

    ViewGroup源码分析

    1 ViewGroup结构

       ViewGroup是一种特殊的View,能够包含其他的View。官网上面的地址: ViewGroup api
    java.lang.Object
       ↳ android.view.View
         ↳ android.view.ViewGroup
    从源码的结构中可以看出,ViewGroup是继承在View的,说明View中的所有方法他也是可以用的。由于ViewGroup中可以添加很多View或者ViewGroup,ViewGroup是采用树状图来管理子View的。在实际的项目开发中,我们经常会继承这个类来实现我们更种各样的功能,比如标签,上拉刷新,很多很多的自定义控件。
    展开全文
  • ViewGroup源码解读

    千次阅读 2017-08-31 11:54:59
    本篇文章已授权我的公众号:我就是马云飞 首发 我们之前刚刚分析完事件传递机制和view的源码,如果没有看过的,建议看完View的事件拦截机制浅析以及View的事件源码解析。这次我们来分析下viewgroup的。可能有人会想...

    本篇文章已授权我的公众号:我就是马云飞 首发

    我们之前刚刚分析完事件传递机制和view的源码,如果没有看过的,建议看完View的事件拦截机制浅析以及View的事件源码解析。这次我们来分析下viewgroup的。

    可能有人会想,怎么又是源码分析,肯定又是一大通。其实没你想的那么复杂。仔细分析一波就行了。

    解读ViewGroup

    我们都知道,一个事件完整的流程是从dispatchTouchevent–>onInterceptTouchevent–>onTouchEvent。我们先不说事件监听的问题。上述三个步骤就是正常一个点击的流程。前面我们分析view的时候发现它并没有onInterceptTouchevent这个方法。这个我之前有提到,view已经是最底层了,所以就不需要拦截了。而这一整套的机制就是在ViewGroup中体现出来的。我们先来看一张图:
    这里写图片描述

    触摸事件发生后,在Activity内最先接收到事件的是Activity自身的dispatchTouchEvent,然后Activity传递给Activity的Window。接着Window传递给最顶端的View,也就是DecorView。接下来才是我们熟悉的触摸事件流程:首先是最顶端的ViewGroup(这边便是DecorView)的dispatchTouchEvent接收到事件。并通过onInterceptTouchEvent判断是否需要拦截。如果拦截则分配到ViewGroup自身的onTouchEvent,如果不拦截则查找位于点击区域的子View(当事件是ACTION_DOWN的时候,会做一次查找并根据查找到的子View设定一个TouchTarget,有了TouchTarget以后,后续的对应id的事件如果不被拦截都会分发给这一个TouchTarget)。查找到子View以后则调用dispatchTransformedTouchEvent把MotionEvent的坐标转换到子View的坐标空间,这不仅仅是x,y的偏移,还包括根据子View自身矩阵的逆矩阵对坐标进行变换(这就是使用setTranslationX,setScaleX等方法调用后,子View的点击区域还能保持和自身绘制内容一致的原因。使用Animation做变换点击区域不同步是因为Animation使用的是Canvas的矩阵而不是View自身的矩阵来做变换)。

    dispatchTouchevent分析

    我们先放上dispatchTouchevent的源码,然后一步一步来分析:

     public boolean dispatchTouchEvent(MotionEvent ev) {
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
            // If the event targets the accessibility focused view and this is it, start
            // normal event dispatch. Maybe a descendant is what will handle the click.
            if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
                ev.setTargetAccessibilityFocus(false);
            }
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // Throw away all previous state when starting a new touch gesture.
                    // The framework may have dropped the up or cancel event for the previous gesture
                    // due to an app switch, ANR, or some other state change.
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
                // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    // There are no touch targets and this action is not an initial down
                    // so this view group continues to intercept touches.
                    intercepted = true;
                }
                // If intercepted, start normal event dispatch. Also if there is already
                // a view that is handling the gesture, do normal event dispatch.
                if (intercepted || mFirstTouchTarget != null) {
                    ev.setTargetAccessibilityFocus(false);
                }
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {
                    // If the event is targeting accessiiblity focus we give it to the
                    // view that has accessibility focus and if it does not handle it
                    // we clear the flag and dispatch the event to all children as usual.
                    // We are looking up the accessibility focused host to avoid keeping
                    // state since these events are very rare.
                    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                            ? findChildWithAccessibilityFocus() : null;
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        final int actionIndex = ev.getActionIndex(); // always 0 for down
                        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                                : TouchTarget.ALL_POINTER_IDS;
                        // Clean up earlier touch targets for this pointer id in case they
                        // have become out of sync.
                        removePointersFromTouchTargets(idBitsToAssign);
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);
                                // If there is a view that has accessibility focus we want it
                                // to get the event first and if not handled we will perform a
                                // normal dispatch. We may do a double iteration but this is
                                // safer given the timeframe.
                                if (childWithAccessibilityFocus != null) {
                                    if (childWithAccessibilityFocus != child) {
                                        continue;
                                    }
                                    childWithAccessibilityFocus = null;
                                    i = childrenCount - 1;
                                }
                                if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    ev.setTargetAccessibilityFocus(false);
                                    continue;
                                }
                                newTouchTarget = getTouchTarget(child);
                                if (newTouchTarget != null) {
                                    // Child is already receiving touch within its bounds.
                                    // Give it the new pointer in addition to the ones it is handling.
                                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                                    break;
                                }
                                resetCancelNextUpFlag(child);
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // Child wants to receive touch within its bounds.
                                    mLastTouchDownTime = ev.getDownTime();
                                    if (preorderedList != null) {
                                        // childIndex points into presorted list, find original index
                                        for (int j = 0; j < childrenCount; j++) {
                                            if (children[childIndex] == mChildren[j]) {
                                                mLastTouchDownIndex = j;
                                                break;
                                            }
                                        }
                                    } else {
                                        mLastTouchDownIndex = childIndex;
                                    }
                                    mLastTouchDownX = ev.getX();
                                    mLastTouchDownY = ev.getY();
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }
                                // The accessibility focus didn't handle the event, so clear
                                // the flag and do a normal dispatch to all children.
                                ev.setTargetAccessibilityFocus(false);
                            }
                            if (preorderedList != null) preorderedList.clear();
                        }
                        if (newTouchTarget == null && mFirstTouchTarget != null) {
                            // Did not find a child to receive the event.
                            // Assign the pointer to the least recently added target.
                            newTouchTarget = mFirstTouchTarget;
                            while (newTouchTarget.next != null) {
                                newTouchTarget = newTouchTarget.next;
                            }
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                        }
                    }
                }
                // Dispatch to touch targets.
                if (mFirstTouchTarget == null) {
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {
                    // Dispatch to touch targets, excluding the new touch target if we already
                    // dispatched to it.  Cancel touch targets if necessary.
                    TouchTarget predecessor = null;
                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }
                            if (cancelChild) {
                                if (predecessor == null) {
                                    mFirstTouchTarget = next;
                                } else {
                                    predecessor.next = next;
                                }
                                target.recycle();
                                target = next;
                                continue;
                            }
                        }
                        predecessor = target;
                        target = next;
                    }
                }
                // Update list of touch targets for pointer up or cancel, if needed.
                if (canceled
                        || actionMasked == MotionEvent.ACTION_UP
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    resetTouchState();
                } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                    final int actionIndex = ev.getActionIndex();
                    final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                    removePointersFromTouchTargets(idBitsToRemove);
                }
            }
            if (!handled && mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
            }
            return handled;
        }
    

    是不是整个人都蒙蔽了,这么长一串。其实整段代码可以缩减成几句话,就是这样:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = false;             
        if (!onInterceptTouchEvent(ev)) { 
            result = child.dispatchTouchEvent(ev);
        }
    
        if (!result) {                    
            result = onTouchEvent(ev);
        }
    
        return result;
    }

    默认不消耗事件,如果本身没有拦截,就交给子类的dispatch事件,如果事件没有消费,就调用自身的onTouchEvent事件。你们仔细想想,流程是不是这样的?

    好了,我们现在开始分析整个dispatch事件。具体说明和代码,你们自己对应= =因为太长了。

    对action_down的处理:
    我们发现,刚进方法的时候有个判断,第一次按下的时候,他会通过 cancelAndClearTouchTargets(ev)取消并且清除所有的手势操作,并且通过resetTouchState()把手势状态设置成默认状态。

    接下来的操作,当然就是检查是否需要拦截事件拉。既然是拦截,当然就会走onInterceptTouchEvent这个方法了。我们来看看,viewgroup的onInterceptTouchEvent方法是怎么处理的。

     public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                    && ev.getAction() == MotionEvent.ACTION_DOWN
                    && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                    && isOnScrollbarThumb(ev.getX(), ev.getY())) {
                return true;
            }
            return false;
        }

    我们可以发现,他默认就是false的。那么我们继续回到dispatch看。判断是否拦截后,我们发现他还执行了一句话ev.setAction(action) 官方说明是恢复操作,防止被更改。

    事件处理
    接下来就是检查事件是否取消咯。如果没有取消并且没有拦截就执行正常的事件处理。

    如果事件是针对可访问性焦点视图,我们将其提供给具有可访问性焦点的视图。如果它不处理它,我们清除该标志并像往常一样将事件分派给所有的 ChildView。我们检测并避免保持这种状态,因为这些事非常罕见。这段是官方的解释。我们继续向下看,他执行这样一个方法removePointersFromTouchTargets(idBitsToAssign)。是为了防止指针不同步,清除之前的触摸标识。自我认为可能会和多指触控有关,先不管他,我们继续向下分析。

    接下来就是打造了,他会先得到触摸点的坐标位置,然后在当前位置查找可接触的ChildView。然后重点!!!他的查找顺序是从后向前查找。什么意思呢?就是如果A和B有重叠的部分,并且B在A的上面,那么他处理的便是B的事件了。而不处理A的事件。

    如果子View可以接受事件,那么我们就给他一个触摸的标识。接下来他会通过调用dispatchTransformedTouchEvent把事件分配给子View。

    最后他会判断是否有touchtarget。如果没有的话,那就处理子view的事件。否则就会遍历touchtarget处理事件,也就是之前说的多点触控。在往后就是对action_up和cancel做的一些处理了,譬如:重置手势状态,移除多指操作等等。

    dispatchTransformedTouchEvent分析

    前面我们说到了,会通过这个方法把事件分发给子view。我们还是先来看代码:

    
        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            final int oldAction = event.getAction();
            if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
                event.setAction(MotionEvent.ACTION_CANCEL);
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
            // Calculate the number of pointers to deliver.
            final int oldPointerIdBits = event.getPointerIdBits();
            final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
            // If for some reason we ended up in an inconsistent state where it looks like we
            // might produce a motion event with no pointers in it, then drop the event.
            if (newPointerIdBits == 0) {
                return false;
            }
            // If the number of pointers is the same and we don't need to perform any fancy
            // irreversible transformations, then we can reuse the motion event for this
            // dispatch as long as we are careful to revert any changes we make.
            // Otherwise we need to make a copy.
            final MotionEvent transformedEvent;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
                        handled = child.dispatchTouchEvent(event);
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
            // Perform any necessary transformations and dispatch.
            if (child == null) {
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }
                handled = child.dispatchTouchEvent(transformedEvent);
            }
            // Done.
            transformedEvent.recycle();
            return handled;
        }

    这段代码就比之前简单很多了。我们会发现,他先判断状态是否取消,如果取消了,把当前事件变成取消状态,然后在判断是否有子view。如果有子view的话直接调用子view的dispatch事件。下面就是多指了,一个pointer对应一个ID,防止处理冲突。我印象中能简单粗暴的处理多指,应该是ViewDragHelper了。具体,你们可以自己去看。后面就如之前一样,判断child是否为null。然后得到是执行自身的事件还是child的事件。

    总结

    1.ViewGroup包涵多个子view的时候,我们是从后遍历,判断当前view是否可以点击,然后分发给需要处理的子view。
    2.我们可以在onInterceptTouchEvent中进行事件拦截。
    3.我们可以发现ViewGroup没有onTouchEvent事件,说明他的处理逻辑和View是一样的。
    4.子view如果消耗了事件,那么ViewGroup就不会在接受到事件了。

    最后我放上ViewGroup源码,你们可以自己去了解。

    展开全文
  • ViewGroup源码分析

    千次阅读 2014-11-12 18:06:20
    本文是对ViewGroup源码分析,先来看看代码结构

    本文是对ViewGroup的源码分析,先来看看关系结构

    public abstract class

    ViewGroup

    extends  View
    implements  ViewManager  ViewParent
    java.lang.Object
       ↳ android.view.View
         ↳ android.view.ViewGroup

    概述


    ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.


    首先ViewGroup是一个抽象类,它继承子View,实现了两个重要接口:ViewManager、ViewParent,这两个接口中分别包含着极为重要的方法,可以说代表了ViewGroup的核心特性。

    1.ViewManager.java接口源码:

    public interface ViewManager
    {
        /**
         * Assign the passed LayoutParams to the passed View and add the view to the window.
         * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
         * errors, such as adding a second view to a window without removing the first view.
         * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
         * secondary {@link Display} and the specified display can't be found
         * (see {@link android.app.Presentation}).
         * @param view The view to be added to this window.
         * @param params The LayoutParams to assign to view.
         */
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }


    2. ViewParent.java接口源码:

    /**
         * Called when something has changed which has invalidated the layout of a
         * child of this view parent. This will schedule a layout pass of the view
         * tree.
         */
        public void requestLayout();
    
        /**
         * Indicates whether layout was requested on this view parent.
         *
         * @return true if layout was requested, false otherwise
         */
        public boolean isLayoutRequested();
    
        /**
         * Called when a child wants the view hierarchy to gather and report
         * transparent regions to the window compositor. Views that "punch" holes in
         * the view hierarchy, such as SurfaceView can use this API to improve
         * performance of the system. When no such a view is present in the
         * hierarchy, this optimization in unnecessary and might slightly reduce the
         * view hierarchy performance.
         * 
         * @param child the view requesting the transparent region computation
         * 
         */
        public void requestTransparentRegion(View child);
    
        /**
         * All or part of a child is dirty and needs to be redrawn.
         * 
         * @param child The child which is dirty
         * @param r The area within the child that is invalid
         */
        public void invalidateChild(View child, Rect r);
    
        /**
         * All or part of a child is dirty and needs to be redrawn.
         *
         * <p>The location array is an array of two int values which respectively
         * define the left and the top position of the dirty child.</p>
         *
         * <p>This method must return the parent of this ViewParent if the specified
         * rectangle must be invalidated in the parent. If the specified rectangle
         * does not require invalidation in the parent or if the parent does not
         * exist, this method must return null.</p>
         *
         * <p>When this method returns a non-null value, the location array must
         * have been updated with the left and top coordinates of this ViewParent.</p>
         *
         * @param location An array of 2 ints containing the left and top
         *        coordinates of the child to invalidate
         * @param r The area within the child that is invalid
         *
         * @return the parent of this ViewParent or null
         */
        public ViewParent invalidateChildInParent(int[] location, Rect r);
    
        /**
         * Returns the parent if it exists, or null.
         *
         * @return a ViewParent or null if this ViewParent does not have a parent
         */
        public ViewParent getParent();
    
        /**
         * Called when a child of this parent wants focus
         * 
         * @param child The child of this ViewParent that wants focus. This view
         *        will contain the focused view. It is not necessarily the view that
         *        actually has focus.
         * @param focused The view that is a descendant of child that actually has
         *        focus
         */
        public void requestChildFocus(View child, View focused);
    
        /**
         * Tell view hierarchy that the global view attributes need to be
         * re-evaluated.
         * 
         * @param child View whose attributes have changed.
         */
        public void recomputeViewAttributes(View child);
        
        /**
         * Called when a child of this parent is giving up focus
         * 
         * @param child The view that is giving up focus
         */
        public void clearChildFocus(View child);
    
        /**
         * Compute the visible part of a rectangular region defined in terms of a child view's
         * coordinates.
         *
         * <p>Returns the clipped visible part of the rectangle <code>r</code>, defined in the
         * <code>child</code>'s local coordinate system. <code>r</code> is modified by this method to
         * contain the result, expressed in the global (root) coordinate system.</p>
         *
         * <p>The resulting rectangle is always axis aligned. If a rotation is applied to a node in the
         * View hierarchy, the result is the axis-aligned bounding box of the visible rectangle.</p>
         *
         * @param child A child View, whose rectangular visible region we want to compute
         * @param r The input rectangle, defined in the child coordinate system. Will be overwritten to
         * contain the resulting visible rectangle, expressed in global (root) coordinates
         * @param offset The input coordinates of a point, defined in the child coordinate system.
         * As with the <code>r</code> parameter, this will be overwritten to contain the global (root)
         * coordinates of that point.
         * A <code>null</code> value is valid (in case you are not interested in this result)
         * @return true if the resulting rectangle is not empty, false otherwise
         */
        public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
    
        /**
         * Find the nearest view in the specified direction that wants to take focus
         * 
         * @param v The view that currently has focus
         * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
         */
        public View focusSearch(View v, int direction);
    
        /**
         * Change the z order of the child so it's on top of all other children.
         * This ordering change may affect layout, if this container
         * uses an order-dependent layout scheme (e.g., LinearLayout). This
         * method should be followed by calls to {@link #requestLayout()} and
         * {@link View#invalidate()} on this parent.
         * 
         * @param child The child to bring to the top of the z order
         */
        public void bringChildToFront(View child);
    
        /**
         * Tells the parent that a new focusable view has become available. This is
         * to handle transitions from the case where there are no focusable views to
         * the case where the first focusable view appears.
         * 
         * @param v The view that has become newly focusable
         */
        public void focusableViewAvailable(View v);
    
        /**
         * Bring up a context menu for the specified view or its ancestors.
         *
         * <p>In most cases, a subclass does not need to override this.  However, if
         * the subclass is added directly to the window manager (for example,
         * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
         * then it should override this and show the context menu.</p>
         * 
         * @param originalView The source view where the context menu was first invoked
         * @return true if a context menu was displayed
         */
        public boolean showContextMenuForChild(View originalView);
    
        /**
         * Have the parent populate the specified context menu if it has anything to
         * add (and then recurse on its parent).
         * 
         * @param menu The menu to populate
         */
        public void createContextMenu(ContextMenu menu);
    
        /**
         * Start an action mode for the specified view.
         *
         * <p>In most cases, a subclass does not need to override this. However, if the
         * subclass is added directly to the window manager (for example,
         * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
         * then it should override this and start the action mode.</p>
         *
         * @param originalView The source view where the action mode was first invoked
         * @param callback The callback that will handle lifecycle events for the action mode
         * @return The new action mode if it was started, null otherwise
         */
        public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
    
        /**
         * This method is called on the parent when a child's drawable state
         * has changed.
         *
         * @param child The child whose drawable state has changed.
         */
        public void childDrawableStateChanged(View child);
        
        /**
         * Called when a child does not want this parent and its ancestors to
         * intercept touch events with
         * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
         *
         * <p>This parent should pass this call onto its parents. This parent must obey
         * this request for the duration of the touch (that is, only clear the flag
         * after this parent has received an up or a cancel.</p>
         * 
         * @param disallowIntercept True if the child does not want the parent to
         *            intercept touch events.
         */
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
        
        /**
         * Called when a child of this group wants a particular rectangle to be
         * positioned onto the screen.  {@link ViewGroup}s overriding this can trust
         * that:
         * <ul>
         *   <li>child will be a direct child of this group</li>
         *   <li>rectangle will be in the child's coordinates</li>
         * </ul>
         *
         * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
         * <ul>
         *   <li>nothing will change if the rectangle is already visible</li>
         *   <li>the view port will be scrolled only just enough to make the
         *       rectangle visible</li>
         * <ul>
         *
         * @param child The direct child making the request.
         * @param rectangle The rectangle in the child's coordinates the child
         *        wishes to be on the screen.
         * @param immediate True to forbid animated or delayed scrolling,
         *        false otherwise
         * @return Whether the group scrolled to handle the operation
         */
        public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
                boolean immediate);
    
        /**
         * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
         * The child has already populated a record for itself in the event and is delegating
         * to its parent to send the event. The parent can optionally add a record for itself.
         * <p>
         * Note: An accessibility event is fired by an individual view which populates the
         *       event with a record for its state and requests from its parent to perform
         *       the sending. The parent can optionally add a record for itself before
         *       dispatching the request to its parent. A parent can also choose not to
         *       respect the request for sending the event. The accessibility event is sent
         *       by the topmost view in the view tree.</p>
         *
         * @param child The child which requests sending the event.
         * @param event The event to be sent.
         * @return True if the event was sent.
         */
        public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
    
        /**
         * Called when a child view now has or no longer is tracking transient state.
         *
         * @param child Child view whose state has changed
         * @param hasTransientState true if this child has transient state
         *
         * @hide
         */
        public void childHasTransientStateChanged(View child, boolean hasTransientState);
    
        /**
         * Ask that a new dispatch of {@link View#fitSystemWindows(Rect)
         * View.fitSystemWindows(Rect)} be performed.
         */
        public void requestFitSystemWindows();
    
        /**
         * Gets the parent of a given View for accessibility. Since some Views are not
         * exposed to the accessibility layer the parent for accessibility is not
         * necessarily the direct parent of the View, rather it is a predecessor.
         *
         * @return The parent or <code>null</code> if no such is found.
         */
        public ViewParent getParentForAccessibility();
    
        /**
         * A child notifies its parent that its state for accessibility has changed.
         * That is some of the child properties reported to accessibility services has
         * changed, hence the interested services have to be notified for the new state.
         *
         * @hide
         */
        public void childAccessibilityStateChanged(View child);
    
        /**
         * Tells if this view parent can resolve the layout direction.
         * See {@link View#setLayoutDirection(int)}
         *
         * @return True if this view parent can resolve the layout direction.
         *
         * @hide
         */
        public boolean canResolveLayoutDirection();
    
        /**
         * Tells if this view parent layout direction is resolved.
         * See {@link View#setLayoutDirection(int)}
         *
         * @return True if this view parent layout direction is resolved.
         *
         * @hide
         */
        public boolean isLayoutDirectionResolved();
    
        /**
         * Return this view parent layout direction. See {@link View#getLayoutDirection()}
         *
         * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
         * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
         *
         * @hide
         */
        public int getLayoutDirection();
    
        /**
         * Tells if this view parent can resolve the text direction.
         * See {@link View#setTextDirection(int)}
         *
         * @return True if this view parent can resolve the text direction.
         *
         * @hide
         */
        public boolean canResolveTextDirection();
    
        /**
         * Tells if this view parent text direction is resolved.
         * See {@link View#setTextDirection(int)}
         *
         * @return True if this view parent text direction is resolved.
         *
         * @hide
         */
        public boolean isTextDirectionResolved();
    
        /**
         * Return this view parent text direction. See {@link View#getTextDirection()}
         *
         * @return the resolved text direction. Returns one of:
         *
         * {@link View#TEXT_DIRECTION_FIRST_STRONG}
         * {@link View#TEXT_DIRECTION_ANY_RTL},
         * {@link View#TEXT_DIRECTION_LTR},
         * {@link View#TEXT_DIRECTION_RTL},
         * {@link View#TEXT_DIRECTION_LOCALE}
         *
         * @hide
         */
        public int getTextDirection();
    
        /**
         * Tells if this view parent can resolve the text alignment.
         * See {@link View#setTextAlignment(int)}
         *
         * @return True if this view parent can resolve the text alignment.
         *
         * @hide
         */
        public boolean canResolveTextAlignment();
    
        /**
         * Tells if this view parent text alignment is resolved.
         * See {@link View#setTextAlignment(int)}
         *
         * @return True if this view parent text alignment is resolved.
         *
         * @hide
         */
        public boolean isTextAlignmentResolved();
    
        /**
         * Return this view parent text alignment. See {@link android.view.View#getTextAlignment()}
         *
         * @return the resolved text alignment. Returns one of:
         *
         * {@link View#TEXT_ALIGNMENT_GRAVITY},
         * {@link View#TEXT_ALIGNMENT_CENTER},
         * {@link View#TEXT_ALIGNMENT_TEXT_START},
         * {@link View#TEXT_ALIGNMENT_TEXT_END},
         * {@link View#TEXT_ALIGNMENT_VIEW_START},
         * {@link View#TEXT_ALIGNMENT_VIEW_END}
         *
         * @hide
         */
        public int getTextAlignment();

    可以看到addView, updateViewLayout, removeView, requestLayout, bringChildToFront等等重要的方法都在这两个接口里面得到定义,下面来一一分析一下:


    一、addView(View view, ViewGroup.LayoutParams params)方法的实现:

    /**
         * Adds a child view with the specified layout parameters.
         *
         * <p><strong>Note:</strong> do not invoke this method from
         * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
         * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
         *
         * @param child the child view to add
         * @param params the layout parameters to set on the child
         */
        public void addView(View child, LayoutParams params) {
            addView(child, -1, params);
        }
    
        /**
         * Adds a child view with the specified layout parameters.
         *
         * <p><strong>Note:</strong> do not invoke this method from
         * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
         * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
         *
         * @param child the child view to add
         * @param index the position at which to add the child
         * @param params the layout parameters to set on the child
         */
        public void addView(View child, int index, LayoutParams params) {
            if (DBG) {
                System.out.println(this + " addView");
            }
    
            // addViewInner() will call child.requestLayout() when setting the new LayoutParams
            // therefore, we call requestLayout() on ourselves before, so that the child's request
            // will be blocked at our level
            requestLayout();
            invalidate(true);
            addViewInner(child, index, params, false);
        }


    可以看到在ViewGroup的addView方法中,首先调用了父类View中的 requestLayout方法:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。这里首先安调用自己的requestLayout告诉自己的父View要求重新布局,然后调用invalidate(调用onDraw())要求重绘,然后调用addViewInner去调用child的requestLayout,这个方法的源码如下:

    private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
    
            if (child.getParent() != null) {
                throw new IllegalStateException("The specified child already has a parent. " +
                        "You must call removeView() on the child's parent first.");
            }
    
            if (!checkLayoutParams(params)) {
                params = generateLayoutParams(params);
            }
    
            if (preventRequestLayout) {
                child.mLayoutParams = params;
            } else {
                child.setLayoutParams(params);
            }
    
            if (index < 0) {
                index = mChildrenCount;
            }
    
            addInArray(child, index);
    
            // tell our children
            if (preventRequestLayout) {
                child.assignParent(this);
            } else {
                child.mParent = this;
            }
    
            if (child.hasFocus()) {
                requestChildFocus(child, child.findFocus());
            }
    
            AttachInfo ai = mAttachInfo;
            if (ai != null) {
                boolean lastKeepOn = ai.mKeepScreenOn;
                ai.mKeepScreenOn = false;
                child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
                if (ai.mKeepScreenOn) {
                    needGlobalAttributesUpdate(true);
                }
                ai.mKeepScreenOn = lastKeepOn;
            }
    
            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewAdded(this, child);
            }
    
            if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
                mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
            }
        }


    这里可以看到我们写代码的时候经常看到的一个异常,也解释了index的具体含义。我们也可以看到:每一个View都保存了一个指向父View的指针。倒数第二段代码,也用到一个接口,可以监听子View的添加和删除:

    /**
         * Interface definition for a callback to be invoked when the hierarchy
         * within this view changed. The hierarchy changes whenever a child is added
         * to or removed from this view.
         */
        public interface OnHierarchyChangeListener {
            /**
             * Called when a new child is added to a parent view.
             *
             * @param parent the view in which a child was added
             * @param child the new child view added in the hierarchy
             */
            void onChildViewAdded(View parent, View child);
    
            /**
             * Called when a child is removed from a parent view.
             *
             * @param parent the view from which the child was removed
             * @param child the child removed from the hierarchy
             */
            void onChildViewRemoved(View parent, View child);
        }

    二、removeView

    removeView方法除了正常调用requestLayout和invalidate方法以外:

    /**
         * Removes the specified range of views from the group.
         *
         * @param start the first position in the group of the range of views to remove
         * @param count the number of views to remove
         */
        public void removeViews(int start, int count) {
            removeViewsInternal(start, count);
            requestLayout();
            invalidate();
        }

    但是看下面两个方法:

    /**
         * {@inheritDoc}
         */
        public void removeView(View view) {
            removeViewInternal(view);
            requestLayout();
            invalidate();
        }
    
        /**
         * Removes a view during layout. This is useful if in your onLayout() method,
         * you need to remove more views.
         *
         * @param view the view to remove from the group
         */
        public void removeViewInLayout(View view) {
            removeViewInternal(view);
        }

    后者没有调用requestLayout和invalidate,但是后者的说明里面提到是在onLayout()方法中调用这个方法的。所以如果没有猜错的话,在布局完成后调用后面一个方法对界面是没有影响的,好的转入正题:

    private void removeViewInternal(int index, View view) {
            boolean clearChildFocus = false;
            if (view == mFocused) {
                view.clearFocusForRemoval();
                clearChildFocus = true;
            }
    
            if (view.getAnimation() != null) {
                addDisappearingView(view);
            } else if (view.mAttachInfo != null) {
               view.dispatchDetachedFromWindow();
            }
    
            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(this, view);
            }
    
            needGlobalAttributesUpdate(false);
    
            removeFromArray(index);
    
            if (clearChildFocus) {
                clearChildFocus(view);
            }
        }

    这里的删除貌似只是从Array中删除了一个元素(更加印证了前面提到的说法)。只有调用onLayout和onMeasure方法之后才会诱发界面重新布局。


    三、测量函数

    /**
         * Ask all of the children of this view to measure themselves, taking into
         * account both the MeasureSpec requirements for this view and its padding.
         * We skip children that are in the GONE state The heavy lifting is done in
         * getChildMeasureSpec.
         *
         * @param widthMeasureSpec The width requirements for this view
         * @param heightMeasureSpec The height requirements for this view
         */
        protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
            final int size = mChildrenCount;
            final View[] children = mChildren;
            for (int i = 0; i < size; ++i) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }
    
        /**
         * Ask one of the children of this view to measure itself, taking into
         * account both the MeasureSpec requirements for this view and its padding.
         * The heavy lifting is done in getChildMeasureSpec.
         *
         * @param child The child to measure
         * @param parentWidthMeasureSpec The width requirements for this view
         * @param parentHeightMeasureSpec The height requirements for this view
         */
        protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
            final LayoutParams lp = child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

    前者只不过是告诉这个Group把自己所有的View全部测量一下,后者则是测量单个子View的大小。这边无疑涉及到一个方法:


    /**
         * Does the hard part of measureChildren: figuring out the MeasureSpec to
         * pass to a particular child. This method figures out the right MeasureSpec
         * for one dimension (height or width) of one child view.
         *
         * The goal is to combine information from our MeasureSpec with the
         * LayoutParams of the child to get the best possible results. For example,
         * if the this view knows its size (because its MeasureSpec has a mode of
         * EXACTLY), and the child has indicated in its LayoutParams that it wants
         * to be the same size as the parent, the parent should ask the child to
         * layout given an exact size.
         *
         * @param spec The requirements for this view
         * @param padding The padding of this view for the current dimension and
         *        margins, if applicable
         * @param childDimension How big the child wants to be in the current
         *        dimension
         * @return a MeasureSpec integer for the child
         */
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }


    三种模式的判断,进行不同的赋值!这些值会传递给子View,告诉它们父View的大小。


    四、布局参数LayoutParams

    /**
         * LayoutParams are used by views to tell their parents how they want to be
         * laid out. See
         * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
         * for a list of all child view attributes that this class supports.
         *
         * <p>
         * The base LayoutParams class just describes how big the view wants to be
         * for both width and height. For each dimension, it can specify one of:
         * <ul>
         * <li>FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which
         * means that the view wants to be as big as its parent (minus padding)
         * <li> WRAP_CONTENT, which means that the view wants to be just big enough
         * to enclose its content (plus padding)
         * <li> an exact number
         * </ul>
         * There are subclasses of LayoutParams for different subclasses of
         * ViewGroup. For example, AbsoluteLayout has its own subclass of
         * LayoutParams which adds an X and Y value.
         *
         * @attr ref android.R.styleable#ViewGroup_Layout_layout_height
         * @attr ref android.R.styleable#ViewGroup_Layout_layout_width
         */
        public static class LayoutParams {
            /**
             * Special value for the height or width requested by a View.
             * FILL_PARENT means that the view wants to be as big as its parent,
             * minus the parent's padding, if any. This value is deprecated
             * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
             */
            @SuppressWarnings({"UnusedDeclaration"})
            @Deprecated
            public static final int FILL_PARENT = -1;
    
            /**
             * Special value for the height or width requested by a View.
             * MATCH_PARENT means that the view wants to be as big as its parent,
             * minus the parent's padding, if any. Introduced in API Level 8.
             */
            public static final int MATCH_PARENT = -1;
    
            /**
             * Special value for the height or width requested by a View.
             * WRAP_CONTENT means that the view wants to be just large enough to fit
             * its own internal content, taking its own padding into account.
             */
            public static final int WRAP_CONTENT = -2;
    
            /**
             * Information about how wide the view wants to be. Can be one of the
             * constants FILL_PARENT (replaced by MATCH_PARENT ,
             * in API Level 8) or WRAP_CONTENT. or an exact size.
             */
            @ViewDebug.ExportedProperty(mapping = {
                @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
                @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
            })
            public int width;
    
            /**
             * Information about how tall the view wants to be. Can be one of the
             * constants FILL_PARENT (replaced by MATCH_PARENT ,
             * in API Level 8) or WRAP_CONTENT. or an exact size.
             */
            @ViewDebug.ExportedProperty(mapping = {
                @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
                @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
            })
            public int height;
    
            /**
             * Used to animate layouts.
             */
            public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
    
            /**
             * Creates a new set of layout parameters. The values are extracted from
             * the supplied attributes set and context. The XML attributes mapped
             * to this set of layout parameters are:
             *
             * <ul>
             *   <li><code>layout_width</code>: the width, either an exact value,
             *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
             *   {@link #MATCH_PARENT} in API Level 8)</li>
             *   <li><code>layout_height</code>: the height, either an exact value,
             *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
             *   {@link #MATCH_PARENT} in API Level 8)</li>
             * </ul>
             *
             * @param c the application environment
             * @param attrs the set of attributes from which to extract the layout
             *              parameters' values
             */
            public LayoutParams(Context c, AttributeSet attrs) {
                TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
                setBaseAttributes(a,
                        R.styleable.ViewGroup_Layout_layout_width,
                        R.styleable.ViewGroup_Layout_layout_height);
                a.recycle();
            }
    
            /**
             * Creates a new set of layout parameters with the specified width
             * and height.
             *
             * @param width the width, either {@link #WRAP_CONTENT},
             *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
             *        API Level 8), or a fixed size in pixels
             * @param height the height, either {@link #WRAP_CONTENT},
             *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
             *        API Level 8), or a fixed size in pixels
             */
            public LayoutParams(int width, int height) {
                this.width = width;
                this.height = height;
            }
    
            /**
             * Copy constructor. Clones the width and height values of the source.
             *
             * @param source The layout params to copy from.
             */
            public LayoutParams(LayoutParams source) {
                this.width = source.width;
                this.height = source.height;
            }
    
            /**
             * Used internally by MarginLayoutParams.
             * @hide
             */
            LayoutParams() {
            }
    
            /**
             * Extracts the layout parameters from the supplied attributes.
             *
             * @param a the style attributes to extract the parameters from
             * @param widthAttr the identifier of the width attribute
             * @param heightAttr the identifier of the height attribute
             */
            protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
                width = a.getLayoutDimension(widthAttr, "layout_width");
                height = a.getLayoutDimension(heightAttr, "layout_height");
            }
    
            /**
             * Returns a String representation of this set of layout parameters.
             *
             * @param output the String to prepend to the internal representation
             * @return a String with the following format: output +
             *         "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
             *
             * @hide
             */
            public String debug(String output) {
                return output + "ViewGroup.LayoutParams={ width="
                        + sizeToString(width) + ", height=" + sizeToString(height) + " }";
            }
    
            /**
             * Converts the specified size to a readable String.
             *
             * @param size the size to convert
             * @return a String instance representing the supplied size
             *
             * @hide
             */
            protected static String sizeToString(int size) {
                if (size == WRAP_CONTENT) {
                    return "wrap-content";
                }
                if (size == MATCH_PARENT) {
                    return "match-parent";
                }
                return String.valueOf(size);
            }
        }


    展开全文
  • 主要是通过侧滑特效来总结如何自定义ViewGroup
  • Android实现的ViewGroup双向滑动界面效果,这在当下的各种android客户端应用中使用广泛,操作也是比较顺手的一种方式,本滑动界面中设置右滑的菜单View、设置主界面View、并设置了菜单和内容边界,定义好这些为下...
  • Call this method to remove all child views from the ViewGroup.从ViewGroup中调用此方法删除所有子视图。不要从以下方法中调用此方法。即下面的注意: 注意:do not invoke this method from {@link #draw(android...
  • 这是android viewgroup view 源码,有一些注释,此源码仅供学习使用。
  • 本文基于Android9.0的源码进行分析...ViewGroup的Down事件的分发源码分析 ViewGroup的Move事件的分发源码分析 ViewGroup的滑动事件冲突处理实战 + 源码分析 ViewGroup的事件分发机制源码概览 ViewGroup的dispat.
  • #ViewGroup#dispatchTouchEvent摘要 // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework m...
  • 甲板视图 Android Lollipop最新应用列表的反向移植。
  • 主要介绍了Android中View的容器ViewGroup,本文从源码解析ViewGroup的事件分发机制等,非常有深度,需要的朋友可以参考下
  • android点击事件传递源码讲解(ViewGroup) 三.android点击事件传递源码讲解(View) 1. 前言android点击 事件一直以来都是很多安卓程序员的心病,之前通过demo模拟总结出一些经验,但是不看源码的程序员不是好...
  • ViewAndGroup 基本图形的绘制 二阶贝塞尔曲线 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制数据点和控制点 mPaint.setColor(Color.GRAY); mPaint.setStrokeWidth(20);...
  • CustomBanner ======== 自定义轮播图 可以修改底部圆点位置 颜色 可以左右无限滑动 支持自动滚动 继承自ViewGroup Add CustomBanner to your project Image
  • 其中ViewGroup是继承自view,只不过ViewGroup可以包含子View,对于View的事件传递的派发上一篇文章已经详细介绍分析了一遍,View的事件传递源码详细解析 本篇文章我们就仔细分析ViewGroup的事件派发机制;...
  • android 从源码分析ViewGroup事件分发
  • 本篇博文是Android点击事件分发机制系列博文的第三篇,主要是从解读ViewGroup类的源码入手,根据源码理清ViewGroup点击事件分发原理,...ViewGroup事件分发中的三个重要方法的源码解析 关于ViewGroup事件分发,我们
  • 通过前一篇博客View的事件分发机制,...当我们手指触摸到屏幕时,最先接收到事件的肯定是Activity,首先调用的是Activity的dispatchTouchEvent(event),那么我们下面先来看它的源码: 1、dispatchTouchEvent(event) publi

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,516
精华内容 19,006
关键字:

viewgroup源码