GVKun编程网logo

Viewgroup measure child

23

在本文中,我们将给您介绍关于Viewgroupmeasurechild的详细内容,此外,我们还将为您提供关于Andriod从源码的角度详解View,ViewGroup的Touch事件的分发机制、And

在本文中,我们将给您介绍关于Viewgroup measure child的详细内容,此外,我们还将为您提供关于Andriod 从源码的角度详解 View,ViewGroup 的 Touch 事件的分发机制、Android - 自定义控件 - 继承 View 与 ViewGroup 的初步理解、Android View&ViewGroup相关类基础关系图、Android ViewParent是否保证是Viewgroup?的知识。

本文目录一览:

Viewgroup measure child

Viewgroup measure child

理解 MeasureSpec:

  • spec(规格要求,分为 width spec 和 height spec) 是一个 int 型的 ** 组合值 **,包括 spec mode 和 size 两个值;
  • MeasureSpec 是产生 spec 的工具;
  • child 的 spec 是由其自身的布局参数 (layout_width,layout_height 等)以及 parent 的 spec 共同决定的

以宽度计算为例:

childDimension_代表_layout_width

  • 如果 parent 的 spec mode 是 exactly (即 parent 的宽度是确定的值):

    • 如果 child 布局参数 layout_width 是 一个具体的值(大于 0), 那么 child 的 spec mode 就是 exactly, 而且 size 是_childDimension_;
    • 如果 child 布局参数 layout_width 是 match_content, 那么 child 的 spec mode 就是 exactly, 而且 size 是 parent 的 size
    • 如果 child 布局参数 layout_width 是 wrap_content, 那么 child 的 spec mode 就是 at_most, 而且 size 是 parent 的 size (不同的布局容器策略会不同,这里以默认的 ViewGroup 为例);
  • 如果 parent 的 spec mode 是 exactly (即 parent 的宽度是确定的值): * 看代码..

  • 如果 parent 的 spec mode 是 UNSPECIFIED (即 parent 的宽度是确定的值): * 看代码..

参数解释:

  • spec 是这个 vg 的 parent 为此 vg 赋予的 MeasureSpec
  • padding 是此 vg 的 padding;
  • childDimension 是 child 的 layout 模式 (wrap_content,即 - 2、match_parent,即 - 1) 或具体的值;

返回值是为 child 产生的 MeasureSpec

//ViewGroup.java
     * 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

Andriod 从源码的角度详解 View,ViewGroup 的 Touch 事件的分发机制

Andriod 从源码的角度详解 View,ViewGroup 的 Touch 事件的分发机制

ViewGroup 的事件分发机制

我们用手指去触摸 Android 手机屏幕,就会产生一个触摸事件,但是这个触摸事件在底层是怎么分发的呢?这个我还真不知道,这里涉及到操作硬件(手机屏幕)方面的知识,也就是 Linux 内核方面的知识,我也没有了解过这方面的东西,所以我们可能就往上层来分析分析,我们知道 Android 中负责与用户交互,与用户操作紧密相关的四大组件之一是 Activity, 所以我们有理由相信 Activity 中存在分发事件的方法,这个方法就是 dispatchTouchEvent (), 我们先看其源码吧

[java] view plaincopy

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  

  2.   

  3.         // 如果是按下状态就调用 onUserInteraction () 方法,onUserInteraction () 方法  

  4.         // 是个空的方法, 我们直接跳过这里看下面的实现  

  5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {  

  6.             onUserInteraction();  

  7.         }  

  8.           

  9.         if (getWindow().superDispatchTouchEvent(ev)) {  

  10.             return true;  

  11.         }  

  12.           

  13.         //getWindow ().superDispatchTouchEvent (ev) 返回 false,这个事件就交给 Activity  

  14.         // 来处理, Activity 的 onTouchEvent () 方法直接返回了 false  

  15.         return onTouchEvent(ev);  

  16.     }  

这个方法中我们还是比较关心 getWindow () 的 superDispatchTouchEvent () 方法,getWindow () 返回当前 Activity 的顶层窗口 Window 对象,我们直接看 Window API 的 superDispatchTouchEvent () 方法

[java] view plaincopy

  1. /** 

  2.      * Used by custom windows, such as Dialog, to pass the touch screen event 

  3.      * further down the view hierarchy. Application developers should 

  4.      * not need to implement or call this. 

  5.      * 

  6.      */  

  7.     public abstract boolean superDispatchTouchEvent(MotionEvent event);  

这个是个抽象方法,所以我们直接找到其子类来看看 superDispatchTouchEvent () 方法的具体逻辑实现,Window 的唯一子类是 PhoneWindow, 我们就看看 PhoneWindow 的 superDispatchKeyEvent () 方法

[java] view plaincopy

  1. public boolean superDispatchKeyEvent(KeyEvent event) {  

  2.         return mDecor.superDispatchKeyEvent(event);  

  3.     }  

里面直接调用 DecorView 类的 superDispatchKeyEvent 方法,或许很多人不了解 DecorView 这个类,DecorView 是 PhoneWindow 的一个 final 的内部类并且继承 FrameLayout 的,也是 Window 界面的最顶层的 View 对象,这是什么意思呢?别着急,我们接着往下看

我们先新建一个项目,取名 AndroidTouchEvent,然后直接用模拟器运行项目, MainActivity 的布局文件为

[html] view plaincopy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  

  2.     xmlns:tools="http://schemas.android.com/tools"  

  3.     android:layout_width="match_parent"  

  4.     android:layout_height="match_parent"  

  5.     tools:context=".MainActivity" >  

  6.   

  7.     <TextView  

  8.         android:layout_width="wrap_content"  

  9.         android:layout_height="wrap_content"  

  10.         android:layout_centerHorizontal="true"  

  11.         android:layout_centerVertical="true"  

  12.         android:text="@string/hello_world" />  

  13.   

  14. </RelativeLayout>  

