您如何知道何时绘制布局?


201

我有一个自定义视图,可在屏幕上绘制可滚动的位图。为了初始化它,我需要传入父布局对象的像素大小。但是在onCreate和onResume函数期间,尚未绘制布局,因此layout.getMeasuredHeight()返回0。

解决方法是,我添加了一个处理程序,等待一秒钟,然后进行测量。这行得通,但是草率,而且我不知道在绘制布局之前我可以减少多少时间。

我想知道的是,如何确定何时绘制布局?是否有事件或回调?

Answers:


391

您可以将树形观察者添加到布局中。这应该返回正确的宽度和高度。onCreate()在子视图的布局完成之前调用。因此尚未计算宽度和高度。要获取高度和宽度,请将其放在onCreate()方法上:

    final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
    ViewTreeObserver vto = layout.getViewTreeObserver(); 
    vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { 
        @Override 
        public void onGlobalLayout() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
            int width  = layout.getMeasuredWidth();
            int height = layout.getMeasuredHeight(); 

        } 
    });

28
非常感谢你!但是我不知道为什么没有比这更简单的方法了,因为这是一个普遍的问题。
felixd 2014年

1
惊人。我尝试了OnLayoutChangeListener,但它不起作用。但是OnGlobalLayoutListener起作用了。非常感谢!
YoYoMyo

8
在API级别16中不推荐使用removeGlobalOnLayoutListener。请改用removeOnGlobalLayoutListener。
tounaobun

3
我看到的一个“陷阱”是,如果您的视图不可见,则调用onGlobalLayout,但是稍后在可见时调用getView,而不是onGlobalLayout ... ugh。
斯蒂芬·麦考密克

1
另一个快速的解决方案是使用view.post(Runnable),它更清洁并且做同样的事情。
dvdciri

74

一个真正简单的方法是简单地调用post()您的布局。创建视图后,这将在下一步中运行您的代码。

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

4
我不知道在创建视图后,post是否会真正运行您的代码。无法保证,因为post只会将您创建的Runnable添加到RunQueue中,以便在UI Thread上的先前队列执行之后执行。
HendraWD

4
相反,post()在下一个UI步骤更新后执行,该更新在创建视图之后发生。因此,可以保证在创建视图后它将执行。
Elliptica

我多次测试此代码,它仅在UI线程中效果最佳。在后台线程中有时不起作用。
CoolMind

3
@HendraWijayaDjiono,也许这个帖子会回答:stackoverflow.com/questions/21937224/…–
CoolMind

1
我遇到一个问题,即每次都无法使用第一个答案,而是在需要滚动的视图(滚动视图)上使用post,使视图准备好后可以立即正确滚动至所需位置scrollTo方法调用。
Danuofr

21

为了避免不推荐使用的代码和警告,可以使用:

view.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    view.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
                yourFunctionHere();
            }
        });

1
为了使用此代码,必须将“视图”声明为“最终”。
BeccaP 2015年

2
使用此条件,而不是Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN
HendraWD

4

Kotlin扩展功能更好

inline fun View.waitForLayout(crossinline yourAction: () -> Unit) {
    val vto = viewTreeObserver
    vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            when {
                vto.isAlive -> {
                    vto.removeOnGlobalLayoutListener(this)
                    yourAction()
                }
                else -> viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

非常好的和优雅的解决方案,可以重复使用。
Ionut Negru


3

常用方法的一种替代方法是插入视图的视图。

OnPreDrawListener显示视图时会多次调用,因此在视图具有有效的测量宽度或高度的情况下,没有特定的迭代。这要求您不断验证(view.getMeasuredWidth() <= 0)或对检查measuredWidth大于零的次数设置限制。

也有可能永远不会绘制视图,这可能表明您的代码存在其他问题。

final View view = [ACQUIRE REFERENCE]; // Must be declared final for inner class
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        if (view.getMeasuredWidth() > 0) {     
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            //Do something with width and height here!
        }
        return true; // Continue with the draw pass, as not to stop it
    }
});

现在的问题是How can you tell when a layout has been drawn?,你是提供在那里你调用一个方法OnPreDrawListener,并don't stop interrogating that view until it has provided you with a reasonable answer因此反对你的解决方案应该是显而易见的。
被遗弃的购物车

您需要知道的不是绘制时间,而是测量时间。我的代码回答了确实需要提出的问题。
jwehrle

这个解决方案有问题吗?是否不能解决所面临的问题?
jwehrle

1
在布局视图之前,不会对其进行测量,因此该替代方法过于繁琐,并执行了多余的检查。您承认没有解决这个问题,但是您应该提出自己的想法。原始注释说明了这两点,但是代码本身也存在一些问题,这些问题已作为编辑提交。
被遗弃的购物车

这是一个很好的编辑。谢谢。我想我要问的是,您认为应该删除此答案吗?我遇到了OP的问题。这样就解决了问题。我真诚地提供了它。那是个错误吗?那是在StackOverflow上不应该做的事情吗?
jwehrle

2

只需在布局或视图上调用post方法即可检查它

 view.post( new Runnable() {
     @Override
     public void run() {
        // your layout is now drawn completely , use it here.
      }
});

这是不正确的,并且可能隐藏潜在的问题。 post只会将可运行对象排队等待运行,这并不能确保视图被测量,甚至绘制得更少。
Ionut Negru

@IonutNegru可能会阅读此stackoverflow.com/a/21938380
Bruno Bieri

@BrunoBieri,您可以使用异步功能来测试此行为,该功能会更改视图度量,并查看您是否在每个度量之后运行排队的任务,并接收期望值。
Ionut Negru

1

onMeasure被称为视图获取其测量宽度/高度。之后,您可以致电layout.getMeasuredHeight()


4
您是否需要制作自己的自定义视图才能知道何时触发onMeasure?
Geeks On Hugs 2012年

是的,您必须使用自定义视图来访问度量。
Trevor Hart
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.