zoukankan      html  css  js  c++  java
  • 自定义view之onMeasure()

    可以说重载onMeasure(),onLayout(),onDraw()三个函数构建了自定义View的外观形象。再加上onTouchEvent()等重载视图的行为,可以构建任何我们需要的可感知到的自定义View。

    本节我们探索自定义View中onMeasure()起到了什么样的作用,题外要插的一句是,Activity框架,View框架中大量的on函数基本上都应用到了Template模式,掌握这一模式对于理解这些框架大有裨益。


    我们知道,不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决定;3.绘制View,onDraw()定义了如何绘制这个View。

    首先我们看看TextView.java中的onMeasure()源码:

    1.     @Override  
    2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    3.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
    4.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
    5.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
    6.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
    7.   
    8.         int width;  
    9.         int height;  
    10.   
    11.         ...   
    12.   
    13.         if (widthMode == MeasureSpec.EXACTLY) {  
    14.             // Parent has told us how big to be. So be it.  
    15.             width = widthSize;  
    16.         } else {  
    17.             if (mLayout != null && mEllipsize == null) {  
    18.                 des = desired(mLayout);  
    19.             }  
    20.   
    21.         ...  
    22.   
    23.         setMeasuredDimension(width, height);   

    首先我们要理解的是widthMeasureSpec, heightMeasureSpec这两个参数是从哪里来的?onMeasure()函数由包含这个View的具体的ViewGroup调用,因此值也是从这个ViewGroup中传入的。这里我直接给出答案:子类View的这两个参数,由ViewGroup中的layout_width,layout_height和padding以及View自身的layout_margin共同决定。权值weight也是尤其需要考虑的因素,有它的存在情况可能会稍微复杂点。

    了解了这两个参数的来源,还要知道这两个值的作用。我们只取heightMeasureSpec作说明。这个值由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低16位为specSize,同样可以由MeasureSpec.getSize()获取。那么specMode和specSize的作用有是什么呢?要想知道这一点,我们需要知道代码中的最后一行,所有的View的onMeasure()的最后一行都会调用setMeasureDimension()函数的作用——这个函数调用中传进去的值是View最终的视图大小。也就是说onMeasure()中之前所作的所有工作都是为了最后这一句话服务的。

    我们知道在ViewGroup中,给View分配的空间大小并不是确定的,有可能随着具体的变化而变化,而这个变化的条件就是传到specMode中决定的,specMode一共有三种可能:

    MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。

    MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。

    MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。

    由TextView中源码也可以知道这个值的设计意义是为了根据ViewGroup中具体能够提供的空间大小来指定子View的视图大小。


    通过以上这些分析,可以知道视图最终的大小由父视图,子视图以及程序员根据需要决定,良好的设计一般会根据子视图的measureSpec设置合适的布局大小。


    讲到上述这些内容,可能已经了解了如何去使用onMeasure来设置我们的视图的大小,但还有一个疑惑的地方,EXACTLY,AT_MOST,UNSPECIFIED和layout_是如何对应的呢?什么情况下对应什么值呢?

    我们通过如下例子,稍作了解:

    1. <LinearLayout 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.     android:gravity="center"  
    6.     android:paddingBottom="@dimen/activity_vertical_margin"  
    7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
    8.     android:paddingRight="@dimen/activity_horizontal_margin"  
    9.     android:paddingTop="@dimen/activity_vertical_margin" >  
    10.   
    11.     <LinearLayout  
    12.         android:layout_width="200dp"  
    13.         android:layout_height="wrap_content"  
    14.         android:paddingTop="20dp"  
    15.         android:layout_marginTop="30dp"  
    16.         android:background="@android:color/darker_gray" >  
    17.         <com.sean.myview.MyView   
    18.             android:layout_width="match_parent"  
    19.             android:layout_height="wrap_content"  
    20.             android:paddingTop="10dp"  
    21.             android:layout_marginTop="15dp"  
    22.             android:background="@android:color/holo_red_light"  
    23.             />  
    24.     </LinearLayout>  
    25.   
    26. </LinearLayout>  

    效果图如下:

    1. package com.sean.myview;  
    2.   
    3. import android.content.Context;  
    4. import android.util.AttributeSet;  
    5. import android.util.Log;  
    6. import android.view.View;  
    7.   
    8. public class MyView extends View {  
    9.   
    10.     public MyView(Context context, AttributeSet attrs) {  
    11.         super(context, attrs);  
    12.         // TODO Auto-generated constructor stub  
    13.     }  
    14.   
    15.     @Override  
    16.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    17.         // TODO Auto-generated method stub  
    18.         Log.d("MyView","------"new Throwable());  
    19.         int speSize = MeasureSpec.getSize(heightMeasureSpec);  
    20.         int speMode = MeasureSpec.getMode(heightMeasureSpec);  
    21.         Log.d("MyView""---speSize = " + speSize + "");  
    22.         Log.d("MyView""---speMode = " + speMode + "");  
    23.         if(speMode == MeasureSpec.AT_MOST){  
    24.             Log.d("MyView""---AT_MOST---");  
    25.         }  
    26.         if(speMode == MeasureSpec.EXACTLY){  
    27.             Log.d("MyView""---EXACTLY---");  
    28.         }  
    29.         if(speMode == MeasureSpec.UNSPECIFIED){  
    30.             Log.d("MyView""---UNSPECIFIED---");  
    31.         }  
    32.         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), speSize);  
    33.     }  
    34.   
    35. }  

    当前情况下打印出的log如下:

    widthMeasureSpecD/MyView  ( 3506): java.lang.Throwable
    D/MyView  ( 3506):     at com.sean.myview.MyView.onMeasure(MyView.java:18)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942)
    D/MyView  ( 3506):     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1411)
    D/MyView  ( 3506):     at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1059)
    D/MyView  ( 3506):     at android.widget.LinearLayout.onMeasure(LinearLayout.java:590)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942)
    D/MyView  ( 3506):     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1411)
    D/MyView  ( 3506):     at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1059)
    D/MyView  ( 3506):     at android.widget.LinearLayout.onMeasure(LinearLayout.java:590)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942)
    D/MyView  ( 3506):     at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.widget.LinearLayout.measureVertical(LinearLayout.java:850)
    D/MyView  ( 3506):     at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942)
    D/MyView  ( 3506):     at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    D/MyView  ( 3506):     at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2193)
    D/MyView  ( 3506):     at android.view.View.measure(View.java:15775)
    D/MyView  ( 3506):     at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2212)
    D/MyView  ( 3506):     at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1291)
    D/MyView  ( 3506):     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1486)
    D/MyView  ( 3506):     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1181)
    D/MyView  ( 3506):     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4942)
    D/MyView  ( 3506):     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:776)
    D/MyView  ( 3506):     at android.view.Choreographer.doCallbacks(Choreographer.java:579)
    D/MyView  ( 3506):     at android.view.Choreographer.doFrame(Choreographer.java:548)
    D/MyView  ( 3506):     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:762)
    D/MyView  ( 3506):     at android.os.Handler.handleCallback(Handler.java:800)
    D/MyView  ( 3506):     at android.os.Handler.dispatchMessage(Handler.java:100)
    D/MyView  ( 3506):     at android.os.Looper.loop(Looper.java:194)
    D/MyView  ( 3506):     at android.app.ActivityThread.main(ActivityThread.java:5391)
    D/MyView  ( 3506):     at java.lang.reflect.Method.invokeNative(Native Method)
    D/MyView  ( 3506):     at java.lang.reflect.Method.invoke(Method.java:525)
    D/MyView  ( 3506):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
    D/MyView  ( 3506):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
    D/MyView  ( 3506):     at dalvik.system.NativeStart.main(Native Method)
    D/MyView  ( 3506): ---speSize = 940
    D/MyView  ( 3506): ---speMode = -2147483648
    D/MyView  ( 3506): ---AT_MOST---

    查看onMeasure()的调用堆栈,然后查看源码,可以知道heightMeasureSpec和widthMeasureSpec的值在ViewRootImpl.java中初始化,而初始化又参考了view和LinearLayout中的属性参数的设置最终得到了heightMeasureSpec和widthMeasureSpec的值。

    而specMode怎么对应呢?我先给出设置对应值的地方,以下代码皆出自ViewRootImpl.java

    1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
    2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);//这里是赋值的代码  

    1. private static int getRootMeasureSpec(int windowSize, int rootDimension) {  
    2.     int measureSpec;  
    3.     switch (rootDimension) {  
    4.   
    5.     case ViewGroup.LayoutParams.MATCH_PARENT:  
    6.         // Window can't resize. Force root view to be windowSize.  
    7.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
    8.         break;  
    9.     case ViewGroup.LayoutParams.WRAP_CONTENT:  
    10.         // Window can resize. Set max size for root view.  
    11.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
    12.         break;  
    13.     default:  
    14.         // Window wants to be an exact size. Force root view to be that size.  
    15.         measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
    16.         break;  
    17.     }  
    18.     return measureSpec;  
    19. }  
    从这里我们基本上可以看出了MATCH_PARENT对应于EXACTLY,WRAP_CONTENT对应于AT_MOST,其他情况也对应于EXACTLY,它和MATCH_PARENT的区别在于size值不一样。现在我们需要知道这个rootDimension即lp.height对应于什么。

    1.     private void performTraversals() {  
    2.         // cache mView since it is used so much below...  
    3. ...  
    4.         WindowManager.LayoutParams lp = mWindowAttributes;  

    1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {  
    2.     synchronized (this) {  
    3.         if (mView == null) {  
    4.             mView = view;  
    5.             mViewLayoutDirectionInitial = mView.getRawLayoutDirection();  
    6.             mFallbackEventHandler.setView(view);  
    7.             mWindowAttributes.copyFrom(attrs);  

    这个View就是ViewGroup中的各个子类视图,这里我们用实验说明,更改MyView中的android:layout_height的值为"match_parent"。log中输出变为了

    D/MyView  ( 4249): ---speSize = 940
    D/MyView  ( 4249): ---speMode = 1073741824
    D/MyView  ( 4249): ---EXACTLY---


    而更改LinearLayout中的这个值对这里的specMode是没有影响的。

    下面我们再来通过实验来验证specSize的值由那些属性决定:

    1. <LinearLayout  
    2.     android:layout_width="200dp"  
    3.     android:layout_height="300dp"  
    4.     android:paddingTop="20dp"  
    5.     android:layout_marginTop="30dp"  
    6.     android:background="@android:color/darker_gray" >  
    7.     <com.sean.myview.MyView   
    8.         android:layout_width="match_parent"  
    9.         android:layout_height="match_parent"  
    10.         android:paddingTop="10dp"  
    11.         android:layout_marginTop="15dp"  
    12.         android:background="@android:color/holo_red_light"  
    13.         />  
    14. </LinearLayout>  

    D/MyView  ( 4959): ---speSize = 530
    D/MyView  ( 4959): ---speMode = 1073741824
    D/MyView  ( 4959): ---EXACTLY---

    先说明一点,xml中用的单位是dp,log中得到的单位是px,我所使用的机子屏幕密度为2.0,只需要进行简单的换算即可px = 2.0 * dp

    我们可以通过控制变量法,逐一改变代码中的LinearLayout和MyView中的相关属性值,看看是哪些影响了specSize,这里我直接给出答案:

    530 = 300 * 2.0 - 20 * 2.0 - 15 * 2.0

    影响specSize height的因素为:父视图的layout_height和paddingTop以及自身的layout_marginTop。但是我们不要忘记有weight时的影响。

      

    onMeasure实例分析

    分类: Android 167人阅读 评论(0) 收藏 举报

               

    上面这个两个视图是Android API中没有给出来的但在来电接听和闹钟被使用到的一个widget视图——GlowPadView.java

    我们通过源码来看看这个View的大小是怎么通过onMeasure来控制的。

    1.     @Override  
    2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    3.         final int minimumWidth = getSuggestedMinimumWidth();  
    4.         final int minimumHeight = getSuggestedMinimumHeight();  
    5.         int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);  
    6.         int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);  
    7.   
    8. ...  
    9.   
    10.         setMeasuredDimension(computedWidth, computedHeight);  
    11.     }  

    1. @Override  
    2. protected int getSuggestedMinimumWidth() {  
    3.     // View should be large enough to contain the background + handle and  
    4.     // target drawable on either edge.  
    5.     return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);  
    6. }  
    mOuterRing为一个指定的圆(由Shape画出,因此给出的是宽和高,相当于圆半径),mOuterRadius为最大的虚线圆的半径,mMaxTargetWidth为图中Zzz图片或其它图片的宽度。这部分值是有开发人员指定的自己期望的自己的视图中属性的大小
    1. private int resolveMeasured(int measureSpec, int desired)  
    2. {  
    3.     int result = 0;  
    4.     int specSize = MeasureSpec.getSize(measureSpec);  
    5.     switch (MeasureSpec.getMode(measureSpec)) {  
    6.         case MeasureSpec.UNSPECIFIED:  
    7.             result = desired;  
    8.             break;  
    9.         case MeasureSpec.AT_MOST:  
    10.             result = Math.min(specSize, desired);  
    11.             break;  
    12.         case MeasureSpec.EXACTLY:  
    13.         default:  
    14.             result = specSize;  
    15.     }  
    16.     return result;  
    17. }  
    我们从width分析,承载这个View的ViewGroup可能有两种情况,一(A)提供的空间比上面我们给出的值大,二(B)是要小(开发中基本上我们不会允许这种状况出现,但设计的逻辑依然必须考虑到这种可能)。然后就要看的是View的layout_width,一是wrap_content,对应AT_MOST,A得到的值为给出的值,B值为父视图的值,虽然我们不希望这样,但父视图只给出了这么些空间,我们也只得这样。

    二是match_parent,对应只有一个父视图的值,这也是符合要求的。


    一个好的设计应该要考虑到各种情况下的使用,而一个可复用的框架设计更是如此,设计之前需要预想到各种可能的应用情况。


     




  • 相关阅读:
    我爱java系列之---【微服务间的认证—Feign拦截器】
    我爱java系列之---【设置权限的三种解决方案】
    581. Shortest Unsorted Continuous Subarray
    129. Sum Root to Leaf Numbers
    513. Find Bottom Left Tree Value
    515. Find Largest Value in Each Tree Row
    155. Min Stack max stack Maxpop O(1) 操作
    painting house
    Minimum Adjustment Cost
    k Sum
  • 原文地址:https://www.cnblogs.com/joelan/p/4770067.html
Copyright © 2011-2022 走看看