Визначення розміру перегляду Android під час виконання


123

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

Це здається легким завданням, і існує багато питань щодо цього, хоча жодне, що вирішило мою проблему, очевидно. Тож, можливо, я пропускаю щось очевидне. Я розумію:

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

Це працює відмінно, але при виклику getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().widthі т.д., вони все повертаються 0. Я також спробував вручну виклику measure()на представленні з подальшим викликом getMeasuredWidth(), але це не має ніякого ефекту.

Я намагався викликати ці методи та перевіряти об'єкт у відладчику в моїй діяльності onCreate()та в onPostCreate(). Як я можу визначити точні розміри цього виду під час виконання?


1
О, також я повинен зазначити, що сам вигляд напевно не має 0 ширини / висоти. Він відображається на екрані просто чудово.
Нік Рейман

Чи можете ви розмістити тег <ImageView ... /> у форматі XML?
fhucho

Я боровся з багатьма рішеннями цієї проблеми, поки не зрозумів, що в моєму випадку розміри View відповідають фізичному екрану (мій додаток "занурює" і View і всі його ширини та висоти встановлюються match_parent). У цьому випадку більш просте рішення, яке можна сміливо викликати ще до того, як перегляд буде намальований (наприклад, з методу onCreate () вашої діяльності), просто використовувати 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? Дві речі, перш за все, оскільки це слухач, я очікував би одного зворотного дзвінка, а наступного, як тільки ми отримаємо перший зворотний дзвінок, ми видаляємо слухача. Я щось пропустив?
Vikram Bodicherla

4
Перш ніж викликати будь-які методи ViewTreeObserver, рекомендується перевірити isAlive (): якщо (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Пітер Тран

4
будь-яка альтернатива removeGlobalOnLayoutListener(this);? тому що вона застаріла.
Сіббс Азартні ігри

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. Якщо ви пишете в Котліні, ви можете використовувати наступну функцію, яка за лаштунками працює точно так, як runJustBeforeBeingDrawnя написав:

    view.doOnPreDraw { actionToBeTriggered() }

    Зауважте, що вам потрібно додати це до gradle (знайдене тут ):

    implementation 'androidx.core:core-ktx:#.#'

1
Здається, що рішення №1 не завжди спрацює OnPreDrawListener. Випробуваний випадок, коли ви хочете запустити код з "Діяльності", onCreate()і ви негайно переглянете додаток. Легко повторити, особливо на повільному пристрої. Якщо ви заміните OnPreDrawListenerна OnGlobalLayoutListener- ви не побачите тієї самої проблеми.
запитувач

@questioner Я не розумію, але я щасливий, що він допоміг вам врешті-решт.
андроїд розробник

Хоча я зазвичай використовую ViewTreeObserverабо post, у моєму випадку, номер 2 допоміг ( view.measure).
CoolMind

3
@CoolMind Ви також можете використовувати новий метод, якщо ви використовуєте Kotlin. Оновлена ​​відповідь, щоб включити її теж.
андроїд розробник

Чому kotlin має додаткові функції для цього? google божевільний, ця мова марна (просто слід залишатися з Java), вона набагато старша за швидку, але лише swift отримала достатню популярність tiobe.com/tiobe-index (swift 12 позиція та kotlin 38)
user924

18

Ви дзвоните getWidth()до того, як перегляд фактично викладений на екран?

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


2
Так це означає, що мені потрібно переосмислити ImageView, щоб просто onSizeChange()зрозуміти, чи є реальна величина події? Це здається трохи надмірним ... хоча на даний момент я відчайдушно намагаюся працювати з кодом, тож варто його зняти.
Нік Рейман

Я більше думав, як чекати, поки ваш додаток onCreate () завершиться у вашій діяльності.
Марк Б

1
Як уже згадувалося, я спробував поставити цей код, у onPostCreate()якому запускається після того, як представлення повністю створено та розпочнеться.
Нік Рейман

1
Звідки цитований текст?
інтригації

1
ця цитата для власного перегляду, не публікуйте її для такої відповіді, коли ми запитуємо про розмір перегляду в цілому
user924

17

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

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


Добре знати. Якщо я знайду щось, що дозволило б вам обійти необхідність підкласировувати Вигляд, я опублікую його тут. Я трохи здивований, що onPostCreate () не працював.
Марк Б

Ну, я спробував це, і, здається, працює. Просто насправді не найкраще рішення, оскільки цей метод часто телефонує не один раз на початку. :(
Тобіас Райх

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


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
Вам не потрібно відкладати затримку, коли вона перебуває у слухачі клацання. Ви нічого не можете натиснути, доки перегляди не вимірять себе та не з’являться на екрані. Таким чином, вони вже будуть виміряні часом, коли ви зможете натиснути на них.
afollestad

Цей код неправильний. Затримка після публікації не гарантує, що в наступному галочку буде оцінено ваш перегляд.
Ян Вонг

Я б рекомендував не ходити на postDelayed (). Це, швидше за все, не станеться, але що робити, якщо ваш перегляд не вимірюється протягом 1 секунди, і ви зателефонуєте на цю програму, щоб отримати ширину / висоту подання. Це все одно призведе до 0. Редагувати: О, і btw, що 1 знаходиться в мілісекундах, тож воно б точно не вийшло.
Олексій

4

Я також втратив близько getMeasuredWidth()і getMeasuredHeight() getHeight()і в getWidth()протягом тривалого часу .......... пізніше я виявив , що отримання ширини і висоти думки , в onSizeChanged()це кращий спосіб зробити це ........ ви можете динамічно отримайте поточну ширину та поточну висоту свого перегляду, перемінивши йогоonSizeChanged( метод).

Можливо, захочете поглянути на це, який має детальний фрагмент коду. Новий допис у блозі: як отримати розміри ширини та висоти користувальницького перегляду (розширює подання) в 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;
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.