onMeasure пояснення спеціального перегляду


316

Я намагався зробити спеціальний компонент. Я розширив Viewклас і роблю кілька малюнків onDrawпереоціненим методом. Чому мені потрібно переосмислити onMeasure? Якщо б я цього не зробив, все, що бачилося, правильно. Може хтось це пояснить? Як мені написати свій onMeasureметод? Я бачив кілька навчальних посібників, але кожен з них дещо відрізняється від іншого. Іноді вони дзвонять super.onMeasureнаприкінці, іноді користуються setMeasuredDimensionі не дзвонять. Де різниця?

Адже я хочу використовувати кілька абсолютно однакових компонентів. Я додав ці компоненти до свого XMLфайлу, але не знаю, наскільки вони мають бути великими. Я хочу встановити його положення та розмір пізніше (чому мені потрібно встановити розмір, onMeasureякщо в, onDrawколи я малюю, працює також) у користувацькому класі компонентів. Коли саме мені це потрібно зробити?

Відповіді:


735

onMeasure()- це ваша можливість сказати Android, наскільки великим ви хочете, щоб ваш власний вигляд залежав від обмежень щодо макета, передбачених батьком; це також можливість вашого власного перегляду дізнатися, що таке обмеження компонування (якщо ви хочете поводитись інакше в match_parentситуації, ніж у wrap_contentситуації). Ці обмеження пакуються в MeasureSpecзначення, передані методу. Ось приблизна кореляція значень режиму:

  • ТОЧНО означає, що layout_widthабо layout_heightзначення було встановлено на певне значення. Ви, ймовірно, повинні зробити ваш погляд такого розміру. Це також може спрацьовувати, коли match_parentвикористовується, щоб точно встановити розмір батьківського подання (це макет залежить від рамки).
  • AT_MOST зазвичай означає, що значення layout_widthабо layout_heightвстановлено на match_parentабо, wrap_contentде потрібен максимальний розмір (це макет залежить від рамки), а розмір батьківського виміру - це значення. Ви не повинні бути більшими за цей розмір.
  • UNSPECIFIED зазвичай означає, що значення layout_widthабо layout_heightвстановлено wrap_contentбез обмежень. Ви можете бути будь-якого розміру, який хочете. Деякі макети також використовують цей зворотний дзвінок, щоб визначити потрібний розмір, перш ніж визначити, які саме характеристики насправді передадуть вам у запиті другої міри.

Договір, який існує, onMeasure()полягає в тому, що setMeasuredDimension() ОБОВ'ЯЗКОВО зателефонувати в кінці того розміру, який ви хотіли б мати. Цей метод викликається всіма реалізаціями фреймворку, включаючи реалізацію за замовчуванням View, яку ви знайдете, і тому superзамість цього безпечно дзвонити, якщо це відповідає вашому випадку використання.

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

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

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

Сподіваюся, що це допомагає.


1
Привіт @Devunwired приємне пояснення найкраще, що я читав до цих пір. Ваше пояснення відповіло на безліч питань, які у мене виникли, і я усунув деякі сумніви, але все ще залишається одне: якщо мій власний погляд знаходиться всередині ViewGroup разом з деякими іншими переглядами (не важливо, які типи), то ViewGroup отримає всіх своїх дітей для кожного зонда на їх обмеження LayoutParams і попросіть кожну дитину самостійно виміряти його відповідно до своїх обмежень?
фараон

47
Зауважте, що цей код не буде робити, якщо ви перекриєте onMeasure будь-якого підкласу ViewGroup. Ваші субпрезентації не відображатимуться, і всі вони матимуть розмір 0x0. Якщо вам потрібно змінити onMeasure для користувальницького ViewGroup, змінити widthMode, widthSize, heightMode та heightSize, скомпілюйте їх назад до мераSpecs за допомогою MeasureSpec.makeMeasureSpec та передайте отримані цілі числа на super.onMeasure.
Олексій

1
Фантастична відповідь. Зауважте, що відповідно до документації Google, відповідальність за перегляд покладок відповідає за перегляд.
Jonstaff

4
Над складним c ** p, що робить Android болючою системою планування для роботи. Вони могли просто мати getParent (). Get *** () ...
Олівер Діксон

2
У Viewкласі є допоміжні методи , які називаються resolveSizeAndStateі resolveSize, які повинні робити те, що робити "якщо" - я вважаю їх корисними, особливо якщо вам потрібно часто писати ці ПЧ.
stan0

5

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

  • ТОЧНО match_parent є ТОЧНО + розмір батьківського
  • AT_MOST wrap_content призводить до AT_MOST MeasureSpec
  • НЕВЕРОЯТНО ніколи не спрацьовує

У разі подання горизонтальної прокрутки ваш код буде працювати.


57
Якщо ви вважаєте, що відповідь тут неповна, додайте її, а не часткову відповідь.
Michaël

1
Добре, що я пов’язав це з тим, як працюють макети, але в моєму випадку onMeasure викликається три рази для мого спеціального перегляду. Розглянута думка мала висоту вмісту wrap_content та зважену ширину (ширина = 0, вага = 1). Перший дзвінок був НЕВЕРОЯТНО / НЕВЕРОЯТНО, другий мав AT_MOST / ТОЧНО, а третій ТОЧНО / ТОЧНО.
Вільям Т. Маллард

0

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

Код Devunwired (обрана і найбільш проголосована відповідь тут) майже ідентичний тому, що реалізація SDK вже робить для вас (і я перевірив - це робив з 2009 року).

Ви можете перевірити метод onMeasure тут :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

Переопределення коду SDK, який слід замінити на той самий код, не має сенсу.

Цей офіційний документ документа, який стверджує, що "за замовчуванням onMeasure () завжди встановлюватиме розмір 100x100" - неправильно.

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