Просмотры внутри пользовательской группы просмотра не отображаются после изменения размера

#android #resize #rendering #custom-view #viewgroup

#Android #изменение размера #визуализация #пользовательский вид #viewgroup

Вопрос:

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

Я работаю над приложением, которое использует пользовательскую ViewGroup (фактически FrameLayout, который содержит RelativeLayout) для отображения календаря событий. События внутри календаря представлены в виде представлений, размер которых соответствует продолжительности события и размеру содержащего представления.

Я сталкиваюсь с проблемой, которая возникает при изменении размера содержащего FrameLayout. Текущая реализация удаляет все представления, представляющие события, и пытается добавить новые и рассчитать их размеры на основе текущего размера FrameLayout. Эта работа запускается с помощью метода просмотра onSizeChanged (), который переопределяется в FrameLayout.

При изменении размера представления выполняется этот код и представления обновляются, однако ни одно из них фактически не отображается на экране… представления, содержащиеся в FrameLayout, просто не видны. Если я загружаю представление в инструмент hierarchyviewer, они являются частью дерева представлений и отображаются в обзоре в тех позициях, в которых они должны находиться, но они не отображаются. (Обратите внимание, что представления видны при первоначальном рендеринге FrameLayout … они исчезают только после изменения размера.)

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

 onMeasure()
onMeasure()
onSizeChanged()
onLayout()
  

Вызов requestLayout () после сброса представлений (внутри onSizeChanged()), похоже, не имеет никакого эффекта. Однако, если я вызываю некоторую задержку перед вызовом requestLayout (), представления становятся видимыми. Я могу вызвать эту задержку, запустив поток и перейдя в спящий режим, или создав фиктивную кнопку, которая просто вызывает requestLayout () и нажимает ее после изменения размера самостоятельно, или даже этот уродливый хак, размещенный в конце onSizeChanged ():

 post(new Runnable() {
    public void run() {
        requestLayout();
    }
});
  

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

 onMeasure()
onMeasure()
onSizeChanged()
onLayout()
onMeasure()
onMeasure()
onLayout()
  

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

Кто-нибудь может предоставить какие-либо указания относительно того, что я делаю неправильно?

Я понимаю, что это несколько сложно понять, не взглянув на некоторый код, поэтому я создал небольшой образец приложения, который демонстрирует мою проблему, и сделал его доступным на Github:

https://github.com/MichaelSims/ViewGroupResizeTest

Упомянутый выше взлом зафиксирован в отдельной ветке:

https://github.com/MichaelSims/ViewGroupResizeTest/tree/post-runnable-hack

Если я могу предоставить какую-либо дополнительную информацию, пожалуйста, дайте мне знать и заранее спасибо.

Ответ №1:

Это не взлом, это то, как это должно работать. onSizeChanged() вызывается как часть прохода layout, и вы не можете / не должны запрашивать Layout() во время прохода layout. Правильно размещать requestLayout() в очереди событий, чтобы указать, что вы изменили иерархию представлений во время прохода макета.

Комментарии:

1. Фантастика! Большое спасибо, что нашли время ответить.

2. Почему onMeasure () следует за onLayout () вместо onDraw (), когда он отправляет requestLayout() в очередь событий?

3. черт возьми, я искал это объяснение часами. Имейте в виду эту функцию, если вы разрабатываете пользовательский макет, который работает аналогично ListView с помощью addViewInLayout — вам нужно опубликовать событие requestLayout через Runnalbe, чтобы обновить measure после добавления всех представлений

Ответ №2:

Я сталкиваюсь с той же проблемой.

Мой onMeasure () был чем-то вроде этого:

 @Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
        //calculate new width and height here
        ...
        setMeasuredDimension(newMeasureSpecWidth, newMeasureSpecHeight);
    }
  

Моя ошибка заключалась в том, что я вызывал super.onMeasure () в первой строке onMeasure (), поэтому внутренние дочерние элементы моей ViewGroup вычислялись на основе размера, который должен был измениться.

Итак, мое исправление делало что-то вроде этого:

 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    //calculate new width and height here
    ...
    int newMeasureSpecWidth = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
    int newMeasureSpecHeight = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
    super.onMeasure(newMeasureSpecWidth, newMeasureSpecHeight);
}
  

Итак, я устанавливал новый размер для моей ViewGroup, используя вызов super.onMeasure (), а затем он также передает новый размер своим дочерним элементам.

Помните: контракт при переопределении onMeasure() заключается в том, что вы должны вызвать setMeasuredDimension() (и вы можете достичь этого, вызвав сам метод или super.onMeasure())