15

Предыдущая версия моего вопроса была слишком многословной. Люди не могли понять это, поэтому следующее полное переписывание. См. edit history, если вас интересует старая версия.Пользовательский вид onMeasure: как получить ширину по высоте

RelativeLayout родителя отправляет MeasureSpecs к onMeasure методе своего дочернего зрения для того, чтобы увидеть, как большой ребенок хотел бы быть. Это происходит в нескольких проходах.

Мой заказ вид

У меня есть custom view. По мере увеличения содержания представления увеличивается высота представления. Когда представление достигает максимальной высоты, которую разрешает родитель, ширина представления увеличивается для любого дополнительного содержимого (если для ширины выбрано wrap_content). Таким образом, ширина пользовательского представления напрямую зависит от того, какой родитель говорит, что максимальная высота должна быть.

enter image description here

An (несогласованный) родитель ребенка разговор

onMeasure проход 1

RelativeLayout родитель говорит мой взгляд, «Вы можете быть любой ширины up to900 и любой высоты up to600 . Что ты говоришь?"

мой взгляд говорит: «Ну, на такой высоте, я могу поместить все с шириной 100. Так что я возьму ширину 100 и высоту 600

onMeasure проход 2

RelativeLayout родитель говорит мой взгляд, «Вы сказали мне в прошлый раз, что вы хотели ширину 100, поэтому давайте установим, что в качестве exact ширины. Теперь, на основании этой ширины, какую высоту вы хотели бы? Ничего up to500 в порядке. "

"Эй!" мой взгляд отвечает. «Если вы только даете мне максимум высоты 500, то 100 слишком узкий. Мне нужна ширина 200 для этой высоты. Но хорошо, сделайте это по-своему. Я не нарушу правила (еще .. .). Я возьму ширину 100 и высоту 500. "

Окончательный результат

RelativeLayout родитель назначает окончательный вид размером 100 для ширины и 500 на высоте. Это, конечно, слишком узко для просмотра, и часть содержимого обрезается.

«Вздох», - думает мой взгляд. «Почему мой родитель не позволит мне быть шире? Существует много места. Может быть, кто-то из Stack Overflow может дать мне несколько советов."

+0

Я думаю, что его вопрос о ваших единиц, пожалуйста, рассмотреть возможность использования только одной единицы дп или точек в ширину и высоту. – Mohamed

+0

@Mohamed, Мой пример здесь использует только px. – Suragch

+0

Когда я использую ваш пользовательский вид внутри LinearLayout, он работает нормально. Вы попробовали это? – Krish

ответ

6

Обновление: Измененный код для исправления некоторых вещей.

Во-первых, позвольте мне сказать, что вы задали большой вопрос и изложил эту проблему очень хорошо Вот мой идти на решение (дважды!):

Кажется, что есть много происходит с onMeasure что, на первый взгляд, не имеет большого смысла. Так как это так, мы допустим, что onMeasure будет выполняться так, как он будет, и в конце выведите решение на границах View в onLayout, установив mStickyWidth на новую минимальную ширину, которую мы примем. В onPreDraw, используя ViewTreeObserver.OnPreDrawListener, мы вынудим еще один макет (requestLayout). Из documentation (подчеркивание добавлено):

boolean onPreDraw()

метод обратного вызова, который будет вызван, когда представление дерева собирается быть обращено. На этом этапе все представления в дереве были измерены и получили кадр. Клиенты могут использовать это, чтобы скорректировать их полосы прокрутки или , даже до запросить новый макет перед тем, как нарисовать рисунок.

Новая минимальная ширина устанавливается в onLayout теперь будет исполнена на onMeasure который теперь умнее о том, что возможно.

Я проверил это с вашим примером кода и, похоже, работает нормально. Это потребует гораздо большего тестирования. Могут быть другие способы сделать это, но это суть подхода.

CustomView.java

import android.content.Context; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.View; 
import android.view.ViewTreeObserver; 

