Перегляд getWidth (), а getHeight () повертає 0


513

Я створюю всі елементи мого проекту 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);
}

Будь-яка допомога вдячна.

Відповіді:


197

Ви телефонуєте getWidth()занадто рано. Користувацький інтерфейс ще не розміщений і не розміщений на екрані.

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

Якщо говорити, ви можете використовувати ресурсний параметр, щоб визначити розмір кнопки, а потім посилатися на цей ресурсний параметр з файлу макета та вихідного коду, щоб уникнути цієї проблеми.


82
ngreenwood6, яке ваше інше рішення?
Андрій

12
@Andrew - якщо ви хочете, щоб negreenwood6 отримував сповіщення про своє подальше сповіщення, ви повинні почати своє повідомлення, як я зробив вам (я думаю, що перших трьох листів достатньо) - CommonsWare отримує повідомлення автоматично, оскільки він написав цю відповідь, але ngreen doesn якщо ви не звертаєтесь до них.
Пітер Аджай

11
@Override public void onWindowFocusChanged (булева hasFocus) {// TODO Автоматично створений метод stub super.onWindowFocusChanged (hasFocus); // Тут ви можете отримати розмір! }
Сана

5
@ ngreenwood6 Тож яким було ваше рішення?
Німа Вазірі

5
Використовуйте цей слухач, щоб отримати розмір, коли ваш екран готовий. view.getViewTreeObserver (). addOnGlobalLayoutListener (новий ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević

815

Основна проблема полягає в тому, що вам доведеться чекати фази креслення для фактичних вимірювань (особливо з такими динамічними значеннями, як wrap_contentабо match_parent), але зазвичай ця фаза ще не закінчена onResume(). Тож вам потрібне вирішення для очікування цієї фази. Для цього є різні можливі рішення:


1. Прослуховуйте події малювання / макета: ViewTreeObserver

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. Додати черговий бік до черги макета: View.post ()

Не дуже відоме і моє улюблене рішення. В основному просто використовуйте метод публікації публікації з власним запуском. Це, в основному, черги з вашим кодом після вимірювання, компонування і т.д., як це заявив Ромен Гай :

Черга подій UI буде обробляти події в порядку. Після виклику setContentView () черга подій буде містити повідомлення із запитом про перенавантаження, тому все, що ви надсилаєте до черги, відбуватиметься після проходу макета

Приклад:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

Перевага перед ViewTreeObserver:

  • ваш код виконується лише один раз, і вам не доведеться вимикати спостерігача після його виконання, що може скласти клопоти
  • менш багатослівний синтаксис

Список літератури:


3. Перезапишіть метод onLayout Views

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

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. Перевірте, чи пройшла фаза компонування

Якщо у вас є код, який виконується кілька разів під час створення інтерфейсу, ви можете використовувати такий метод підтримки v4 lib:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Повертає істину, якщо погляд пройшов принаймні один макет з моменту останнього прикріплення до вікна.


Додатково: отримання статично визначених вимірювань

Якщо досить просто отримати статично задану висоту / ширину, ви можете просто зробити це за допомогою:

Але зауважте, що після малювання це може відрізнятися від фактичної ширини / висоти. Явадок чудово описує різницю:

Розмір виду виражається шириною та висотою. Вид фактично має дві пари значень ширини та висоти.

Перша пара відома як вимірювана ширина і вимірювана висота. Ці параметри визначають, наскільки великий вигляд хоче бути у його батьківського (див. Макет для отримання більш детальної інформації.) Вимірені розміри можна отримати, викликавши getMeasuredWidth () та getMeasuredHeight ().

Друга пара просто відома як ширина та висота, а іноді ширина малювання та висота малюнка. Ці розміри визначають фактичний розмір зображення на екрані, під час малювання та після компонування. Ці значення можуть, але не повинні, відрізнятися від вимірюваної ширини та висоти. Ширину та висоту можна отримати, зателефонувавши getWidth () та getHeight ().


1
Я використовую фрагмент view.post у фрагменті. Мені потрібно виміряти вигляд, зріст батька якого становить 0dp і має набір ваги. Все, що я намагаюсь, повертає висоту нуля (я також спробував глобальний слухач верстки) Коли я поміщаю код view.post в onViewCreate із затримкою 125, він працює, але не без затримки. Ідеї?
Psest328

6
занадто погано, що ви можете отримати відповідь лише один раз, дуже добре пояснення. У мене виникла проблема з обертанням перегляду, якщо я закрив додаток і перезапустив його з останніх програм. Якщо я провев її звідти і зробив новий перезапуск, все було добре. View.post () врятував мені день!
Маттіас

1
Дякую. Зазвичай я використовую View.post () (2-й метод), але якщо він викликається з фонової нитки, він іноді не працює. Отже, не забудьте написати runOnUiThread(new Runnable() {...}. В даний час я використовую зміна 1 - го методу: addOnLayoutChangeListener. Не забудьте видалити його потім всередині: removeOnLayoutChangeListener. Він працює і з фонової нитки.
CoolMind

2
На жаль, для мене View.post () не працює весь час. У мене є 2 проекти, які дуже схожі. В одному проекті він працює, в іншому не працює. :(. В обох ситуаціях я називаю метод з методу "onCreate ()" з діяльності. Будь-яка пропозиція, чому?
Адріан Буціан

1
Я знаходжу це питання. В одному проекті представлення має android:visibility="gone", а в іншому проект властивість видимості invisible. Якщо видимість goneширини завжди буде 0.
Адріан Буціан

262

Ми можемо використовувати

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }

10
тож як ми можемо працювати в фрагментах? це працює лише в діяльності?
Олкунмустафа

8
Це має робити, але це не повинно бути відповіддю. Користувач хоче отримати розміри в будь-який момент часу замість того, щоб отримати малюнок у вікні. Також цей метод називається багаторазово або щоразу, коли у вікні є зміни (Переглянути приховати, піти, додавати нові види тощо). Тому використовуйте це обережно :)
Доктор aNdRO

1
Це злом, і, схоже, краще використовувати ViewTreeObserver.
i_am_jorf

Не майте на увазі звучати грубо, але залишаючи автоматично сформований коментар (особливо в 2-х рядках коду) не дає великої впевненості, що ви насправді знаєте, що робите.
Натікс

На жаль, мені це не вийшло. Спроба viewTreeObserver працювала на мене.
Нарендра Сінгх

109

Я використав це рішення, яке, на мою думку, краще, ніж 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 :)
}

