Как вы можете сказать, когда макет был нарисован?
У меня есть собственное представление, которое рисует прокручиваемое растровое изображение на экране. Чтобы инициализировать его, мне нужно передать размер в пикселях родительского объекта макета. Но во время функций 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();
}
}