Я создаю все элементы в моем проекте 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);
}
Любая помощь приветствуется.
Вы вызываете getWidth()
слишком рано. Пользовательский интерфейс еще не был установлен и размещен на экране.
Я сомневаюсь, что вы хотите делать то, что вы делаете, так или иначе - анимированные виджеты не меняют свои интерактивные области, поэтому кнопка будет реагировать на клики в исходной ориентации независимо от того, как она поворачивается.
Чтобы определить размер кнопки, вы можете использовать ресурс размеров, а затем ссылаться на этот ресурс измерения из вашего файла макета и вашего источника кода, чтобы избежать этой проблемы.
Основная проблема заключается в том, что вам нужно дождаться фазы рисования для фактических измерений (особенно с динамическими значениями, такими как wrap_content
или match_parent
), но обычно эта фаза не завершена до onResume()
, Поэтому вам нужно обходное решение для ожидания этой фазы. Существуют различные возможные решения:
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)
Не очень хорошо известно и мое любимое решение. В основном просто используйте метод View post с вашим собственным runnable. Это в основном ставит в очередь ваш код после измерения, макета и т.д., Как указано Romain Guy:
Очередь событий пользовательского интерфейса будет обрабатывать события по порядку. После setContentView() вызывается, очередь событий будет содержать сообщение запрашивая ретранслятор, так что все, что вы отправляете в очередь, произойдет после прохода макета
Пример:
final View view=//smth;
...
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
}
});
Преимущество над ViewTreeObserver
:
Литература:
Это практично только в определенной ситуации, когда логика может быть инкапсулирована в самом представлении, иначе это довольно многословный и громоздкий синтаксис.
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 будет вызываться много раз, поэтому следует учитывать, что вы делаете в этом методе, или отключить свой код после первого раза
Если у вас есть код, который выполняется несколько раз при создании ui, вы можете использовать следующий способ поддержки v4 lib:
View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
viewYouNeedHeightFrom.getHeight();
}
Возвращает true, если представление прошло по крайней мере с одного макета, поскольку оно было последний прикрепленный к окну или отсоединенный от него.
Если достаточно просто получить статически определенную высоту/ширину, вы можете просто сделать это с помощью
Но помните, что это может отличаться от фактической ширины/высоты после рисования. Javadoc отлично описывает разницу:
Размер представления выражается шириной и высотой. Вид фактически имеют две пары значений ширины и высоты.
Первая пара известна как измеренная ширина и измеренная высота. Эти размеры определяют, насколько большой вид хочет быть внутри его родителя (см. Макет для более подробной информации.) Измеренные размеры можно получить с помощью вызов getMeasuredWidth() и getMeasuredHeight().
Вторая пара просто известна как ширина и высота, или иногда ширины чертежа и высоты чертежа. Эти измерения определяют размер представления на экране, время рисования и после макета. Эти значения могут, но не должны отличаться от измеренной ширины и высоты. Ширина и высота можно получить, вызвав getWidth() и getHeight().
Мы можем использовать
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
Я использовал это решение, которое, я думаю, лучше, чем 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 :)
}
Как утверждает Ян в этот Android-разработчик:
Во всяком случае, сделка заключается в том, что макет содержимое окна происходит после того как все элементы будут построены и добавлены к их родителям Просмотры. Это должно быть так, потому что пока вы не узнаете, какие компоненты видят содержит, и что они содержат, и так далее, нет разумного способа, которым вы можете выложите его.
Нижняя строка, если вы вызываете getWidth() и т.д. в конструкторе, он вернется нуль. Процедура заключается в создании всех ваши элементы представления в конструкторе, затем дождитесь просмотра метод onSizeChanged(), который будет вызываться - что, когда вы впервые узнаете реальный размер, чтобы при настройке размеры ваших элементов графического интерфейса.
Также помните, что onSizeChanged() иногда вызываются с параметрами нуль - проверьте этот случай и немедленно возвращайтесь (так что вы не получаете делиться на ноль при расчете макет и т.д.). Спустя некоторое время будет вызываться с реальными значениями.
Если вам нужно получить ширину какого-либо виджета перед его отображением на экране, вы можете использовать getMeasuredWidth() или getMeasuredHeight().
myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
Я бы предпочел использовать 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 false;
}
});
Расширение для получения высоты в Котлине динамически.
Использование:
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)
}
Один вкладыш, если вы используете RxJava и RxBindings. Подобный подход без шаблона. Это также решает взломать предупреждения, как в ответе Tim Autin.
RxView.layoutChanges(yourView).take(1)
.subscribe(aVoid -> {
// width and height have been calculated here
});
Это он. Не нужно отказываться от подписки, даже если ее не вызывали.
Если вы используете 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
}
})
Прошедшие просмотры возвращают 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(this@postWithTreeObserver, measuredWidth, measuredHeight)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
@Suppress("DEPRECATION")
viewTreeObserver.removeGlobalOnLayoutListener(this)
} else {
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
После некоторых тестов на Android 7.1.2 я нашел только эту опцию действительно полезной, все остальные вернули 0
view.postDelayed({
height = view.height
},100)
Самое смешное, что я заметил, это результат зависит от времени задержки. Когда я установил аргумент delayMillis
в 60, я получил height
= 0, а delayMillis
= 61 height
уже имел значение. Поэтому я думаю, delayMillis
= 100 должно быть достаточно.