Как вы можете сказать, когда макет был нарисован?

У меня есть собственное представление, которое рисует прокручиваемое растровое изображение на экране. Чтобы инициализировать его, мне нужно передать размер в пикселях родительского объекта макета. Но во время функций onCreate и onResume макет еще не отрисован, и поэтому layout.getMeasuredHeight() возвращает 0.

В качестве обходного пути я добавил обработчик, чтобы подождать одну секунду, а затем измерить. Это работает, но неаккуратно, и я понятия не имею, насколько я могу урезать время до того, как я закончу до того, как будет нарисован макет.

Я хочу знать, как я могу определить, когда макет нарисован? Есть ли событие или обратный звонок?

13 ответов

Решение

Вы можете добавить дерево наблюдателя к макету. Это должно вернуть правильную ширину и высоту. onCreate() вызывается перед созданием дочерних представлений. Так что ширина и высота еще не рассчитаны. Чтобы получить высоту и ширину, поместите это на onCreate() метод:

final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver vto = layout.getViewTreeObserver(); 
vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { 
    @Override 
    public void onGlobalLayout() {
        layout.getViewTreeObserver().removeOnGlobalLayoutListener(this); 
        int width  = layout.getMeasuredWidth();
        int height = layout.getMeasuredHeight(); 

    } 
});

Действительно простой способ - просто позвонить post() на вашем макете. Это запустит ваш код на следующем шаге, после того как ваше представление уже будет создано.

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

Чтобы избежать устаревшего кода и предупреждений, вы можете использовать:

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

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

Лучше с функциями расширения 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)
            }
        }
    })
}

К сожалению, ни одно из этих решений не сработало для меня надежно. Итак, вот что я нашел, работал для меня, и я надеюсь, что это работает для других.

Ключевое понимание заключается в том, что OnPreDrawListener вызывается много раз, и нет никакой гарантии, что первая, вторая, третья и т. д. будут итерацией, в которой ваш вид имеет правильную измеренную ширину или высоту.

Поэтому не прекращайте опрашивать эту точку зрения, пока она не предоставит вам разумного ответа (view.getMeasuredWidth() <= 0). Вы также можете установить ограничение на количество проверок measuredWidth больше нуля при вероятности того, что ваше представление никогда не будет отображено, хотя это может указывать на другие проблемы с вашим кодом.

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                    if (view.getMeasuredWidth() <= 0) { return false; }     
                    view.getViewTreeObserver().removeOnPreDrawListener(this);
                    int width = view.getMeasuredWidth();
                    int height = view.getMeasuredHeight();

                //Do something with width and height here!

                return true;
            }
        });

Самый элегантный способ сделать это - использовать. An OneShotPreDrawListener зарегистрирует обратный вызов, который будет вызываться, когда дерево представления будет нарисовано, и удалит себя после одного OnPreDraw вызов.

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

Другой ответ:
Попробуйте проверить размеры просмотра в onWindowFocusChanged,

Просто проверьте это, вызвав метод post на вашем макете или представлении

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

Я рекомендую вам использовать doOnLayout, если вы используете kotlin

      doOnLayout {
    doWhateverYouNeed()
}

doc: выполняет данное действие, когда это представление выложено

Когда onMeasure называется вид получает свою измеренную ширину / высоту. После этого вы можете позвонить layout.getMeasuredHeight(),

Я создаю статическую переменную, чтобы проверить, был ли макет уже нарисован. Если он был создан, вы можете просто вызвать функцию, которая рисует.

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

view.post { TODO(«Еще не реализовано») }

Другие вопросы по тегам