4
це, безумовно, найкраще рішення! тому що він також працює на динамічних рішеннях, де ширина і висота не відомі і не обчислюються. тобто у відносному макеті на основі позицій / розмірів інших елементів. Молодці!
Джордж Плігоропулос

2
Для цього потрібен API 16 або новішої версії (Jelly bean)
tomsv

7
Це НЕ ВИМОГА API 16! Єдине питання , є метод removeGlobalOnLayoutListener застарілим з API 16, але є просте рішення сумісно з усіма рівнями API - stackoverflow.com/questions/16189525 / ...
Гінкго

Якщо вам потрібно видалити слухача, щоб він не називав себе багато разів, і у вас виникли проблеми з різними API, я рекомендую вам встановити помилкове булеве значення на чолі класу, а потім після захоплення значень встановити його на істинне, так що він знову не захоплює значення.
Мансур Фахад

Якщо ви видалите слухача після першого запуску, ви можете отримати неправильні розміри: 02-14 10: 18: 53,786 15063-15063 / PuzzleFragment: висота: 1647 ширина: 996 02-14 10: 18: 53,985 15063-15063 / PuzzleFragment: висота : 1500 ширина: 996
Леос Літерак

76

Як зазначає Ян у цій темі для розробників Android :

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

Підсумок, якщо ви викликаєте getWidth () тощо у конструкторі, він поверне нуль. Процедура полягає у створенні всіх елементів перегляду у конструкторі, а потім дочекайтеся виклику методу onSizeChanged () вашого перегляду - саме тоді ви вперше дізнаєтесь свій реальний розмір, тож саме тоді ви встановите розміри елементів GUI.

