五、View的工作原理

来源:互联网 发布:mac显示wifi未安装硬件 编辑:程序博客网 时间:2024/05/22 00:09

1.ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl和Decor-View建立关联。

DecorView作为顶级View,一般情况下,它的内部会包含一个竖直的LinearLayout,LinearLayout有上下两部分(具体看Android版本和当前的主题),上面是标题栏下面是内容栏。在Activity我们通过setContentView所设置的布局就是被添加到内容栏中的,而内容栏的id是content,布局是FrameLayout。
得到content:
findViewById(R.android.id.content);
得到我们设置的view:
findViewById(R.android.id.content).getChildAt(0);

DecorView是一个FrameLayout。

2.MeasureSpec

MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode代表测量模式,而SpecSize是指在某种测量模式下的规格大小。

MeasureSpec的简单说明
http://blog.csdn.net/chunqiuwei/article/details/44515345

Java 位运算(移位、位与、或、异或、非)
http://blog.csdn.net/xiaochunyong/article/details/7748713

MeasureSpec的代码很少:

    public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        public static final int EXACTLY     = 1 << MODE_SHIFT;        public static final int AT_MOST     = 2 << MODE_SHIFT;        public static int makeMeasureSpec( int size, int mode) {            if ( sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return ( size & ~ MODE_MASK) | ( mode & MODE_MASK);            }        }        public static int getMode(int measureSpec) {            return ( measureSpec & MODE_MASK);        }        public static int getSize(int measureSpec) {            return ( measureSpec & ~ MODE_MASK);        }        static int adjust(int measureSpec, int delta) {            return makeMeasureSpec(getSize (measureSpec + delta ), getMode(measureSpec));        }    }

把SpecMode和SpecSize打包成int值,提供解包方法,方便操作。

SpecMode:
UNSPECIFIED
父容器不对view有任何限制,要多大,给多大,这种情况一般用于系统部,表示一种测量状态。

EXACTLY
父容器已经检测出view所需要的精确大小,这个时候,view的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体的数值这两种模式。

AT_MOST
父容器指定了一个可用大小即SpecSize,view的大小不能大于这个值,具体是什么值,要看不同view的具体实现,它对应于LayoutParams中的warp_content。

表 普通view的MeasureSpec的创建规则:
普通view的MeasureSpec的创建规则
解释:当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小。

当view的宽高是match_parent的时候,如果父容器的模式是精确模式,那么view也是精确模式并且其大小不会超过父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且大小不会超过父容器的剩余空间。

当view的宽高是warp_content时,不管父容器的模式是精确还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。

3.View的工作流程

View的工作流程主要是指measure,layout,draw这三大流程,分别测量宽高,确定位置,画出来。
Measure:
在Activity里面获取view的宽高:
(1)Activity/view的onWindowFocusChanged
这个方法的含义是:view已经初始化完毕了,宽高已经准备好了。需要注意的是这个方法会被调用多次,当Activity的窗口得到焦点和失去焦点时均会被调用,具体如果频繁的onResume和onPause
,那么就会多次调用。

      public void onWindowFocusChanged( boolean hasFocus) {             super.onWindowFocusChanged( hasFocus);             if( hasFocus) {                   int width = tv.getMeasuredWidth();                   int height = tv.getMeasuredHeight();            }      }

(2)view.post(Runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此Runnable的时候,view也已经初始化好。

      protected void onStart() {             super.onStart();             tv.post( new Runnable() {                                     @Override                   public void run() {                int width = tv.getMeasuredWidth();                int height = tv.getMeasuredHeight();                  }            });      }

(3)ViewTreeObserver
使用ViewTreeObserver的众多回调可以完成这个功能,比如使用onGlobalLayoutListener这个接口,当view树的状态发生变化或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取view的宽高的一个很好的时机。
注意:随着view树状态的发生的变化,会被调用多次。

      protected void onStart() {             super.onStart();            ViewTreeObserver observer = mr.getViewTreeObserver();             observer.addOnGlobalLayoutListener( new OnGlobalLayoutListener() {                   @Override                   public void onGlobalLayout() {                         mr.getViewTreeObserver(). removeGlobalOnLayoutListener( this);                int width = mr.getMeasuredWidth();                int height = mr.getMeasuredHeight();                  }            });      }

(4)view.measure(int widthMeasureSpec,int heightMeasureSpec )
需要根据view的LayoutParams来分:
mach_parent:无法Measure出来。
具体数值:

int widthMeasureSpec = MeasureSpec. makeMeasureSpec(100, MeasureSpec.EXACTLY);int heightMeasureSpec = MeasureSpec. makeMeasureSpec(100, MeasureSpec.EXACTLY);mr.measure( widthMeasureSpec, heightMeasureSpec);

warp_content:
如下Measure:

int widthMeasureSpec = MeasureSpec. makeMeasureSpec((1<<30)-1, MeasureSpec. AT_MOST);int heightMeasureSpec = MeasureSpec. makeMeasureSpec((1<<30)-1, MeasureSpec. AT_MOST);mr.measure( widthMeasureSpec, heightMeasureSpec);

用view在理论上能支持的最大的值去构造MeasureSpec是合理的。

两种错误的用法:

int widthMeasureSpec = MeasureSpec. makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED );int heightMeasureSpec = MeasureSpec. makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED );mr.measure( widthMeasureSpec, heightMeasureSpec);
mr.measure( LayoutParams.WRAP_CONTENT, LayoutParams. WRAP_CONTENT);

Layout:
View的getMeasureWidth和getWidth这两个方法有什么区别?
View的默认实现中,是相等的,只是赋值的时机不同,实际开发中可能会不相等。

public void layout(int l,int t,int r,int b) {super.layout(l,t,r+100,b+100);}

Draw:
view的绘制过程遵循下面几个步骤:
(1)绘制背景background.draw(canvas)
(2)绘制自己(onDraw)
(3)绘制children(dispatchDraw)
(4)绘制装饰(onDrawScrollBars)

4.自定义View
分类:
1.继承view,重写onDraw方法。
2.继承ViewGroup派生特殊的Layout。
3.继承特定的View如TextView。
4.继承特定的ViewGroup如LinearLayout。

须知:
1.让view支持warp_content
在onMeasure里面处理。

2.如有必要,让view支持padding
View要在onDraw方法中要处理padding,而ViewGroup要在onMeasure和onLayout中处理padding和margin。

3.尽量不要在view中使用HandlerView内部提供了post系列方法供使用。

4.View中如果有线程或者动画,需要及时停止在View的onDetachedFromWindow中停止动画,线程或回收其他资源。

5.View带有滑动嵌套的时候,需要处理好滑动冲突需要处理好滑动冲突。

0 0
原创粉丝点击