如何知道布局何时绘制?

发布于 2024-12-09 11:45:07 字数 236 浏览 0 评论 0原文

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

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

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

I have a custom view that draws a scrollable bitmap to the screen. In order to initialize it, i need to pass in the size in pixels of the parent layout object. But during the onCreate and onResume functions, the Layout has not been drawn yet, and so layout.getMeasuredHeight() returns 0.

As a workaround, i have added a handler to wait one second and then measure. This works, but its sloppy, and I have no idea how much i can trim the time before I end up before the layout gets drawn.

What I want to know is, how can I detect when a layout gets drawn? Is there an event or callback?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

人疚 2024-12-16 11:45:07

您可以将树观察器添加到布局中。这应该返回正确的宽度和高度。 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(); 

        } 
    });

You can add a tree observer to the layout. This should return the correct width and height. onCreate() is called before the layout of the child views are done. So the width and height is not calculated yet. To get the height and width, put this on the onCreate() method:

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

        } 
    });
谜泪 2024-12-16 11:45:07

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

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

A really easy way is to simply call post() on your layout. This will run your code the next step, after your view has already been created.

YOUR_LAYOUT.post( new Runnable() {
    @Override
    public void run() {
        int width  = YOUR_LAYOUT.getMeasuredWidth();
        int height = YOUR_LAYOUT.getMeasuredHeight(); 
    }
});
∞梦里开花 2024-12-16 11:45:07

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

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

To avoid deprecated code and warnings you can use:

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();
            }
        });
时间海 2024-12-16 11:45:07

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

androidx.core-ktx already has this

/**
 * 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)
        }
    }
}
呆萌少年 2024-12-16 11:45:07

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

Better with kotlin extension functions

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)
            }
        }
    })
}
や三分注定 2024-12-16 11:45:07

通常方法的替代方法是挂钩视图的绘制。

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

An alternative to the usual methods is to hook into the drawing of the view.

OnPreDrawListener is called many times when displaying a view, so there is no specific iteration where your view has valid measured width or height. This requires that you continually verify (view.getMeasuredWidth() <= 0) or set a limit to the number of times you check for a measuredWidth greater than zero.

There is also a chance that the view will never be drawn, which may indicate other problems with your code.

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
    }
});
小傻瓜 2024-12-16 11:45:07

最优雅的方法是使用 OneShotPreDrawListenerOneShotPreDrawListener 将注册一个回调,在即将绘制视图树时调用,并且在一次 OnPreDraw 调用后它将自行删除。

OneShotPreDrawListener.add(view) {
    view.doSomething();
}

The most elegant way to do this is using OneShotPreDrawListener. An OneShotPreDrawListener will register a callback to be invoked when the view tree is about to be drawn and it will remove itself after one OnPreDraw call.

OneShotPreDrawListener.add(view) {
    view.doSomething();
}
寒冷纷飞旳雪 2024-12-16 11:45:07

另一个答案是:
尝试在 onWindowFocusChanged 处检查视图尺寸。

Another answer is:
Try checking the View dimensions at onWindowFocusChanged.

独享拥抱 2024-12-16 11:45:07

如果您使用 kotlin 文档,我建议您使用 doOnLayout

doOnLayout {
    doWhateverYouNeed()
}


布局此视图时执行给定的操作

I recommend you to use doOnLayout if you're using kotlin

doOnLayout {
    doWhateverYouNeed()
}

doc:
Performs the given action when this view is laid out

东风软 2024-12-16 11:45:07

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

When onMeasure is called the view gets its measured width/height. After this, you can call layout.getMeasuredHeight().

隱形的亼 2024-12-16 11:45:07

我创建静态变量来检查布局是否已经绘制。如果它已创建,您只需调用绘制的函数即可。

static int firstAccess = 0;
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main8);

    if (firstAccess==0) {
        LAYOUT_YOU_DRAW.post(new Runnable() {
            @Override
            public void run() {
                creatSpread();
                firstAccess = 1;
            }
        });
    }else{
        creatSpread();
    }
}

I create the static variable to check if the layout was already drawn. If it was created you can simply call the function that draws.

static int firstAccess = 0;
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main8);

    if (firstAccess==0) {
        LAYOUT_YOU_DRAW.post(new Runnable() {
            @Override
            public void run() {
                creatSpread();
                firstAccess = 1;
            }
        });
    }else{
        creatSpread();
    }
}
百思不得你姐 2024-12-16 11:45:07

view.post { TODO("尚未实现") }

view.post { TODO("Not yet implemented") }

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文