利用 hierarchyviewer 工具来查看下 MainActivity 的 View 的层次结构,如下图


我们看到最顶层就是 PhoneWindow$DecorView,接着 DecorView 下面有一个 LinearLayout, LinearLayout 下面有两个 FrameLayout

上面那个 FrameLayout 是用来显示标题栏的,这个 Demo 中是一个 TextView, 当然我们还可以定制我们的标题栏,利用 getWindow ().setFeatureInt (Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx 就是我们自定义标题栏的布局 XML 文件
下面的 FrameLayout 是用来装载 ContentView 的,也就是我们在 Activity 中利用 setContentView () 方法设置的 View,现在我们知道了,原来我们利用 setContentView () 设置 Activity 的 View 的外面还嵌套了这么多的东西

我们来理清下思路,Activity 的最顶层窗体是 PhoneWindow, 而 PhoneWindow 的最顶层 View 是 DecorView,接下来我们就看 DecorView 类的 superDispatchTouchEvent () 方法

[java] view plaincopy

  1. public boolean superDispatchTouchEvent(MotionEvent event) {  

  2.             return super.dispatchTouchEvent(event);  

  3.         }  

在里面调用了父类 FrameLayout 的 dispatchTouchEvent () 方法,而 FrameLayout 中并没有 dispatchTouchEvent () 方法,所以我们直接看 ViewGroup 的 dispatchTouchEvent () 方法

[java] view plaincopy

  1. /** 

  2.     * {@inheritDoc} 

  3.     */  

  4.    @Override  

  5.    public boolean dispatchTouchEvent(MotionEvent ev) {  

  6.        final int action = ev.getAction();  

  7.        final float xf = ev.getX();  

  8.        final float yf = ev.getY();  

  9.        final float scrolledXFloat = xf + mScrollX;  

  10.        final float scrolledYFloat = yf + mScrollY;  

  11.        final Rect frame = mTempRect;  

  12.   

  13.        // 这个值默认是 false, 然后我们可以通过 requestDisallowInterceptTouchEvent (boolean disallowIntercept) 方法  

  14.        // 来改变 disallowIntercept 的值  

  15.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  

  16.   

  17.        // 这里是 ACTION_DOWN 的处理逻辑  

  18.        if (action == MotionEvent.ACTION_DOWN) {  

  19.         // 清除 mMotionTarget, 每次 ACTION_DOWN 都很设置 mMotionTarget 为 null  

  20.            if (mMotionTarget != null) {  

  21.                mMotionTarget = null;  

  22.            }  

  23.   

  24.            //disallowIntercept 默认是 false, 就看 ViewGroup 的 onInterceptTouchEvent () 方法  

  25.            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

  26.                ev.setAction(MotionEvent.ACTION_DOWN);  

  27.                final int scrolledXInt = (int) scrolledXFloat;  

  28.                final int scrolledYInt = (int) scrolledYFloat;  

  29.                final View[] children = mChildren;  

  30.                final int count = mChildrenCount;  

  31.                // 遍历其子 View  

  32.                for (int i = count - 1; i >= 0; i--) {  

  33.                    final View child = children[i];  

  34.                      

  35.                    // 如果该子 View 是 VISIBLE 或者该子 View 正在执行动画, 表示该 View 才  

  36.                    // 可以接受到 Touch 事件  

  37.                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  

  38.                            || child.getAnimation() != null) {  

  39.                     // 获取子 View 的位置范围  

  40.                        child.getHitRect(frame);  

  41.                          

  42.                        // 如 Touch 到屏幕上的点在该子 View 上面  

  43.                        if (frame.contains(scrolledXInt, scrolledYInt)) {  

  44.                            // offset the event to the view''s coordinate system  

  45.                            final float xc = scrolledXFloat - child.mLeft;  

  46.                            final float yc = scrolledYFloat - child.mTop;  

  47.                            ev.setLocation(xc, yc);  

  48.                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  49.                              

  50.                            // 调用该子 View 的 dispatchTouchEvent () 方法  

  51.                            if (child.dispatchTouchEvent(ev))  {  

  52.                                // 如果 child.dispatchTouchEvent (ev) 返回 true 表示  

  53.                             // 该事件被消费了,设置 mMotionTarget 为该子 View  

  54.                                mMotionTarget = child;  

  55.                                // 直接返回 true  

  56.                                return true;  

  57.                            }  

  58.                            // The event didn''t get handled, try the next view.  

  59.                            // Don''t reset the event''s location, it''s not  

  60.                            // necessary here.  

  61.                        }  

  62.                    }  

  63.                }  

  64.            }  

  65.        }  

  66.   

  67.        // 判断是否为 ACTION_UP 或者 ACTION_CANCEL  

  68.        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  

  69.                (action == MotionEvent.ACTION_CANCEL);  

  70.   

  71.        if (isUpOrCancel) {  

  72.            // 如果是 ACTION_UP 或者 ACTION_CANCEL, 将 disallowIntercept 设置为默认的 false  

  73.         // 假如我们调用了 requestDisallowInterceptTouchEvent () 方法来设置 disallowIntercept 为 true  

  74.         // 当我们抬起手指或者取消 Touch 事件的时候要将 disallowIntercept 重置为 false  

  75.         // 所以说上面的 disallowIntercept 默认在我们每次 ACTION_DOWN 的时候都是 false  

  76.            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  

  77.        }  

  78.   

  79.        // The event wasn''t an ACTION_DOWN, dispatch it to our target if  

  80.        // we have one.  

  81.        final View target = mMotionTarget;  

  82.        //mMotionTarget 为 null 意味着没有找到消费 Touch 事件的 View, 所以我们需要调用 ViewGroup 父类的  

  83.        //dispatchTouchEvent () 方法,也就是 View 的 dispatchTouchEvent () 方法  

  84.        if (target == null) {  

  85.            // We don''t have a target, this means we''re handling the  

  86.            // event as a regular view.  

  87.            ev.setLocation(xf, yf);  

  88.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

  89.                ev.setAction(MotionEvent.ACTION_CANCEL);  

  90.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  91.            }  

  92.            return super.dispatchTouchEvent(ev);  

  93.        }  

  94.   

  95.        // 这个 if 里面的代码 ACTION_DOWN 不会执行,只有 ACTION_MOVE  

  96.        //ACTION_UP 才会走到这里, 假如在 ACTION_MOVE 或者 ACTION_UP 拦截的  

  97.        //Touch 事件, 将 ACTION_CANCEL 派发给 target,然后直接返回 true  

  98.        // 表示消费了此 Touch 事件  

  99.        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  

  100.            final float xc = scrolledXFloat - (float) target.mLeft;  

  101.            final float yc = scrolledYFloat - (float) target.mTop;  

  102.            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  103.            ev.setAction(MotionEvent.ACTION_CANCEL);  

  104.            ev.setLocation(xc, yc);  

  105.              

  106.            if (!target.dispatchTouchEvent(ev)) {  

  107.            }  

  108.            // clear the target  

  109.            mMotionTarget = null;  

  110.            // Don''t dispatch this event to our own view, because we already  

  111.            // saw it when intercepting; we just want to give the following  

  112.            // event to the normal onTouchEvent().  

  113.            return true;  

  114.        }  

  115.   

  116.        if (isUpOrCancel) {  

  117.            mMotionTarget = null;  

  118.        }  

  119.   

  120.        // finally offset the event to the target''s coordinate system and  

  121.        // dispatch the event.  

  122.        final float xc = scrolledXFloat - (float) target.mLeft;  

  123.        final float yc = scrolledYFloat - (float) target.mTop;  

  124.        ev.setLocation(xc, yc);  

  125.   

  126.        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

  127.            ev.setAction(MotionEvent.ACTION_CANCEL);  

  128.            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  129.            mMotionTarget = null;  

  130.        }  

  131.   

  132.        // 如果没有拦截 ACTION_MOVE, ACTION_DOWN 的话,直接将 Touch 事件派发给 target  

  133.        return target.dispatchTouchEvent(ev);  

  134.    }  

