Як ви можете визначити, коли намальовано макет?


201

У мене є власний вид, який малює прокручувану растрову карту на екран. Для того, щоб ініціалізувати його, мені потрібно передати розмір у пікселях батьківського об’єкта макета. Але під час функцій onCreate і onResume макет ще не намальований, і тому layout.getMeasuredHeight () повертає 0.

Як вирішення, я додав обробник, щоб зачекати одну секунду, а потім виміряти. Це працює, але його неохайно, і я поняття не маю, скільки я можу обрізати час, перш ніж я закінчуся, перш ніж макет буде намальований.

Що я хочу знати, як я можу виявити, коли виводиться макет? Чи є подія чи зворотний дзвінок?

Відповіді:


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
Велике спасибі! Але мені цікаво, чому немає більш простого методу для цього, оскільки це звичайна проблема.
фелікс

1
Дивовижний. Я спробував OnLayoutChangeListener, і це не вийшло. Але OnGlobalLayoutListener працював. Дуже дякую!
YoYoMyo

8
deleteGlobalOnLayoutListener застарілий на рівні API 16. Використовуйте натомість RemoveOnGlobalLayoutListener.
tounaobun

3
Я бачу один "gotcha", якщо ваш погляд не видно, викликається onGlobalLayout, але пізніше, коли видимий, викликається getView, але не onGlobalLayout ... так.
Стівен Маккормік

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
Я не знаю, чи буде після публікації вашого коду після того, як представлення вже створено. Гарантії немає, оскільки публікація просто додасть створений вами Runnable до RunQueue, який буде виконуватися після попередньої черги, виконаної на UI Thread.
HendraWD

4
Навпаки, post () виконується після наступного оновлення кроку користувальницького інтерфейсу, що відбувається після створення представлення. Тому ви гарантуєте, що він виконається після того, як буде зроблено перегляд.
Elliptica

Я перевіряв цей код кілька разів, він найкраще працює лише в UI-потоці. У фоновому потоці він іноді не працює.
CoolMind

3
@HendraWijayaDjiono, може бути , цей пост буде відповідати: stackoverflow.com/questions/21937224 / ...
CoolMind

1
Я зіткнувся з проблемою, коли використання першої відповіді не працювало кожен раз, використовуючи допис у поданні, яке мені потрібно прокрутити (перегляд прокрутки), дозволив мені правильно прокрутити потрібну позицію, як тільки вид був готовий прийняти scrollДля викликів методу.
Данаофр

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
Щоб використовувати цей код, "view" повинен бути оголошений як "остаточний".
BeccaP

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

Дуже приємне та елегантне рішення, яке можна використовувати повторно.
Іонут Негру


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
Погляд не вимірюється до тих пір, поки він не буде викладений, тому ця альтернатива є надмірною і виконує надмірну перевірку. Ви визнаєте, що не звертаєтесь до цього питання, але те, що відчуваєте, слід запитати. Оригінальний коментар містив обидва ці пункти, але також є деякі проблеми з самим кодом, які були подані як редагування.
Покинутий кошик

Це приємна редакція. Дякую. Я думаю, що я запитую, чи вважаєте ви, що цю відповідь слід видалити чи ні? Я зіткнувся з проблемою ОП. Це вирішило проблему. Я пропонував це добросовісно. Це була помилка? Це щось, чого не слід робити на StackOverflow?
jwehrle

2

Просто перевірте це, зателефонувавши до методу публікації на своєму макеті чи перегляді

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

Це неправда і може приховати можливі проблеми. postбуде просто в черзі, на яку можна запустити біг, це не забезпечить вимір виміру, ще менше намальовано.
Іонут Негру

@IonutNegru можливо прочитати цю stackoverflow.com/a/21938380
Бруно Bieri

@BrunoBieri, ви можете перевірити цю поведінку за допомогою функцій асинхронізації, які змінюють вимірювання перегляду, і побачити, чи виконується завдання в черзі після кожного вимірювання та отримуєте очікувані значення.
Іонут Негру

1

Коли onMeasureвикликається, вид отримує свою виміряну ширину / висоту. Після цього можна зателефонувати layout.getMeasuredHeight().


4
Чи не доведеться вам робити власний власний вигляд, щоб знати, коли onMeasure пожежі?
Geeks On Hugs

Так, для доступу до міри ви повинні використовувати власні представлення даних.
Тревор Харт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.