Определение размера представления Android во время выполнения

123

Я пытаюсь применить анимацию к представлению в моем приложении для Android после создания моего действия. Для этого мне нужно определить текущий размер представления, а затем настроить анимацию для масштабирования от текущего размера до нового. Эта часть должна выполняться во время выполнения, так как вид масштабируется до разных размеров в зависимости от ввода от пользователя. Мой макет определен в XML.

Это кажется легкой задачей, и по этому поводу есть много ТАК вопросов, хотя, очевидно, ни один из них не решил мою проблему. Так что, возможно, я упускаю что-то очевидное. Я понимаю свое мнение следующим образом:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

Это работает отлично, но при вызове getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().widthи т.д., они все возвращаются 0. Я также попытался вручную вызова measure()на представлении с последующим вызовом getMeasuredWidth(), но это не имеет никакого эффекта.

Я пробовал вызывать эти методы и проверять объект в отладчике в своей деятельности onCreate()и в onPostCreate(). Как я могу определить точные размеры этого представления во время выполнения?

Ник Рейман
источник
1
О, и я должен отметить , что сам вид определенно имеет не имеет 0 ширина / высота. Он отлично отображается на экране.
Nik Reiman
Можете ли вы разместить тег <ImageView ... /> из макета xml?
fhucho
Я боролся с множеством SO-решений этой проблемы, пока не понял, что в моем случае размеры View совпадают с физическим экраном (мое приложение является «иммерсивным», а для View и всех его родительских ширины и высоты установлены значения match_parent). В этом случае более простым решением, которое можно безопасно вызвать даже до отрисовки View (например, из метода onCreate () вашего Activity), является использование Display.getSize (); подробнее см. stackoverflow.com/a/30929599/5025060 . Я знаю, что @NikReiman просил не об этом, просто оставил записку для тех, кто может использовать этот более простой метод.
CODE-REaD

Ответы:

180

Используйте ViewTreeObserver в представлении, чтобы дождаться первого макета. Только после первого макета будет работать getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight ().

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}
Викрам Бодичерла
источник
21
Будьте осторожны с этим ответом, так как он будет постоянно вызываться, если основной вид является видео / поверхностью
Кевин Паркер
7
Не могли бы вы рассказать об этом @Kevin? Две вещи, во-первых, поскольку это слушатель, я бы ожидал одного обратного вызова, а затем, как только мы получим первый обратный вызов, мы удаляем слушателя. Я что-то пропустил?
Викрам Бодичерла
4
Перед вызовом любых методов ViewTreeObserver рекомендуется проверить isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Питер Тран,
4
любая альтернатива removeGlobalOnLayoutListener(this);? потому что он устарел.
Sibbs Gambling
7
@FarticlePilter, используйте вместо него removeOnGlobalLayoutListener (). Это упоминается в документации.
Викрам Бодичерла
64

На самом деле существует несколько решений, в зависимости от сценария:

  1. Безопасный метод будет работать непосредственно перед рисованием вида, после завершения этапа компоновки:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

Пример использования:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. В некоторых случаях достаточно вручную измерить размер представления:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Если вы знаете размер контейнера:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. если у вас есть расширенное пользовательское представление, вы можете получить его размер с помощью метода onMeasure, но я думаю, что он работает хорошо только в некоторых случаях:
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. Если вы пишете на Kotlin, вы можете использовать следующую функцию, которая негласно работает точно так же, как runJustBeforeBeingDrawnя написал:

    view.doOnPreDraw { actionToBeTriggered() }

    Обратите внимание, что вам нужно добавить это в gradle (можно найти здесь ):

    implementation 'androidx.core:core-ktx:#.#'
разработчик Android
источник
1
Кажется, что решение №1 не всегда работает OnPreDrawListener. Протестированный случай, когда вы хотите запустить код из Activity onCreate()и сразу запустить фоновое приложение. Легко повторить, особенно на более медленном устройстве. Если заменить OnPreDrawListenerна OnGlobalLayoutListener- такой же проблемы не будет.
вопрошающий
@questioner Я не понимаю, но я рад, что в конце концов он тебе помог.
разработчик Android
Хотя обычно использую ViewTreeObserverили post, в моем случае помог номер 2 ( view.measure).
CoolMind 07
3
@CoolMind Вы также можете использовать новый метод, если вы используете Kotlin. Обновлен ответ, чтобы включить его.
разработчик Android
почему у kotlin есть для этого дополнительные функции? Google сумасшедшие, этот язык бесполезен (просто следует остаться с java), он намного старше, чем swift, но только swift получил достаточную популярность tiobe.com/tiobe-index (swift 12 position и kotlin 38)
user924
18