这个方法相对来说还是蛮长,不过所有的逻辑都写在一起,看起来比较方便,接下来我们就具体来分析一下


我们点击屏幕上面的 TextView 来看看 Touch 是如何分发的,先看看 ACTION_DOWN

在 DecorView 这一层会直接调用 ViewGroup 的 dispatchTouchEvent (), 先看 18 行,每次 ACTION_DOWN 都会将 mMotionTarget 设置为 null, mMotionTarget 是什么?我们先不管,继续看代码,走到 25 行, disallowIntercept 默认为 false,我们再看 ViewGroup 的 onInterceptTouchEvent () 方法

[java] view plaincopy

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  

  2.       return false;  

  3.   }  

直接返回 false, 继续往下看,循环遍历 DecorView 里面的 Child,从上面的 MainActivity 的层次结构图我们可以看出,DecorView 里面只有一个 Child 那就是 LinearLayout, 第 43 行判断 Touch 的位置在不在 LinnearLayout 上面,这是毫无疑问的,所以直接跳到 51 行, 调用 LinearLayout 的 dispatchTouchEvent () 方法,LinearLayout 也没有 dispatchTouchEvent () 这个方法,所以也是调用 ViewGroup 的 dispatchTouchEvent () 方法,所以这个方法卡在 51 行没有继续下去,而是去先执行 LinearLayout 的 dispatchTouchEvent ()

LinearLayout 调用 dispatchTouchEvent () 的逻辑跟 DecorView 是一样的,所以也是遍历 LinearLayout 的两个 FrameLayout,判断 Touch 的是哪个 FrameLayout,很明显是下面那个,调用下面那个 FrameLayout 的 dispatchTouchEvent (),  所以 LinearLayout 的 dispatchTouchEvent () 卡在 51 也没继续下去

继续调用 FrameLayout 的 dispatchTouchEvent () 方法,和上面一样的逻辑,下面的 FrameLayout 也只有一个 Child,就是 RelativeLayout,FrameLayout 的 dispatchTouchEvent () 继续卡在 51 行,先执行 RelativeLayout 的 dispatchTouchEvent () 方法

执行 RelativeLayout 的 dispatchTouchEvent () 方法逻辑还是一样的,循环遍历 RelativeLayout 里面的孩子,里面只有一个 TextView, 所以这里就调用 TextView 的 dispatchTouchEvent (), TextView 并没有 dispatchTouchEvent () 这个方法,于是找 TextView 的父类 View,在看 View 的 dispatchTouchEvent () 的方法之前,我们先理清下上面这些 ViewGroup 执行 dispatchTouchEvent () 的思路,我画了一张图帮大家理清下(这里没有画出 onInterceptTouchEvent()方法)

上面的 ViewGroup 的 Touch 事件分发就告一段落先,因为这里要调用 TextView(也就是 View)的 dispatchTouchEvent () 方法,所以我们先分析 View 的 dispatchTouchEvent () 方法在将上面的继续下去


View 的 Touch 事件分发机制

我们还是先看 View 的 dispatchTouchEvent () 方法的源码

[java] view plaincopy

  1. public boolean dispatchTouchEvent(MotionEvent event) {  

  2.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  

  3.                 mOnTouchListener.onTouch(this, event)) {  

  4.             return true;  

  5.         }  

  6.         return onTouchEvent(event);  

  7.     }  

在这个方法里面,先进行了一个判断

