3.2View的绘制基础
目录介绍
- 01.View坐标系介绍
- 02.如何获取View宽高
01.View坐标系介绍
- 在自定义控件的时候,坐标系也是经常用到的,下面记录一下在复习过程中view坐标系的一些知识。
1.1 view的四个顶点坐标
- view的位置主要由四个顶点坐标来决定,并且这个四个坐标都是相当坐标,view在移动过程中,它们的值不会发生变化。
- 四个顶点坐标分别为:
getTop(); //获取子View左上角距父View顶部的距离 getLeft(); //获取子View左上角距父View左侧的距离 getBottom(); //获取子View右下角距父View顶部的距离 getRight(); //获取子View右下角距父View左侧的距离
- view的宽高计算:
//宽=right-left int width = getRight() - getLeft(); //框架自带 int width2 = getWidth(); //高=bottom-top int height = getBottom() - getTop(); int height2 = getHeight();
- 具体view的坐标如图所示
image
1.2 view的x、y坐标
- android3.0之后为view增加了x、y、translationX、translationY坐参数(上面图2)。其中x、y为view左上角的坐标,translationX、translationY是view左上角相对于父容器的偏移量,它们默认值为0。
//x、y的计算 float x = getLeft() + getTranslationX(); float x2 = getX(); float y = getTop() + getTranslationY(); float y2 = getY();
image
02.如何获取View宽高
2.0 为何直接获取宽高为0
- 如果我们想在activity已启动时去获取某个View的宽、高,实际上在onCreate,onStart,onResume中均无法正确得到某个View的宽高信息。
- 因为View的measure过程和activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate,onStart,onResume时,某个View已经测量完毕,一旦View没有测量完毕,那么我们此时获得的宽/高就是0。
2.1 onWindowFocusChanged
- 如下所示
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
- 该方法的含义是:View已经初始化完毕了,宽/高已经准备好了,所以此时去获取宽/高是没有问题的。
- 注意:onWindowFocusChanged会被调用多次,当activity的窗口得到焦点和失去焦点时均会被调用一次
- 具体来说,当activity继续执行(onResume)和暂停执行(onPause)时,onWindowFocusChanged均会被调用。
2.2 view.post(runnable)
- 如下所示
view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } });
- 通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable时,View也已经初始化好了。
- View的post(Runnable r)方法里,r会带来一个新的线程吗?不会,最终还是handler发送消息,执行在UI线程。
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; }
2.3 ViewTreeObserver
- 如下所示
ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } });
- 使用ViewTreeObserver的众多回调也可以完成这个功能,比如使用OnGlobalLayoutListener这个接口。当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法将被调用。
- 注意:伴随着View树的状态改变等,onGlobalLayout会被调用多次。因此需要在适当时机将监听回调移除。
- 可能会报错
- IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
2.4 手动测量控件宽高
- 如下所示
int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); imageView.measure(w, h); int height = imageView.getMeasuredHeight(); int width = imageView.getMeasuredWidth();
- 比其他的方法多调用了一次onMeasure()方法,该方法虽然看上去简单,但是如果要目标控件计算耗时比较大的话(如listView等),不建议使用
- 如果是简单的button,textView等控件,用该方法也可以