Також пам’ятайте, що іноді викликається onSizeChanged () з параметрами нуля - перевірте цей випадок і поверніться негайно (так що ви не отримаєте ділення на нуль при обчисленні вашого макета тощо). Через деякий час він буде називатися справжніми значеннями.


8
onSizeChanged працював на мене. onWindowFocusedChanged не називається в моєму власному режимі перегляду.
aaronmarino

це виглядає гарним рішенням, але що я завжди повинен створювати свій власний вигляд, щоб перекрити метод onSizeChanged?
M.ElSaka

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.це бінго. Корінь моїх питань.
MikeNereson

Я пропоную це рішення для фрагментів, оскільки деякі інші рішення в моєму випадку повертають 0.
WindRider

66

Якщо вам потрібно отримати ширину якогось віджета, перш ніж він відображатиметься на екрані, ви можете використовувати getMeasuredWidth () або getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();

5
Чомусь це була єдина відповідь, яка працювала на мене - дякую!
Farbod Salamat-Zadeh

Те саме, найпростіше рішення, яке працювало на мене
Тіма,

Працюй як шарм!
Моралес Батовський

1
@CommonsУважте, як це рішення дає висоту та ширину перед спостерігачем дерев перегляду для зворотного виклику глобальних макетів, пояснення було б корисним.
Mightian

22

Я вважаю за краще використовувати 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;
        }
    });

3
Зверніть увагу , що return falseкошти для скасування поточного креслення пасу . Щоб продовжити, return trueнатомість.
Панг

9

Розширення 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)
}

6

AndroidX має декілька функцій розширення, які допомагають вам у подібній роботі всередині androidx.core.view

Для цього вам потрібно використовувати Котлін.

Тут найкраще підходить doOnLayout:

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

Дію буде запущено лише один раз у наступному макеті, а потім видалено.

У вашому прикладі:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Залежність: androidx.core:core-ktx:1.0.0


Це єдина правильна відповідь. android.googlesource.com/platform/frameworks/support/+/…
hrules6872

1
ці розширення дійсно важливі та корисні
Ахмед

5

Один вкладиш, якщо ви використовуєте RxJava & RxBindings . Аналогічний підхід без котла. Це також вирішує хак для придушення попереджень, як у відповіді Тіма Отіна .

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

Ось воно. Не потрібно скасовувати підписку, навіть якщо ніколи не телефонував.


4

Це трапляється тому, що погляд повинен бути надутим більше. Тож замість дзвінка view.widthта view.heightв основній темі, ви повинні використовувати, view.post { ... }щоб переконатися, що ваш viewуже завищений. У Котліні:

view.post{width}
view.post{height}

На Java ви також можете зателефонувати getWidth()та getHeight()методи в a Runnableта передати метод Runnableto view.post().

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });

Це неправильна відповідь, оскільки представлення не може бути виміряно та викладено, коли виконується розміщений код. Це був би кращий випадок, але .postне гарантує вигляд.
d.aemon

1
Ця відповідь була чудовою і спрацювала для мене. Дуже дякую Ehsan
MMG

3

Висота та ширина дорівнюють нулю, оскільки представлення не було створено до моменту, коли ви запитуєте, це висота та ширина. Одне з найпростіших рішень

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

Цей метод хороший порівняно з іншими методами, оскільки він короткий і чіткий.


3

Можливо, це комусь допомагає:

Створіть функцію розширення для класу View

ім'я файлу: 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
}

2

Відповідь з postневірна, тому що розмір може бути не перерахований.
Інша важлива річ, що погляд і всі його предки повинні бути видні . Для цього я використовую власність View.isShown.

Ось моя функція kotlin, яку можна розмістити десь утиліти:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

А використання таке:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}

1

Якщо ви використовуєте 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

Пропадені подання повертає 0 як висоту, якщо програма у фоновому режимі. Це мій код (1oo% працює)

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

0

Потрібно чекати, коли перегляд буде намальовано. Для цього використовуйте OnPreDrawListener. Приклад Котліна:

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