Авторизаційний спосіб перевизначення onMeasure ()?


88

Який правильний спосіб замінити onMeasure ()? Я бачив різні підходи. Наприклад, Професійна розробка Android використовує MeasureSpec для обчислення розмірів, а потім закінчується викликом setMeasuredDimension (). Наприклад:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

З іншого боку, згідно з цим повідомленням , "правильним" способом є використання MeasureSpec, виклик setMeasuredDimensions (), після чого виклик setLayoutParams () і закінчення викликом super.onMeasure (). Наприклад:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Тож який правильний шлях? Жоден з підходів не спрацював на 100% для мене.

Думаю, насправді я запитую, чи хтось знає підручник, який пояснює onMeasure (), макет, розміри дочірніх подань тощо?


15
Ви знайшли відповідь корисною / правильною? чому б не відзначити його як в відповідь?
superjos

Відповіді:


68

Інші рішення не є комплексними. Вони можуть працювати в деяких випадках і є хорошим місцем для початку, але вони можуть не гарантовано працювати.

Коли викличуть onMeasure, ви можете або не мати права змінювати розмір. Значення, які передаються у ваш onMeasure ( widthMeasureSpec, heightMeasureSpec), містять інформацію про те, що дозволено робити вашому дочірньому представленню. В даний час існує три значення:

  1. MeasureSpec.UNSPECIFIED - Ти можеш бути таким великим, як хочеш
  2. MeasureSpec.AT_MOST- Скільки завгодно (до розміру специфікації), це parentWidthу вашому прикладі.
  3. MeasureSpec.EXACTLY- Немає вибору. Вибрав батько.

Це зроблено для того, щоб Android міг зробити кілька проходів, щоб знайти потрібний розмір для кожного елемента, див. Тут для отримання додаткової інформації.

Якщо ви не дотримуєтесь цих правил, ваш підхід не гарантує успіху.

Наприклад, якщо ви хочете перевірити, чи дозволено вам взагалі змінювати розмір, ви можете зробити наступне:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

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

int resolSizeAndState (розмір int, int measureSpec, int childMeasuredState)

int resolSize (розмір int, int measureSpec)

Хоча перший доступний лише в Honeycomb, другий доступний у всіх версіях.

Примітка: Ви можете виявити, що це resizeWidthабо resizeHeightзавжди є помилковими. Я виявив, що це так, якщо я запитую MATCH_PARENT. Я зміг це виправити, запитувавши WRAP_CONTENTна моєму батьківському макеті, а потім під час НЕ ВКАЗАНО запитуючи розмір Integer.MAX_VALUE. Це дає вам максимальний розмір, який дозволяють ваші батьки під час наступного проходження onMeasure.


39

Документація є повноваженнями з цього питання: http://developer.android.com/guide/topics/ui/how-android-draws.html та http://developer.android.com/guide/topics/ui/custom -components.html

Підсумовуючи: наприкінці вашого перевизначеного onMeasureметоду вам слід зателефонуватиsetMeasuredDimension .

Вам не слід телефонувати super.onMeasureпісля дзвінка setMeasuredDimension, це просто видалить все, що ви встановили. У деяких ситуаціях вам може знадобитися зателефонувати super.onMeasureпершому, а потім змінити результати, зателефонувавши setMeasuredDimension.

Чи не викликайте setLayoutParamsв onMeasure. Макет відбувається за другий прохід після вимірювання.


Мій досвід полягає в тому, що якщо я перевизначую onMeasure, не викликаючи super.onMeasure, OnLayout не викликається для дочірніх подань.
Вільям Джокуш,

2

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

Наприклад, якщо ви розширюєте ViewGroup (наприклад, FrameLayout), коли ви виміряли розмір, вам слід зателефонувати, як показано нижче

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

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

Якщо ви розширюєте подання (як ImageView), ви можете просто зателефонувати this.setMeasuredDimension(width, height);, оскільки батьківський клас просто зробить щось, як ви зазвичай робили.

Одним словом, якщо ви хочете отримати деякі функції, які ваш батьківський клас пропонує безкоштовно, вам слід зателефонувати super.onMeasure()(передайте MeasureSpec.ЕКСЧАТНО режим зазвичай вимірюйте специфікацію), інакше this.setMeasuredDimension(width, height);достатньо дзвінка .


1

ось як я вирішив проблему:

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

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

Більше того, це було потрібно для компонента ViewPager


3
Це не належне рішення. super.onMeasureочистить розміри, встановлені за допомогою setMeasuredDimension.
tomrozb

@tomrozb це не humptydevelopers.com/2013/05/…, і ви можете перевірити джерела Android
Yuli Reiri

@YuliReiri: Ідеальне рішення!
Shayan

0

Якщо ви змінюєте розмір подання всередині, onMeasureвам потрібен лише setMeasuredDimensionдзвінок. Якщо ви змінюєте розмір за межами onMeasureвас, вам потрібно зателефонувати setLayoutParams. Наприклад, зміна розміру подання тексту при зміні тексту.


Ви все ще можете викликати setMinWidth () та setMaxWidth () і мати стандартний onMeasure, який обробляє це. Я виявив, що краще не викликати setLayoutParams зсередини власного класу View. Він справді використовується для ViewGroups або Activity, щоб замінити поведінку дочірнього перегляду.
Доррін

0

Залежить від контролю, яким ви користуєтесь. Інструкції в документації працюють для деяких елементів керування (TextView, Button, ...), але не для інших (LinearLayout, ...). Шлях, який дуже добре працював для мене, - це зателефонувати супер, як тільки я закінчу. На підставі статті в посиланні нижче.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html


-1

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

Однак це рідко працює правильно (з якоїсь причини ...), краще викликати measureChildren (при отриманні ViewGroup) або спробувати щось подібне, коли це необхідно.


-5

ви можете взяти цей шматок коду як приклад onMeasure () ::

public class MyLayerLayout extends RelativeLayout {

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

6
Цей код принципово помилковий. По-перше, він не враховує режим макетування (див. Відповідь Гриммаса). Це погано, але ви не побачите ефекту від цього, тому що ви також викликаєте super.onMeasure () у самому кінці, який перекриває значення, які ви встановили (див. Відповідь satur9nine), і перешкоджає цілі перевизначення onMeasure () взагалі.
spaaarky21
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.