第一个条件 mOnTouchListener 就是我们调用 View 的 setTouchListener () 方法设置的

第二个条件是判断 View 是否为 enabled 的, View 一般都是 enabled, 除非你手动设置为 disabled

第三个条件就是 OnTouchListener 接口的 onTouch () 方法的返回值了,如果调用了 setTouchListener () 设置 OnTouchListener,并且 onTouch () 方法返回 true,View 的 dispatchTouchEvent () 方法就直接返回 true, 否则就执行 View 的 onTouchEvent () 并返回 View 的 onTouchEvent () 的值
现在你了解了 View 的 onTouchEvent () 方法和 onTouch () 的关系了吧,为什么 Android 提供了处理 Touch 事件 onTouchEvent () 方法还要增加一个 OnTouchListener 接口呢?我觉得 OnTouchListener 接口是对处理 Touch 事件的屏蔽和扩展作用吧,屏蔽作用我就不举例介绍了,看上面的源码就知道了,我就说下扩展吧,比如我们要打印 View 的 Touch 的点的坐标,我们可以自定义一个 View 如下

[java] view plaincopy

  1. public class CustomView extends View {  

  2.       

  3.     public CustomView(Context context, AttributeSet attrs) {  

  4.         super(context, attrs);  

  5.     }  

  6.   

  7.     public CustomView(Context context, AttributeSet attrs, int defStyle) {  

  8.         super(context, attrs, defStyle);  

  9.     }  

  10.   

  11.     @Override  

  12.     public boolean onTouchEvent(MotionEvent event) {  

  13.           

  14.         Log.i("tag""X 的坐标 = " + event.getX() + " Y 的坐标 = " + event.getY());  

  15.           

  16.         return super.onTouchEvent(event);  

  17.     }  

  18.   

  19. }  

也可以直接对 View 设置 OnTouchListener 接口,在 return 的时候调用下 v.onTouchEvent ()

[java] view plaincopy

  1. view.setOnTouchListener(new OnTouchListener() {  

  2.               

  3.             @Override  

  4.             public boolean onTouch(View v, MotionEvent event) {  

  5.                   

  6.                 Log.i("tag""X 的坐标 = " + event.getX() + " Y 的坐标 = " + event.getY());  

  7.                   

  8.                 return v.onTouchEvent(event);  

  9.             }  

  10.         });  

这样子也实现了我们所需要的功能,所以我认为 OnTouchListener 是对 onTouchEvent () 方法的一个屏蔽和扩展作用,假如你有不一样的理解,你也可以告诉我下,这里就不纠结这个了。

我们再看 View 的 onTouchEvent () 方法

[java] view plaincopy

  1. public boolean onTouchEvent(MotionEvent event) {  

  2.       final int viewFlags = mViewFlags;  

  3.   

  4.       if ((viewFlags & ENABLED_MASK) == DISABLED) {  

  5.           return (((viewFlags & CLICKABLE) == CLICKABLE ||  

  6.                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  

  7.       }  

  8.   

  9.       // 如果设置了 Touch 代理,就交给代理来处理,mTouchDelegate 默认是 null  

  10.       if (mTouchDelegate != null) {  

  11.           if (mTouchDelegate.onTouchEvent(event)) {  

  12.               return true;  

  13.           }  

  14.       }  

  15.   

  16.       // 如果 View 是 clickable 或者 longClickable 的 onTouchEvent 就返回 true, 否则返回 false  

  17.       if (((viewFlags & CLICKABLE) == CLICKABLE ||  

  18.               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

  19.           switch (event.getAction()) {  

  20.               case MotionEvent.ACTION_UP:  

  21.                   boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

  22.                   if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  

  23.                       boolean focusTaken = false;  

  24.                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  

  25.                           focusTaken = requestFocus();  

  26.                       }  

  27.   

  28.                       if (!mHasPerformedLongPress) {  

  29.                           removeLongPressCallback();  

  30.   

  31.                           if (!focusTaken) {  

  32.                               if (mPerformClick == null) {  

  33.                                   mPerformClick = new PerformClick();  

  34.                               }  

  35.                               if (!post(mPerformClick)) {  

  36.                                   performClick();  

  37.                               }  

  38.                           }  

  39.                       }  

  40.   

  41.                       if (mUnsetPressedState == null) {  

  42.                           mUnsetPressedState = new UnsetPressedState();  

  43.                       }  

  44.   

  45.                       if (prepressed) {  

  46.                           mPrivateFlags |= PRESSED;  

  47.                           refreshDrawableState();  

  48.                           postDelayed(mUnsetPressedState,  

  49.                                   ViewConfiguration.getPressedStateDuration());  

  50.                       } else if (!post(mUnsetPressedState)) {  

  51.                           mUnsetPressedState.run();  

  52.                       }  

  53.                       removeTapCallback();  

  54.                   }  

  55.                   break;  

  56.   

  57.               case MotionEvent.ACTION_DOWN:  

  58.                   if (mPendingCheckForTap == null) {  

  59.                       mPendingCheckForTap = new CheckForTap();  

  60.                   }  

  61.                   mPrivateFlags |= PREPRESSED;  

  62.                   mHasPerformedLongPress = false;  

  63.                   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  

  64.                   break;  

  65.   

  66.               case MotionEvent.ACTION_CANCEL:  

  67.                   mPrivateFlags &= ~PRESSED;  

  68.                   refreshDrawableState();  

  69.                   removeTapCallback();  

  70.                   break;  

  71.   

  72.               case MotionEvent.ACTION_MOVE:  

  73.                   final int x = (int) event.getX();  

  74.                   final int y = (int) event.getY();  

  75.   

  76.                   // 当手指在 View 上面滑动超过 View 的边界,  

  77.                   int slop = mTouchSlop;  

  78.                   if ((x < 0 - slop) || (x >= getWidth() + slop) ||  

  79.                           (y < 0 - slop) || (y >= getHeight() + slop)) {  

  80.                       // Outside button  

  81.                       removeTapCallback();  

  82.                       if ((mPrivateFlags & PRESSED) != 0) {  

  83.                           removeLongPressCallback();  

  84.   

  85.                           mPrivateFlags &= ~PRESSED;  

  86.                           refreshDrawableState();  

  87.                       }  

  88.                   }  

  89.                   break;  

  90.           }  

  91.           return true;  

  92.       }  

  93.   

  94.       return false;  

  95.   }  

这个方法也是比较长的,我们先看第 4 行,如果一个 View 是 disabled, 并且该 View 是 Clickable 或者 longClickable, onTouchEvent () 就不执行下面的代码逻辑直接返回 true, 表示该 View 就一直消费 Touch 事件,如果一个 enabled 的 View, 并且是 clickable 或者 longClickable 的,onTouchEvent () 会执行下面的代码逻辑并返回 true,综上,一个 clickable 或者 longclickable 的 View 是一直消费 Touch 事件的,而一般的 View 既不是 clickable 也不是 longclickable 的 (即不会消费 Touch 事件,只会执行 ACTION_DOWN 而不会执行 ACTION_MOVE 和 ACTION_UP) Button 是 clickable 的,可以消费 Touch 事件,但是我们可以通过 setClickable () 和 setLongClickable () 来设置 View 是否为 clickable 和 longClickable。当然还可以通过重写 View 的 onTouchEvent () 方法来控制 Touch 事件的消费与否

我们在看 57 行的 ACTION_DOWN, 新建一个 CheckForTap,我们看看 CheckForTap 是什么

[java] view plaincopy

  1. private final class CheckForTap implements Runnable {  

  2.        public void run() {  

  3.            mPrivateFlags &= ~PREPRESSED;  

  4.            mPrivateFlags |= PRESSED;  

  5.            refreshDrawableState();  

  6.            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  

  7.                postCheckForLongClick(ViewConfiguration.getTapTimeout());  

  8.            }  

  9.        }  

  10.    }  

