如何获得View与ViewGroup建立View层级关系时的回调接口?

提出问题

 在自定义 View 的时候,我们经常要往一个自定义的 ViewGroup 中添加子 View 。但是如何监听子 View 被添加到该 ViewGroup 中呢,或者我们需要监听一个 View 是什么时候添加到父 ViewGroup 中的,相应的回调在哪里。
 所以这就是我们今天的问题,有两个点,分别是:

1:如何监听 ViewGroup 添加子 View
2:对于一个 View ,在什么时机可以判断他已经被添加至窗口,可以使用 getParent() 方法来获得父 ViewGroup

分析问题

以下代码基于Android 7.1(API = 25)
 上面说了这么多,有一定Android开发基础的童鞋都可以想到这两个问题其实上整个过程都是围绕着 ViewGroup#addView 方法,甚至第一个问题可以这么解决:

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        /**
         * 定义回调接口
         */
    }

 解决方法就是在注释处编写监听器的回调。
 其实这种方法绝大多数情况下的确是能解决问题的,不管你是通过 addView 方法来添加一个 ViewViewGroup ,还是使用xml文件来编写布局文件,最后都是通过这个方法来添加到ViewGroup内。但是这种解决方案有两个小问题:

 1:addView 本身并不具有监听器的回调功能,你可能需要自己定义一个接口,然后设置回调函数使其在外部可以调用。
 2:实际上并不是所有添加子 ViewViewGroup 中都会调用这个方法,存在另外一种情况,可能并不会调用 addView 方法。

这里写图片描述

这里写图片描述
 对于 ViewGroup 添加子 View ,其实最终调用的是 addViewInner 方法,上面提到的解决方案,覆盖了图中蓝色和绿色的所有路径,我们重写的方法的位置是即使图中青色的位置,但是我们忽略了一个路径,就是图中的淡红色路径。
ViewGroup#addViewInLayout 方法,这个方法虽然不是经常使用,但是的确是客观存在,可以将一个 View 添加至一个 ViewGroup
 这个方法的注释写着:Adds a view during layout. This is useful if in your onLayout() method, you need to add more views (as does the list view for example). If index is negative, it means put it at the end of the list.(在layout过程中添加 View ,这个在 onLayout 方法中很有用,当你需要添加更多 View 时{比如列表 View },如果你传入一个负数作为index的参数,代表着该 View 放在 ViewGroup 最后面)。

所以,我们还是继续向里看,看看在 ViewGroup#addViewInLayout 方法中,有没有合适的回调函数。

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        ······
        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 && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
            boolean lastKeepOn = ai.mKeepScreenOn;
            ai.mKeepScreenOn = false;
            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
            if (ai.mKeepScreenOn) {
                needGlobalAttributesUpdate(true);
            }
            ai.mKeepScreenOn = lastKeepOn;
        }

        if (child.isLayoutDirectionInherited()) {
            child.resetRtlProperties();
        }

        dispatchViewAdded(child);

        ······
    }

 这个方法定义在 ViewGroup 类中,笔者删除了一些和本文无关的代码。我们从上往下看重点。
addInArray这个方法,将child加入了ViewGroupchildren中,也就是说,理论上执行完这句代码,你就能通过ViewGroup#getChildAtViewGroup#getChildCount方法来获取child和children的数量。
接下来的一小段代码,child的mParent变量被赋值,理论上执行完这行代码,作为child的View就可以执行View.getParent方法来获取父布局。

 下面的代码我们先跳过一段,直接看AttachInfo下面的if判断内部的代码,里面调用了 View#dispatchAttachedToWindow方法,提到Android中的Window机制,不得不提一篇老罗写的神文 Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析我们进入这个dispatchAttachedToWindow方法去看。

 这个方法定义在View.java中:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ···
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }
        ···
    }

dispatchAttachedToWindow方法里面调用了这样一个回调函数,其中涉及到一个接口。

    /**
     * Interface definition for a callback to be invoked when this view is attached
     * or detached from its window.
     */
    public interface OnAttachStateChangeListener {
        /**
         * Called when the view is attached to a window.
         * @param v The view that was attached
         */
        public void onViewAttachedToWindow(View v);
        /**
         * Called when the view is detached from a window.
         * @param v The view that was detached
         */
        public void onViewDetachedFromWindow(View v);
    }

 从这个接口的注释可以读出来这个方法将在 ViewWindow 建立联系时调用,继续看这个接口的具体使用部分:

    /**
     * Add a listener for attach state changes.
     *
     * This listener will be called whenever this view is attached or detached
     * from a window. Remove the listener using
     * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
     *
     * @param listener Listener to attach
     * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
     */
    public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
        ListenerInfo li = getListenerInfo();
        if (li.mOnAttachStateChangeListeners == null) {
            li.mOnAttachStateChangeListeners
                    = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
        }
        li.mOnAttachStateChangeListeners.add(listener);
    }

 所以,我们率先解决了第二个问题,找到了 View 在什么时候有 mParent 这个问题的答案。

 我们重新回到addViewInner方法,我们继续往下走,发现又调用了ViewGroup#dispatchViewAdded这个方法。我们来看看这个方法相关代码。

    /**
     * Register a callback to be invoked when a child is added to or removed
     * from this view.
     *
     * @param listener the callback to invoke on hierarchy change
     */
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        mOnHierarchyChangeListener = listener;
    }

    void dispatchViewAdded(View child) {
        onViewAdded(child);
        if (mOnHierarchyChangeListener != null) {
            mOnHierarchyChangeListener.onChildViewAdded(this, child);
        }
    }

    /**
     * Called when a new child is added to this ViewGroup. Overrides should always
     * call super.onViewAdded.
     *
     * @param child the added child view
     */
    public void onViewAdded(View child) {
    }

setOnHierarchyChangeListener 这个方法的注释写的很清楚了,将会在有 View添加和删除的时候调用。 dispatchViewAdded 方法内部则是调用了这个接口的回调函数,顺便一提,这个方法还是买一送一的, onViewAdded 方法也具有相同的功能,只是不具有回调的功能。

解决问题

 所以到这里,我们的两个问题就算是得到了解决,你可以在一切可以获得View对象的地方去调用下面这个两个方法,因为其都是公开方法而不是保护方法,顺带一提,addOnAttachStateChangeListener 方法可以被多个地方调用,因为内部维护了一个list去调用。

问题1:

问:如何监听 ViewGroup 添加子 View
答:你可以给你的viewgroup对象添加下面这个监听器,可以监听每一个child添加和删除的过程。

        parent.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
            @Override
            public void onChildViewAdded(View parent, View child) {
                Log.v("onChildViewAdded",child.toString());
            }

            @Override
            public void onChildViewRemoved(View parent, View child) {
                Log.v("onChildViewRemoved",child.toString());
            }
        });
问题2:

问:对于一个 View ,在什么时机可以判断他已经被添加至窗口,可以使用 getParent() 方法来获得父 ViewGroup
你可以给你的View添加下面这个监听器,这个监听器可以声明多次,在每一处你都可以收到这个View添加窗口和被窗口移除的回调。

        view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                Log.v("onViewAttachedToWindow",v.toString());
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                Log.v("onViewDetachedFromWindow",v.toString());
            }
        });
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值