Просмотр getWidth() и getHeight() возвращает 0
Я создаю все элементы в моем проекте Android в динамическом режиме. Я пытаюсь получить ширину и высоту кнопки, чтобы я мог повернуть эту кнопку вокруг. Я просто пытаюсь научиться работать с языком Android. Однако он возвращает 0.
Я провел некоторое исследование, и я увидел, что его нужно выполнять где-то иначе, чем в методе onCreate()
. Если кто-то может дать мне пример того, как это сделать, это было бы здорово.
Вот мой текущий код:
package com.animation;
import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;
public class AnimateScreen extends Activity {
//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = new LinearLayout(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(30, 20, 30, 0);
Button bt = new Button(this);
bt.setText(String.valueOf(bt.getWidth()));
RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
ra.setDuration(3000L);
ra.setRepeatMode(Animation.RESTART);
ra.setRepeatCount(Animation.INFINITE);
ra.setInterpolator(new LinearInterpolator());
bt.startAnimation(ra);
ll.addView(bt,layoutParams);
setContentView(ll);
}
Любая помощь приветствуется.
Ответы
Ответ 1
Вы вызываете getWidth()
слишком рано. Пользовательский интерфейс еще не был установлен и размещен на экране.
Я сомневаюсь, что вы хотите делать то, что вы делаете, так или иначе - анимированные виджеты не меняют свои интерактивные области, поэтому кнопка будет реагировать на клики в исходной ориентации независимо от того, как она поворачивается.
Чтобы определить размер кнопки, вы можете использовать ресурс размеров, а затем ссылаться на этот ресурс измерения из вашего файла макета и вашего источника кода, чтобы избежать этой проблемы.
Ответ 2
Основная проблема заключается в том, что вам нужно дождаться фазы рисования для фактических измерений (особенно с динамическими значениями, такими как wrap_content
или match_parent
), но обычно эта фаза не завершена до onResume()
, Поэтому вам нужно обходное решение для ожидания этой фазы. Существуют различные возможные решения:
1. Слушайте события Draw/Layout: ViewTreeObserver
A ViewTreeObserver запускается для разных событий рисования. Обычно OnGlobalLayoutListener
- это то, что вы хотите получить для измерения, поэтому код в слушателе будет вызываться после фазы компоновки, поэтому измерения готовы:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
view.getHeight(); //height is ready
}
});
Примечание. Слушатель будет немедленно удален, поскольку в противном случае он будет срабатывать при каждом событии макета. Если вам нужно поддерживать приложения SDK Lvl < 16 используйте это, чтобы отменить регистрацию слушателя:
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
2. Добавить runnable в очередь макета: View.post()
Не очень хорошо известно и мое любимое решение. В основном просто используйте метод View post с вашим собственным runnable. Это в основном ставит в очередь ваш код после измерения, макета и т.д., Как указано Romain Guy:
Очередь событий пользовательского интерфейса будет обрабатывать события по порядку. После setContentView() вызывается, очередь событий будет содержать сообщение запрашивая ретранслятор, так что все, что вы отправляете в очередь, произойдет после прохода макета
Пример:
final View view=//smth;
...
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
}
});
Преимущество над ViewTreeObserver
:
- ваш код выполняется только один раз, и вам не нужно отключать Наблюдатель после выполнения, что может быть проблемой
- менее подробный синтаксис
Литература:
3. Перезаписать Views onLayout Method
Это практично только в определенной ситуации, когда логика может быть инкапсулирована в самом представлении, иначе это довольно многословный и громоздкий синтаксис.
view = new View(this) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
view.getHeight(); //height is ready
}
};
Также помните, что onLayout будет вызываться много раз, поэтому следует учитывать, что вы делаете в этом методе, или отключить свой код после первого раза
4. Проверьте, прошла ли фаза компоновки
Если у вас есть код, который выполняется несколько раз при создании ui, вы можете использовать следующий способ поддержки v4 lib:
View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
viewYouNeedHeightFrom.getHeight();
}
Возвращает true, если представление прошло по крайней мере с одного макета, поскольку оно было последний прикрепленный к окну или отсоединенный от него.
Дополнительно: получение измерений, заданных по статическому закону
Если достаточно просто получить статически определенную высоту/ширину, вы можете просто сделать это с помощью
Но помните, что это может отличаться от фактической ширины/высоты после рисования. Javadoc отлично описывает разницу:
Размер представления выражается шириной и высотой. Вид фактически имеют две пары значений ширины и высоты.
Первая пара известна как измеренная ширина и измеренная высота. Эти размеры определяют, насколько большой вид хочет быть внутри его родителя (см. Макет для более подробной информации.) Измеренные размеры можно получить с помощью вызов getMeasuredWidth() и getMeasuredHeight().
Вторая пара просто известна как ширина и высота, или иногда ширины чертежа и высоты чертежа. Эти измерения определяют размер представления на экране, время рисования и после макета. Эти значения могут, но не должны отличаться от измеренной ширины и высоты. Ширина и высота можно получить, вызвав getWidth() и getHeight().
Ответ 3
Мы можем использовать
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
Ответ 4
Я использовал это решение, которое, я думаю, лучше, чем onWindowFocusChanged(). Если вы откроете диалоговое окно DialogFragment, затем поверните телефон, onWindowFocusChanged вызывается только тогда, когда пользователь закрывает диалог):
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Ensure you call it only once :
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// Here you can get the size :)
}
});
Изменить: поскольку removeGlobalOnLayoutListener устарел, вы должны сделать следующее:
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
// Ensure you call it only once :
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else {
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// Here you can get the size :)
}
Ответ 5
Как утверждает Ян в этот Android-разработчик:
Во всяком случае, сделка заключается в том, что макет содержимое окна происходит после того как все элементы будут построены и добавлены к их родителям Просмотры. Это должно быть так, потому что пока вы не узнаете, какие компоненты видят содержит, и что они содержат, и так далее, нет разумного способа, которым вы можете выложите его.
Нижняя строка, если вы вызываете getWidth() и т.д. в конструкторе, он вернется нуль. Процедура заключается в создании всех ваши элементы представления в конструкторе, затем дождитесь просмотра метод onSizeChanged(), который будет вызываться - что, когда вы впервые узнаете реальный размер, чтобы при настройке размеры ваших элементов графического интерфейса.
Также помните, что onSizeChanged() иногда вызываются с параметрами нуль - проверьте этот случай и немедленно возвращайтесь (так что вы не получаете делиться на ноль при расчете макет и т.д.). Спустя некоторое время будет вызываться с реальными значениями.
Ответ 6
Если вам нужно получить ширину какого-либо виджета перед его отображением на экране, вы можете использовать getMeasuredWidth() или getMeasuredHeight().
myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
Ответ 7
Я бы предпочел использовать OnPreDrawListener()
вместо addOnGlobalLayoutListener()
, так как он вызывается немного раньше.
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
{
@Override
public boolean onPreDraw()
{
if (view.getViewTreeObserver().isAlive())
view.getViewTreeObserver().removeOnPreDrawListener(this);
// put your code here
return true;
}
});
Ответ 8
Расширение Kotlin для наблюдения за глобальным макетом и выполнения заданной задачи, когда высота готова динамически.
Использование:
view.height { Log.i("Info", "Here is your height:" + it) }
Реализация:
fun <T : View> T.height(function: (Int) -> Unit) {
if (height == 0)
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
function(height)
}
})
else function(height)
}
Ответ 9
Один вкладыш, если вы используете RxJava и RxBindings. Подобный подход без шаблона. Это также решает взломать предупреждения, как в ответе Tim Autin.
RxView.layoutChanges(yourView).take(1)
.subscribe(aVoid -> {
// width and height have been calculated here
});
Это он. Не нужно отказываться от подписки, даже если ее не вызывали.
Ответ 10
Если вы используете Kotlin
leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
else {
leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
}
// Here you can get the size :)
leftThreshold = leftPanel.width
}
})
Ответ 11
Высота и ширина равны нулю, потому что представление не было создано к тому времени, когда вы запрашиваете его высоту и ширину. Одним из самых простых решений является
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
view.getWidth(); //width is ready
}
});
Этот метод хорош по сравнению с другими методами, поскольку он короткий и четкий.
Ответ 12
Может быть, это кому-то поможет:
Создать функцию расширения для класса View
filename: ViewExt.kt
fun View.afterLayout(what: () -> Unit) {
if(isLaidOut) {
what.invoke()
} else {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
what.invoke()
}
})
}
}
Это можно использовать в любом представлении с помощью:
view.afterLayout {
do something with view.height
}
Ответ 13
После некоторых тестов на Android 7.1.2 я нашел только эту опцию действительно полезной, все остальные вернули 0
view.postDelayed({
height = view.height
},100)
Самое смешное, что я заметил, это результат зависит от времени задержки. Когда я установил аргумент delayMillis
в 60, я получил height
= 0, а delayMillis
= 61 height
уже имел значение. Поэтому я думаю, delayMillis
= 100 должно быть достаточно.
Ответ 14
Прошедшие просмотры возвращают 0 как высоту, если приложение в фоновом режиме. Это мой код (1оо% работает)
fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
measure(widthSpec, heightSpec)
postJob([email protected], measuredWidth, measuredHeight)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
@Suppress("DEPRECATION")
viewTreeObserver.removeGlobalOnLayoutListener(this)
} else {
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
Ответ 15
Надо дождаться, когда вид будет нарисован. Для этого используйте OnPreDrawListener. Пример Kotlin:
val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
view.viewTreeObserver.removeOnPreDrawListener(this)
// code which requires view size parameters
return true
}
}
view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
Ответ 16
В AndroidX есть несколько расширенных функций, которые помогут вам с такой работой, внутри androidx.core.view
Для этого вам нужно использовать Kotlin.
Здесь лучше всего подходит doOnLayout
:
Выполняет данное действие, когда выложено это представление. Если представление было размечено и не запрашивало макет, действие будет выполнено сразу же, в противном случае действие будет выполнено после следующего размещения представления.
Действие будет вызвано только один раз в следующем макете, а затем удалено.
В вашем примере:
bt.doOnLayout {
val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
... more code
}
Зависимость: androidx.core:core-ktx:1.0.0