原来是个 Runnable 对象,然后使用 Handler 的 post 方法延时 ViewConfiguration.getTapTimeout () 执行 CheckForTap 的 run () 方法,在 run 方法中先判断 view 是否 longClickable 的,一般的 View 都是 false, postCheckForLongClick (ViewConfiguration.getTapTimeout ()) 这段代码就是执行长按的逻辑的代码,只有当我们设置为 longClickble 才会去执行 postCheckForLongClick (ViewConfiguration.getTapTimeout ()),这里我就不介绍了

由于考虑到文章篇幅的问题,我就不继续分析 View 的长按事件和点击事件了,在这里我直接得出结论吧

长按事件是在 ACTION_DOWN 中执行,点击事件是在 ACTION_UP 中执行,要想执行长按事件,这个 View 必须是 longclickable 的, 也许你会纳闷,一般的 View 不是 longClickable 为什么也会执行长按事件呢?我们要执行长按事件必须要调用 setOnLongClickListener () 设置 OnLongClickListener 接口,我们看看这个方法的源码

[java] view plaincopy

  1. public void setOnLongClickListener(OnLongClickListener l) {  

  2.      if (!isLongClickable()) {  

  3.          setLongClickable(true);  

  4.      }  

  5.      mOnLongClickListener = l;  

  6.  }  

看到没有,如果这个 View 不是 longClickable 的,我们就调用 setLongClickable (true) 方法设置为 longClickable 的,所以才会去执行长按方法 onLongClick ();

要想执行点击事件,这个 View 就必须要消费 ACTION_DOWN 和 ACTION_MOVE 事件,并且没有设置 OnLongClickListener 的情况下,如果设置了 OnLongClickListener 的情况下,需要 onLongClick () 返回 false 才能执行到 onClick () 方法,也许你又会纳闷,一般的 View 默认是不消费 touch 事件的,这不是和你上面说的相违背嘛,我们要向执行点击事件必须要调用 setOnClickListener () 来设置 OnClickListener 接口,我们看看这个方法的源码就知道了

[java] view plaincopy

  1. public void setOnClickListener(OnClickListener l) {  

  2.      if (!isClickable()) {  

  3.          setClickable(true);  

  4.      }  

  5.      mOnClickListener = l;  

  6.  }  

所以说一个 enable 的并且是 clickable 的 View 是一直消费 touch 事件的,所以才会执行到 onClick()方法



对于 View 的 Touch 事件的分发机制算是告一段落了,从上面我们可以得出 TextView 的 dispatchTouchEvent () 的返回 false 的,即不消费 Touch 事件。我们就要往上看 RelativeLayout 的 dispatchTouchEvent () 方法的 51 行,由于 TextView.dispatchTouchEvent () 为 false, 导致 mMotionTarget 没有被赋值,还是 null, 继续往下走执行 RelativeLayout 的 dispatchTouchEvent () 方法,来到第 84 行, 判断 target 是否为 null,这个 target 就是 mMotionTarget,满足条件,执行 92 行的 super.dispatchTouchEvent (ev) 代码并返回, 这里调用的是 RelativeLayout 父类 View 的 dispatchTouchEvent () 方法,由于 RelativeLayout 没有设置 onTouchListener, 所以这里直接调用 RelativeLayout (其实就是 View, 因为 RelativeLayout 没有重写 onTouchEvent ()) 的 onTouchEvent () 方法 由于 RelativeLayout 既不是 clickable 的也是 longClickable 的,所以其 onTouchEvent () 方法 false, RelativeLayout 的 dispatchTouchEvent () 也是返回 false, 这里就执行完了 RelativeLayout 的 dispatchTouchEvent () 方法

