这个问题大家肯定遇到过不止一次,其实很简单,解决它也很容易,但是咱们追求的毕竟不是解决它,而是找到几种方法去解决,并且这么解决的原理是什么。
  这里列出4种解决方案:

Activity/View#onWindowFocusChanged

  这个函数的含义是:view已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽高是可以成功获取的。但是需要注意的是onWindowFocusChanged函数会被调用多次,当Activity的窗口得到焦点和失去焦点时均会被调用一次,如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用。

@Overridepublic void onWindowFocusChanged(boolean hasFocus) {    super.onWindowFocusChanged(hasFocus);    L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()            + "  v_view1.getHeight():" + v_view1.getHeight());}

view.post(runnable)

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

v_view1.post(new Runnable() {    @Override    public void run() {        L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()                + "  v_view1.getHeight():" + v_view1.getHeight());    }});

ViewTreeObserver

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

v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {    @Override    public void onGlobalLayout() {        L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()                + "  v_view1.getHeight():" + v_view1.getHeight());    }});

  再来详细介绍一下ViewTreeObserver这个类,这个类是用来注册当view tree全局状态改变时的回调监听器,这些全局事件包括很多,比如整个view tree视图的布局,视图绘制的开始,点击事件的改变等等。还有千万不要在应用程序中实例化ViewTreeObserver对象,因为该对象仅是由视图提供的。
  ViewTreeObserver类提供了几个相关函数用来添加view tree的相关监听器:

  • public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener)
  • 该函数为api 16版本中添加,作用是注册在该view tree将要绘制时候的回调监听器,注意该函数和相关的remove函数不能在监听器回调的onDraw()中调用。
  • public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener)
  • 该函数用来注册在view tree焦点改变时候的回调监听器。
  • public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
  • 该函数用来注册在该view tree中view的全局布局属性改变或者可见性改变时候的回调监听器。
  • public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
  • 该函数用来注册当view tree将要被绘制时候(view 的 onDraw 函数之前)的回调监听器。
  • public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
  • 该函数用来注册当view tree滑动时候的回调监听器,比如用来监听ScrollView的滑动状态。
  • public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
  • 该函数用来注册当view tree的touch mode改变时的回调监听器,回调函数onTouchModeChanged (boolean isInTouchMode)中的isInTouchMode为该view tree的touch mode状态。
  • public void addOnWindowAttachListener (ViewTreeObserver.OnWindowAttachListener listener)
  • api 18添加,该函数用来注册当view tree被附加到一个window上时的回调监听器。
  • public void addOnWindowFocusChangeListener (ViewTreeObserver.OnWindowFocusChangeListener listener)
  • api 18添加,该函数用来注册当window中该view tree焦点改变时候的回调监听器。
而且对应每一个add方法都会有一个remove方法用来删除相应监听器。

view.measure(int widthMeasureSpec, int heightMeasureSpec)

  通过手动对view进行measure来得到view的宽/高,这种情况比较复杂,这里要分情况处理,根据view的layoutparams来分:

match_parent

  直接放弃,无法measure出具体的宽/高。原因很简单,根据view的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量处view的大小。

wrap_content

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

注意到(1<<30)-1,我们知道MeasureSpec的前2位为mode,后面30位为size,所以说我们使用最大size值去匹配该最大化模式,让view自己去计算需要的大小。

具体的数值(dp/px)

  这种模式下,只需要使用具体数值去measure即可,比如宽/高都是100px:

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

源码和结果

demo代码如下
xml:

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView        android:id="@+id/v_view1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@mipmap/ic_launcher"/>    <View        android:id="@+id/v_view2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:background="@mipmap/ic_launcher"/>LinearLayout>

activity:

public class MainActivity extends BaseActivity{    private View v_view1;    private View v_view2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        v_view1 = findViewById(R.id.v_view1);        v_view2 = findViewById(R.id.v_view2);        L.i("normal: v_view1.getWidth():" + v_view1.getWidth()                + "  v_view1.getHeight():" + v_view1.getHeight());        v_view1.post(new Runnable() {            @Override            public void run() {                L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()                        + "  v_view1.getHeight():" + v_view1.getHeight());            }        });        v_view1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()                        + "  v_view1.getHeight():" + v_view1.getHeight());            }        });        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);        v_view1.measure(widthMeasureSpec, heightMeasureSpec);        L.i("measure : v_view1.getMeasuredWidth():" + v_view1.getMeasuredWidth()                + "  v_view1.getMeasuredHeight():" + v_view1.getMeasuredHeight());        v_view2.measure(widthMeasureSpec, heightMeasureSpec);        L.i("measure : v_view2.getMeasuredWidth():" + v_view2.getMeasuredWidth()                + "  v_view2.getMeasuredHeight():" + v_view2.getMeasuredHeight());    }    @Override    public void onWindowFocusChanged(boolean hasFocus) {        super.onWindowFocusChanged(hasFocus);        L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()                + "  v_view1.getHeight():" + v_view1.getHeight());    }}

log日志:

I/[PID:2659]: [TID:1] MainActivity.onCreate(line:28): normal: v_view1.getWidth():0  v_view1.getHeight():0I/[PID:2659]: [TID:1] MainActivity.onCreate(line:50): measure : v_view1.getMeasuredWidth():144  v_view1.getMeasuredHeight():144I/[PID:2659]: [TID:1] MainActivity.onCreate(line:53): measure : v_view2.getMeasuredWidth():16777215  v_view2.getMeasuredHeight():16777215I/[PID:2659]: [TID:1] 2.onGlobalLayout(line:42): ViewTreeObserver : v_view1.getWidth():144  v_view1.getHeight():144I/[PID:2659]: [TID:1] 1.run(line:34): post(Runnable) : v_view1.getWidth():144  v_view1.getHeight():144I/[PID:2659]: [TID:1] MainActivity.onWindowFocusChanged(line:61): onWindowFocusChanged : v_view1.getWidth():144  v_view1.getHeight():144

界面:

小的为view_1,大的为view_2,从log日志中就发现有问题了:view_2视图使用measure之后计算出来的宽高是错误的,所以View类的视图使用measure计算出来的结果是不准确的,这点需要特别特别注意。

更多相关文章

  1. android 系统核心机制binder(06)binder C++层 TestClient分析
  2. Android(安卓)init进程--属性服务器
  3. Viewpager2—登录注册引导页面
  4. 收藏-------Android应用程序组件Content Provider在应用程序之间
  5. Android学习笔记(1)-永远不变的Hello World
  6. android wifi模块分析
  7. Android(安卓)preference与ActivityGroup UI更新
  8. Android调试之TraceView
  9. Android(安卓)UI 的几个简单技巧

随机推荐

  1. Android ADB驱动安装详解
  2. android 动态向Gallery中添加图片及倒影&
  3. Android相对布局和线性布局
  4. Android 获得联系人信息
  5. Android OpenGL学习笔记(二)--三角形的绘
  6. android中如何隐藏应用程序标题栏和通知
  7. 如何改变Android(安卓)Dialog弹出后的Act
  8. 给 Android(安卓)开发者的 Flutter 指南(
  9. Android WebView加载HTML表单并通过javas
  10. Android中自定义控件之飞入飞出布局及随