public class CustomView extends View 
     implements ViewTreeObserver.OnPreDrawListener { 
    private int mStickyWidth = STICKY_WIDTH_UNDEFINED; 

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

    public CustomView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    @Override 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
     logMeasureSpecs(widthMeasureSpec, heightMeasureSpec); 
     int desiredHeight = 10000; // some value that is too high for the screen 
     int desiredWidth; 

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

     int width; 
     int height; 

     // Height 
     if (heightMode == MeasureSpec.EXACTLY) { 
      height = heightSize; 
     } else if (heightMode == MeasureSpec.AT_MOST) { 
      height = Math.min(desiredHeight, heightSize); 
     } else { 
      height = desiredHeight; 
     } 

     // Width 
     if (mStickyWidth != STICKY_WIDTH_UNDEFINED) { 
      // This is the second time through layout and we are trying renogitiate a greater 
      // width (mStickyWidth) without breaking the contract with the View. 
      desiredWidth = mStickyWidth; 
     } else if (height > BREAK_HEIGHT) { // a number between onMeasure's two final height requirements 
      desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number 
     } else { 
      desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number 
     } 

     if (widthMode == MeasureSpec.EXACTLY) { 
      width = widthSize; 
     } else if (widthMode == MeasureSpec.AT_MOST) { 
      width = Math.min(desiredWidth, widthSize); 
     } else { 
      width = desiredWidth; 
     } 

     Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")"); 
     setMeasuredDimension(width, height); 
    } 

    @Override 
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 
     int w = right - left; 
     int h = bottom - top; 

     super.onLayout(changed, left, top, right, bottom); 
     // Here we need to determine if the width has been unnecessarily constrained. 
     // We will try for a re-fit only once. If the sticky width is defined, we have 
     // already tried to re-fit once, so we are not going to have another go at it since it 
     // will (probably) have the same result. 
     if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER) 
       && (mStickyWidth == STICKY_WIDTH_UNDEFINED)) { 
      mStickyWidth = ARBITRARY_WIDTH_GREATER; 
      getViewTreeObserver().addOnPreDrawListener(this); 
     } else { 
      mStickyWidth = STICKY_WIDTH_UNDEFINED; 
     } 
     Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth); 
    } 

    @Override 
    public boolean onPreDraw() { 
     getViewTreeObserver().removeOnPreDrawListener(this); 
     if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width. 
      return true; 
     } 

     Log.d(TAG, ">>>>onPreDraw() requesting new layout"); 
     requestLayout(); 
     return false; 
    } 

    protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) { 
     int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
     int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
     int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
     int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
     String measureSpecHeight; 
     String measureSpecWidth; 

     if (heightMode == MeasureSpec.EXACTLY) { 
      measureSpecHeight = "EXACTLY"; 
     } else if (heightMode == MeasureSpec.AT_MOST) { 
      measureSpecHeight = "AT_MOST"; 
     } else { 
      measureSpecHeight = "UNSPECIFIED"; 
     } 

     if (widthMode == MeasureSpec.EXACTLY) { 
      measureSpecWidth = "EXACTLY"; 
     } else if (widthMode == MeasureSpec.AT_MOST) { 
      measureSpecWidth = "AT_MOST"; 
     } else { 
      measureSpecWidth = "UNSPECIFIED"; 
     } 

     Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: " 
       + measureSpecHeight + ", " + heightSize); 
    } 

    private static final String TAG = "CustomView"; 
    private static final int STICKY_WIDTH_UNDEFINED = -1; 
    private static final int BREAK_HEIGHT = 1950; 
    private static final int ARBITRARY_WIDTH_LESSER = 200; 
    private static final int ARBITRARY_WIDTH_GREATER = 800; 
} 
+0

Я очень рад видеть ответ, который действительно решает проблему. Я собирался отказаться от надежды. – Suragch

+0

Это интригующая проблема - повесьте там. Когда я борюсь с платформой Android, платформа обычно выигрывает, но на этот раз мы можем выйти вперед. – Cheticamp

+1

Я тестировал это как на MCVE, так и на моем фактическом пользовательском коде. Оба сейчас работают в ситуациях, которые я описал в своем вопросе. – Suragch

1

Чтобы сделать пользовательский макет, который вы должны прочитать и понять эту статью https://developer.android.com/guide/topics/ui/how-android-draws.html

Это не трудно реализовать поведение, которое вы хотите. Вам просто нужно переопределить onMeasure и OnLayout в пользовательском представлении.

В onMeasure вы получите максимальную возможную высоту своего пользовательского вида и меры вызова() для дочерних элементов в цикле. После того, как детское измерение получит желаемую высоту от каждого дочернего элемента и вычисляет, соответствует ли ребенок в текущем столбце или нет, если не увеличивать пользовательский вид широко для новых колонка

В onLayout вы должны позвонить в layou t() для всех дочерних представлений, чтобы установить их позиции в родительском. Это позиции, которые вы подсчитали в onMeasure раньше.

+0

В моем 'onMeasure' я подсчитываю, насколько широка моя точка зрения. Однако к моменту, когда я дойду до 'onLayout', неправильный размер был выбран родителем' RelativeLayout' (не во всех ситуациях, но конкретно, когда это «layout_below», другой вид, а содержимое просто обтекает второй столбец на определенная высота). Фактический код [здесь] (https://github.com/suragch/mongol-library/blob/master/mongol-library/src/main/java/net/studymongolian/mongollibrary/MongolTextView.java) (или в редактируйте историю моего вопроса). – Suragch