继续执行 FrameLayout 的 dispatchTouchEvent () 的第 51 行,由于 RelativeLayout.dispatchTouchEvent () 返回的是 false, 跟上面的逻辑是一样的, 也是执行到 92 行的 super.dispatchTouchEvent (ev) 代码并返回,然后执行 FrameLayout 的 onTouchEvent () 方法,而 FrameLayout 的 onTouchEvent () 也是返回 false, 所以 FrameLayout 的 dispatchTouchEvent () 方法返回 false, 执行完毕 FrameLayout 的 dispatchTouchEvent () 方法

在上面的我就不分析了,大家自行分析一下,跟上面的逻辑是一样的,我直接画了个图来帮大家理解下(这里没有画出 onInterceptTouchEvent()方法)

所以我们点击屏幕上面的 TextView 的事件分发流程是上图那个样子的,表示 Activity 的 View 都不消费 ACTION_DOWN 事件,所以就不能在触发 ACTION_MOVE, ACTION_UP 等事件了,具体是为什么?我还不太清楚,毕竟从 Activity 到 TextView 这一层是分析不出来的,估计是在底层实现的。


但如果将 TextView 换成 Button,流程是不是还是这个样子呢?答案不是,我们来分析分析一下,如果是 Button , Button 是一个 clickable 的 View,onTouchEvent () 返回 true, 表示他一直消费 Touch 事件,所以 Button 的 dispatchTouchEvent () 方法返回 true, 回到 RelativeLayout 的 dispatchTouchEvent () 方法的 51 行,满足条件,进入到 if 方法体,设置 mMotionTarget 为 Button,然后直接返回 true, RelativeLayout 的 dispatchTouchEvent () 方法执行完毕, 不会调用到 RelativeLayout 的 onTouchEvent () 方法

然后到 FrameLayout 的 dispatchTouchEvent () 方法的 51 行,由于 RelativeLayout.dispatchTouchEvent () 返回 true, 满足条件,进入 if 方法体,设置 mMotionTarget 为 RelativeLayout,注意下,这里的 mMotionTarget 跟 RelativeLayout 的 dispatchTouchEvent () 方法的 mMotionTarget 不是同一个哦,因为他们是不同的方法中的,然后返回 true

同理 FrameLayout 的 dispatchTouchEvent () 也是返回 true, DecorView 的 dispatchTouchEvent () 方法也返回 true, 还是画一个流程图(这里没有画出 onInterceptTouchEvent()方法)给大家理清下

从上面的流程图得出一个结论,Touch 事件是从顶层的 View 一直往下分发到手指按下的最里面的 View,如果这个 View 的 onTouchEvent () 返回 false, 即不消费 Touch 事件,这个 Touch 事件就会向上找父布局调用其父布局的 onTouchEvent () 处理,如果这个 View 返回 true, 表示消费了 Touch 事件,就不调用父布局的 onTouchEvent ()


接下来我们用一个自定义的 ViewGroup 来替换 RelativeLayout, 自定义 ViewGroup 代码如下

[java] view plaincopy

  1. package com.example.androidtouchevent;  

  2.   

  3. import android.content.Context;  

  4. import android.util.AttributeSet;  

  5. import android.view.MotionEvent;  

  6. import android.widget.RelativeLayout;  

  7.   

  8. public class CustomLayout extends RelativeLayout {  

  9.       

  10.     public CustomLayout(Context context, AttributeSet attrs) {  

  11.         super(context, attrs, 0);  

  12.     }  

  13.   

  14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

  15.         super(context, attrs, defStyle);  

  16.     }  

  17.   

  18.     @Override  

  19.     public boolean onTouchEvent(MotionEvent event) {  

  20.         return super.onTouchEvent(event);  

  21.     }  

  22.   

  23.     @Override  

  24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

  25.         return true;  

  26.     }  

  27.       

  28.   

  29. }  

我们就重写了 onInterceptTouchEvent (), 返回 true, RelativeLayout 默认是返回 false, 然后再 CustomLayout 布局中加一个 Button , 如下图

我们这次不从 DecorView 的 dispatchTouchEvent () 分析了,直接从 CustomLayout 的 dispatchTouchEvent () 分析

我们先看 ACTION_DOWN 来到 25 行,由于我们重写了 onInterceptTouchEvent () 返回 true, 所以不走这个 if 里面,直接往下看代码,来到 84 行, target 为 null, 所以进入 if 方法里面,直接调用 super.dispatchTouchEvent () 方法, 也就是 View 的 dispatchTouchEvent () 方法,而在 View 的 dispatchTouchEvent () 方法中是直接调用 View 的 onTouchEvent () 方法,但是 CustomLayout 重写了 onTouchEvent (),所以这里还是调用 CustomLayout 的 onTouchEvent (), 这个方法返回 false, 不消费 Touch 事件,所以不会在触发 ACTION_MOVE,ACTION_UP 等事件了,这里我再画一个流程图吧(含有 onInterceptTouchEvent () 方法的)


好了,就分析到这里吧,差不多分析完了,还有一种情况没有分析到,例如我将 CustomLayout 的代码改成下面的情形,Touch 事件又是怎么分发的呢?我这里就不带大家分析了

[java] view plaincopy

  1. package com.example.androidtouchevent;  

  2.   

  3. import android.content.Context;  

  4. import android.util.AttributeSet;  

  5. import android.view.MotionEvent;  

  6. import android.widget.RelativeLayout;  

  7.   

  8. public class CustomLayout extends RelativeLayout {  

  9.       

  10.     public CustomLayout(Context context, AttributeSet attrs) {  

  11.         super(context, attrs, 0);  

  12.     }  

  13.   

  14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

  15.         super(context, attrs, defStyle);  

  16.     }  

  17.   

  18.     @Override  

  19.     public boolean onTouchEvent(MotionEvent event) {  

  20.         return super.onTouchEvent(event);  

  21.     }  

  22.   

  23.     @Override  

  24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

  25.         if(ev.getAction() == MotionEvent.ACTION_MOVE){  

  26.             return true;  

  27.         }  

  28.         return super.onInterceptTouchEvent(ev);  

  29.     }  

  30.       

  31.   

  32. }  

