Android 您如何判断何时绘制了布局?

Android 您如何判断何时绘制了布局?,android,android-layout,Android,Android Layout,我有一个自定义视图,可以在屏幕上绘制一个可滚动的位图。为了初始化它,我需要传入父布局对象的大小(以像素为单位)。但是在onCreate和onResume函数期间,布局尚未绘制,因此Layout.getMeasuredHeight()返回0 作为一种解决方法,我添加了一个处理程序来等待一秒钟,然后进行测量。这是可行的,但它太草率了,我不知道在绘制布局之前,我能缩短多少时间 我想知道的是,如何检测布局何时绘制?是否存在事件或回调?您可以将树观察者添加到布局中。这将返回正确的宽度和高度onCreate

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

作为一种解决方法,我添加了一个处理程序来等待一秒钟,然后进行测量。这是可行的,但它太草率了,我不知道在绘制布局之前,我能缩短多少时间


我想知道的是,如何检测布局何时绘制?是否存在事件或回调?

您可以将树观察者添加到布局中。这将返回正确的宽度和高度<在完成子视图的布局之前调用code>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(); 

        } 
    });

调用测量时,视图将获得其测量的宽度/高度。在此之后,您可以调用
layout.getMeasuredHeight()

另一个答案是:

请尝试在WindowFocusChanged上检查视图尺寸,以避免使用不推荐的代码和警告:

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();
            }
        });

一个非常简单的方法是在布局上调用
post()
。这将在下一步创建视图后运行代码

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

通常方法的替代方法是挂接视图的图形


显示视图时会多次调用
OnPreDrawListener
,因此在视图具有有效测量宽度或高度的情况下,没有特定的迭代。这要求您不断地验证(
view.getMeasuredWidth()),只需在布局或视图上调用post方法即可进行检查

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

使用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)
            }
        }
    })
}

androidx.core-ktx已经有了这个

/**
 * Performs the given action when this view is next laid out.
 *
 * The action will only be invoked once on the next layout and then removed.
 *
 * @see doOnLayout
 */
public inline fun View.doOnNextLayout(crossinline action: (view: View) -> Unit) {
    addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
        override fun onLayoutChange(
            view: View,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
            oldLeft: Int,
            oldTop: Int,
            oldRight: Int,
            oldBottom: Int
        ) {
            view.removeOnLayoutChangeListener(this)
            action(view)
        }
    })
}

/**
 * Performs the given action when this view is laid out. If the view has been laid out and it
 * has not requested a layout, the action will be performed straight away, otherwise the
 * action will be performed after the view is next laid out.
 *
 * The action will only be invoked once on the next layout and then removed.
 *
 * @see doOnNextLayout
 */
public inline fun View.doOnLayout(crossinline action: (view: View) -> Unit) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
        action(this)
    } else {
        doOnNextLayout {
            action(it)
        }
    }
}


您不需要创建自己的自定义视图才能知道onMeasure何时触发吗?是的,您必须使用自定义视图来访问度量值。非常感谢!但我想知道为什么没有更简单的方法来执行此操作,因为这是一个常见的问题。太棒了。我只尝试了LayoutChangeListener,但它不起作用。但是OnGlobalYoutListener成功了。非常感谢很多!RemoveGlobalOnlayOutliner在API级别16中被弃用。请改用removeOnGlobalLayoutListener。我看到的一个“问题”是,如果视图不可见,则调用onGlobalLayout,但稍后在可见时调用getView,而不是onGlobalLayout…呃。另一个快速解决方案是使用
view.post(Runnable)
,它更干净,并且做同样的事情。为了使用此代码,“视图”必须声明为“最终”。使用此条件,而不是Build.VERSION.SDK\u INT如何判断何时绘制了布局?
并且您提供了一个方法,在该方法中调用
OnPreDrawListener
并且
在该视图向您提供合理的答案
因此,对您的解决方案的反对意见应该是显而易见的。您需要知道的不是何时绘制,而是何时测量。我的代码回答了真正需要问的问题。此解决方案是否存在问题?它是否无法解决所面临的问题?视图在放置之前不会被测量t、 所以这个替代方案是多余的,并且执行了冗余检查。你承认没有解决问题,但是你觉得应该问什么。最初的评论提出了这两个问题,但是作为编辑提交的代码本身也存在一些问题。这是一个很好的编辑。谢谢。我想我问的是,你认为呢这个答案是否应该被删除?我遇到了OP的问题。这解决了问题。我真诚地提供了它。这是一个错误吗?这是不应该在StackOverflow上做的事情吗?非常好和优雅的解决方案,可以重复使用。这不是真的,可能会隐藏可能的问题。
post
只会对运行进行排队如果无法在之后运行,则无法确保视图已测量,甚至无法绘制。@IonutNegru可能会阅读此@BrunoBieri,您可以使用更改视图测量值的异步函数测试此行为,并查看队列中的任务是否在每次测量后运行并收到预期值。
/**
 * Performs the given action when this view is next laid out.
 *
 * The action will only be invoked once on the next layout and then removed.
 *
 * @see doOnLayout
 */
public inline fun View.doOnNextLayout(crossinline action: (view: View) -> Unit) {
    addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
        override fun onLayoutChange(
            view: View,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
            oldLeft: Int,
            oldTop: Int,
            oldRight: Int,
            oldBottom: Int
        ) {
            view.removeOnLayoutChangeListener(this)
            action(view)
        }
    })
}

/**
 * Performs the given action when this view is laid out. If the view has been laid out and it
 * has not requested a layout, the action will be performed straight away, otherwise the
 * action will be performed after the view is next laid out.
 *
 * The action will only be invoked once on the next layout and then removed.
 *
 * @see doOnNextLayout
 */
public inline fun View.doOnLayout(crossinline action: (view: View) -> Unit) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
        action(this)
    } else {
        doOnNextLayout {
            action(it)
        }
    }
}