Вы звоните getWidth()до того, как изображение фактически отобразится на экране?

Распространенная ошибка новых разработчиков Android - использовать ширину и высоту представления внутри его конструктора. Когда вызывается конструктор представления, Android еще не знает, насколько большим будет представление, поэтому размеры устанавливаются равными нулю. Реальные размеры рассчитываются на этапе макета, который происходит после строительства, но до того, как что-либо будет нарисовано. Вы можете использовать onSizeChanged()метод , чтобы получать уведомления о значениях , когда они известны, или вы можете использовать getWidth()и getHeight()методы позже, например, в onDraw()методе.

Марк Б
источник
2
Значит ли это, что мне нужно переопределить ImageView, чтобы поймать onSizeChange()событие и узнать его реальный размер? Это кажется излишним ... хотя на данный момент я отчаянно пытаюсь заставить код работать, так что попробовать стоит.
Ник Рейман,
Я думал больше о том, как дождаться завершения onCreate () в вашей деятельности.
Mark B
1
Как уже упоминалось, я попытался поместить этот код, onPostCreate()который запускается после того, как представление полностью создано и запущено.
Ник Рейман,
1
Откуда цитируемый текст?
Intrications
1
эта цитата для пользовательского представления, не
публикуйте
17

Основываясь на совете @mbaird, я нашел работоспособное решение, создав подкласс ImageViewкласса и переопределив onLayout(). Затем я создал интерфейс наблюдателя, который реализовал мое действие, и передал классу ссылку на себя, что позволило ему сообщить активности, когда она фактически закончила определение размера.

Я не уверен на 100%, что это лучшее решение (поэтому я пока не помечаю этот ответ как правильный), но он действительно работает, и, согласно документации, это первый раз, когда можно найти фактический размер представления.

Ник Рейман
источник
Хорошо знать. Если я найду что-нибудь, что позволит вам обойтись без создания подкласса View, я опубликую это здесь. Я немного удивлен, что onPostCreate () не сработал.
Mark B
Ну, я попробовал это, и, похоже, это сработало. Просто не самое лучшее решение, поскольку этот метод вызывается часто, а не только один раз при запуске. :(
Тобиас Райх
10

Вот код для получения макета путем переопределения представления, если API <11 (API 11 включает функцию View.OnLayoutChangedListener):

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}
gregm
источник
5

Используйте приведенный ниже код, он дает размер представления.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Асиф Патель
источник
5

Это работает для меня в моем onClickListener:

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);
Биро Чаба Норберт
источник
4
Вам не нужно postDelayed, когда он находится в прослушивателе кликов. Вы не можете ничего щелкнуть, пока все представления не будут измерены и не появятся на экране. Таким образом, они уже будут измерены к тому времени, когда вы сможете их щелкнуть.
afollestad
Этот код неверен. Задержка публикации не гарантирует, что в следующем тике ваше представление будет измерено.
Ян Вонг
Я бы рекомендовал не использовать postDelayed (). Это вряд ли произойдет, но что, если ваше представление не измеряется в течение 1 секунды, и вы вызываете это runnable, чтобы получить ширину / высоту просмотра. Это все равно приведет к 0. Edit: Да, кстати, это 1 в миллисекундах, поэтому он наверняка потерпит неудачу.
Алекс
4

Я также потерял около getMeasuredWidth()и getMeasuredHeight() getHeight()и в getWidth()течение длительного времени .......... позже я обнаружил , что получение ширины и высоты мнения , в onSizeChanged()это лучший способ сделать это ........ вы можете динамически получите ТЕКУЩУЮ ширину и ТЕКУЩУЮ высоту вашего представления, переопределив onSizeChanged(метод).

может захотеть взглянуть на это, в котором есть тщательно продуманный фрагмент кода. Новое сообщение в блоге: как получить размеры по ширине и высоте customView (расширяет View) в Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Ракиб
источник
0

Вы можете получить как положение, так и размер представления на экране

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}
Хитеш Саху
источник
-1

у меня работает идеально:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
Атом
источник