这篇文章的篇幅有点长,如果你想了解 Touch 事件的分发机制,你一定要认真看完,下面来总结一下吧

1.Activity 的最顶层 Window 是 PhoneWindow,PhoneWindow 的最顶层 View 是 DecorView

2. 一个 clickable 或者 longClickable 的 View 会永远消费 Touch 事件,不管他是 enabled 还是 disabled 的

3.View 的长按事件是在 ACTION_DOWN 中执行,要想执行长按事件该 View 必须是 longClickable 的,并且不能产生 ACTION_MOVE

4.View 的点击事件是在 ACTION_UP 中执行,想要执行点击事件的前提是消费了 ACTION_DOWN 和 ACTION_MOVE, 并且没有设置 OnLongClickListener 的情况下,如设置了 OnLongClickListener 的情况,则必须使 onLongClick () 返回 false

5. 如果 View 设置了 onTouchListener 了,并且 onTouch () 方法返回 true,则不执行 View 的 onTouchEvent () 方法,也表示 View 消费了 Touch 事件,返回 false 则继续执行 onTouchEvent ()

6.Touch 事件是从最顶层的 View 一直分发到手指 touch 的最里层的 View, 如果最里层 View 消费了 ACTION_DOWN 事件(设置 onTouchListener,并且 onTouch () 返回 true 或者 onTouchEvent () 方法返回 true)才会触发 ACTION_MOVE,ACTION_UP 的发生,如果某个 ViewGroup 拦截了 Touch 事件,则 Touch 事件交给 ViewGroup 处理

7.Touch 事件的分发过程中,如果消费了 ACTION_DOWN, 而在分发 ACTION_MOVE 的时候,某个 ViewGroup 拦截了 Touch 事件,就像上面那个自定义 CustomLayout,则会将 ACTION_CANCEL 分发给该 ViewGroup 下面的 Touch 到的 View, 然后将 Touch 事件交给 ViewGroup 处理,并返回 true

Android - 自定义控件 - 继承 View 与 ViewGroup 的初步理解

Android - 自定义控件 - 继承 View 与 ViewGroup 的初步理解

继承 View 需要走的流程是:

            1. 构造实例化, public ChildView (Context context, @Nullable AttributeSet attrs)

            2. 测量自身的高和宽 onMeasure-->setMeasuredDimension (宽,高)

            3.onDraw 绘制,需要 X 轴,Y 轴

 

继承 ViewGroup 需要走的流程是:

            1. 构造实例化, public ChildView (Context context, @Nullable AttributeSet attrs)

            2.onMeasure 测量子控件的高和宽,子控件.measure (宽,高),而自己的高和宽交给父控件去测量,因为我是父控件的子控件

            3.onLayout 给子控件排版指定位置

 

布局文件,指定自定义类:

<!-- 继承View 与 继承ViewGroup的初步理解 -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="400dp"
    android:layout_height="500dp">

    <!-- 爷爷类,爷爷类有一个孩子(爸爸类) -->
    <custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#3300cc"
        android:layout_centerInParent="true"
        > <!--
            虽然android:layout_centerInParent="true"属性可以去居中
            但这是Android RelativeLayout 对爷爷类进行了居中的排版固定位置处理
         -->

        <!-- 爸爸类,爸爸类有一个孩子(孩子类) -->
        <custom.view.upgrade.view_viewgroup_theory.FatherViewGroup
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#00ccff">

            <!-- 孩子类目前不包含孩子 -->
            <custom.view.upgrade.view_viewgroup_theory.ChildView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:background="#cc3300"/>

        </custom.view.upgrade.view_viewgroup_theory.FatherViewGroup>

    </custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup>
    

</RelativeLayout>

 

爷爷类,GrandpaViewGroup:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * 爷爷类,爷爷类有自己的孩子(爸爸类) ----> 爸爸类有自己的孩子(孩子类)
 */
public class GrandpaViewGroup extends ViewGroup {

    private static final String TAG = GrandpaViewGroup.class.getSimpleName();

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public GrandpaViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 爸爸类
    private View fatherViewGroup;

    /**
     * 当xml布局完成加载后,调用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 获取子控件(爸爸类)
        fatherViewGroup = getChildAt(0);
    }

    /**
     * 由于当前是ViewGroup所以测量子控件的宽和高
     * ,如果当前是View就是此类自己的高和宽
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 测量子控件(爸爸类)的宽和高
        // 宽和高就是 爸爸类在xml布局中设置的值
        fatherViewGroup.measure(fatherViewGroup.getLayoutParams().width, fatherViewGroup.getLayoutParams().height);

        // 想获取测量后子控件(爸爸类)的高和宽,是无法获取到的,因为子控件(爸爸类)是ViewGroup,拿到测量后的高和宽需要 View-->setMeasuredDimension()
        // 测试下:子控件(爸爸类)的高和宽
        Log.d(TAG, "fatherViewGroup.getMeasuredWidth():" + fatherViewGroup.getMeasuredWidth() +
                         " fatherViewGroup.getMeasuredHeight():" + fatherViewGroup.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只给子控件指定位置的
     * @param changed 当发生改变
     * @param l 左边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param t 上边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param r 右边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param b 底边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     *
     * 父控件必须排版了layout此类的位置,此onLayout方法才会执行,否则不执行
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 测试下:获取自身当前GrandpaViewGroup测量后的宽和高
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 给子控件(爸爸类)指定位置
        // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(GrandpaViewGroup爷爷类)宽度的一半 减 子控件(爸爸类)的一半就居中了
        int fatherL = (measuredWidth / 2) - (fatherViewGroup.getLayoutParams().width / 2);
        int fatherT = (measuredHeight / 2) - (fatherViewGroup.getLayoutParams().height / 2);

        // L位置增加了,R位置也需要增加
        int fatherR = fatherViewGroup.getLayoutParams().width + fatherL;
        int fatherB = fatherViewGroup.getLayoutParams().height + fatherT;
        fatherViewGroup.layout(fatherL, fatherT, fatherR, fatherB);
    }

    /**
     * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
     * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
     */
}

 

爸爸类,FatherViewGroup:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * 爸爸类,爸爸类有自己的孩子
 */
public class FatherViewGroup extends ViewGroup {

    private static final String TAG = FatherViewGroup.class.getSimpleName();

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public FatherViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 子控件(孩子类)
    private View childView;

    /**
     * 当xml布局完成加载后,调用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        childView = getChildAt(0);
    }

    private int w;
    private int h;

    /**
     * 测量子控件(孩子类)的高和宽
     * @param widthMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的宽
     * @param heightMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的高
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 可以获取模式
        MeasureSpec.getMode(widthMeasureSpec);
        MeasureSpec.getMode(heightMeasureSpec);

        /**
         * 可以获取父类(爷爷类)在测量方法--->
         * fatherViewGroup.measure(fatherViewGroup.getLayoutParams()
         * .width, fatherViewGroup.getLayoutParams().height);
         * 传递过来的高和宽
         */
        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        Log.d(TAG, " w:" + w + " h:" + h);

        // 开始测量子控件(孩子类)
        // 宽高都是孩子类在xml布局设置的宽高
        childView.measure(childView.getLayoutParams().width, childView.getLayoutParams().height);

        // 测试下:子控件(孩子类)的高和宽
        /**
         * 注意:为什么在这里又可以获取到子控件的宽和高呢?
         *      因为子控件是View 并且这个View在测量方法中执行了 setMeasuredDimension(w, h);
         */
        Log.d(TAG, "childView.getMeasuredWidth():" + childView.getMeasuredWidth() +
                " childView.getMeasuredHeight():" + childView.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只给子控件指定位置的
     * @param changed 当发生改变
     * @param l 左边线距离左边距离
     * @param t 上边线距离上边距离
     * @param r 右边线距离左边距离
     * @param b 底边线距离上边距离
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 测试下:获取自身当前FatherViewGroup测量后的宽和高
        // 注意:在这里无法获取,还不知道是什么原因!!!,
        // 爷爷类可以获取到是因为爷爷类的父类控件是系统的RelativeLayout
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "爸爸类 >>> measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 给子控件(爸爸类)指定位置
        // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(FatherViewGroup爸爸类)宽度的一半 减 子控件(孩子类)的一本就居中了
        int childL = (w / 2) - (childView.getMeasuredWidth() / 2);
        int childT = (h / 2) - (childView.getMeasuredHeight() / 2);

        // L位置增加了,R位置也需要增加
        int childR = childView.getMeasuredWidth() + childL;
        int childB = childView.getMeasuredHeight() + childT;

        childView.layout(childL, childT, childR, childB);
    }

    /**
     * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
     * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
     */
}

 

孩子类,ChildView:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * 孩子类,孩子类暂时还没有自己的孩子,所有是继承View
 */
public class ChildView extends View {

    private static final String CONTEXT = "child";

    // 创建画笔把文字画到画布中去
    private Paint mPaint;

    private Rect rect;

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public ChildView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mPaint =  new Paint();
        // 画笔防锯齿
        mPaint.setAntiAlias(true);
        // 画笔白色
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(40);

        rect = new Rect();
    }

    /**
     * 此高宽是父控件(爸爸类)传递过来的高和宽
     */
    private int w;
    private int h;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(w, h);
    }

    /**
     * 绘制的方法
     * @param canvas 画布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 拿到自身宽度的一半 减 文字宽度的一半

        mPaint.getTextBounds(CONTEXT, 0, CONTEXT.length(), rect);

        int x = (w / 2) - (rect.width() / 2);
        int y = (h / 2) - (rect.height() / 2) + rect.height();
        canvas.drawText(CONTEXT, x, y , mPaint);
    }
}

 

效果图:

 

Android View&ViewGroup相关类基础关系图

Android View&ViewGroup相关类基础关系图


        一个很乱的树形结构,以View的直接子类集合红色方框为跟节点。

Android ViewParent是否保证是Viewgroup?

Android ViewParent是否保证是Viewgroup?

是否存在View.getParent()返回不属于ViewGroup类型的对象的实际情况?或者我可以安全地投射它而不首先检查它的类型,如下面的代码示例中所示?

 if (getParent() == null){
     throw new IllegalStateException("View does not have a parent,it cannot be rootview!");
 }
 ViewGroup parent = (ViewGroup) getParent();
最佳答案
如果比较ViewGroup和ViewParent的直接子类和间接子类,它们看起来是一样的(考虑到ViewGroup本身).仍然有可能在某些自定义库中,您可以从getParent()获取不是ViewGroup的ViewParent.这是真的吗?所以 – 如果你不能确定父类型,你最好检查一下.
– 在普通应用程序中,您通常了解父母是或可以 – 所以您可以跳过检查

关于Viewgroup measure child的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于Andriod 从源码的角度详解 View,ViewGroup 的 Touch 事件的分发机制、Android - 自定义控件 - 继承 View 与 ViewGroup 的初步理解、Android View&ViewGroup相关类基础关系图、Android ViewParent是否保证是Viewgroup?的相关信息,请在本站寻找。

本文标签: