ConstraintLayout GONE view occupies space
Asked Answered
D

3

10

I have a ViewHolder with the header on top and that header becomes visible in a specific case. In most other cases, the header is set as GONE. The problem is that when a header is set as GONE, its height is still calculated and other views are spread differently (with more space between).

Here is a layout blueprint: blueprint

Blueprint explanation:

  1. The header is constrained to top, left and right.
  2. Two views below are in packed chain, constrained to a header on top, ImageView to the right and parent to the left and bottom.

And here is a screenshot from the layout inspector with highlighted header view which is set as GONE: enter image description here

According to documentation, the header view, when set to GONE should be shrunk to point with constraints from other views still applied to it, but the header should not occupy layout space and affect the height of the ConstraintLayout as it is set as wrap_content.

In this inspector screenshot, it's not clear what happened actually. The header is not visible, a bottom view is obviously constrained to parent top, but the header is still shown in the inspector as full width with specified height.

I'm not sure if this is a bug or I should force ConstraintLayout to re-measure itself.

XML UPDATE:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <TextView
        android:id="@+id/list_item_step_conversion_tv_header"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/gray_very_light"
        android:padding="@dimen/activity_vertical_margin"
        android:text="@string/favorites"
        android:textColor="@android:color/black"
        android:textSize="@dimen/default_text_size"
        android:textStyle="bold"
        android:visibility="gone"
        tools:visibility="visible"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:id="@+id/list_item_step_conversion_tv_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="@color/gray_dark"
        android:textSize="@dimen/medium_text_size"
        app:layout_constraintBottom_toTopOf="@+id/list_item_step_conversion_tv_description"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/list_item_step_conversion_iv_favorite"
        app:layout_constraintTop_toBottomOf="@+id/list_item_step_conversion_tv_header"
        app:layout_constraintVertical_chainStyle="packed"
        tools:text="Bicycling - light (10-11.9 mph)"/>

    <TextView
        android:id="@+id/list_item_step_conversion_tv_description"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="0dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="4dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="@android:color/black"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintLeft_toLeftOf="@+id/list_item_step_conversion_tv_title"
        app:layout_constraintRight_toLeftOf="@+id/list_item_step_conversion_iv_favorite"
        app:layout_constraintTop_toBottomOf="@+id/list_item_step_conversion_tv_title"
        tools:text="182 steps/minute"/>

    <ImageView
        android:id="@+id/list_item_step_conversion_iv_favorite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="0dp"
        android:layout_marginRight="24dp"
        android:layout_marginTop="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/list_item_step_conversion_tv_header"
        app:srcCompat="@drawable/ic_not_liked"/>    
</android.support.constraint.ConstraintLayout>

UPDATE 2

After additional observations, this problem occurs only after a call to notifyDataSetChanged in RecyclerView.Adapter. Here is a screenshot of layout state before and after the click to the favorite icon to one of the items. enter image description here

Screenshots explanation:

  • On the left side, ViewHolder with visible header view is on position: 2. Items above are displayed correctly.
  • After click on favorite icon (item with value 242), ViewHolder on position: 1 is the one with visible header view, while the ViewHolder on position: 2 have header view set as GONE. I was expecting for ViewHolder height to decrease and have the same height as ViewHolder on position: 0.

Having in mind that this ViewHolder had header set to VISIBLE in previous state, it might have something with recycling, not sure.

Damaraland answered 28/4, 2017 at 12:43 Comment(6)
Also post your xml.Latimer
@Latimer Sure, added.Damaraland
Hm. The bottom item looks like the two TextViews were using the spread chain style instead of the packed one. Too late in the day for me to look into this. Hopefully I can have a look sat it tomorrow. This is based on a device and looks there the same (apart from the red marker for the highlighted view, of course)? And you're using ConstraintLayout 1.0.2?Cauda
Hey Wolfram, tnx for replying. I have updated the question with additional screenshots and I think that packed chain is still used, but the height of recycled viewholder is not decreased and actual space between items is larger than anticipated. 1.0.2 version of course.Damaraland
@Damaraland I think what's happening here is that the element is correctly marked as gone, so the other two elements -- as they are in a chains -- are repositioned to take advantage of the space, so spread out more (see your original screenshot). Basically, it acts as if the wrap_content of the layout wasn't respected, or called. I suspect some shenanigan due to recycling the view.Longspur
@NicolasRoard Yes you're right, wrap_content is not respected in case of the chain in viewholder after recycle. I have excluded chain and tested and it behaves correctly. Honestly, I didn't notice that original screenshot and the last one are different (don't know why..). Wolfram was right when he said that first screenshot looks like chain: spread. The second one is definitely packed. At this point, I'm not sure if behavior above is ok, it still makes sense to me (resize on GONE, apply chain). If you think that this is a bug, I can create one for you, or to close the question :). TnxDamaraland
P
2

I've solved this issue by calling view.requestLayout()* in the end of my adapter's #getView, after the usual call to binding.executePendingBindings().

*I'm using an old-school Adapter right now, so you should search for the RecyclerView.Adapter's equivalent, probably #onBindViewHolder()

Protrusile answered 27/6, 2017 at 15:33 Comment(0)
L
0

It seems like the layout is actually correct -- disregard the frame of the gone object in the inspector, when a widget is marked as gone we don't lay it out again, as it will simply be skipped (in Studio we do, to provide an easier way to see what's going on, but there's no point doing that on the real device).

Which version of ConstraintLayout are you using? I seem to get the correct behavior here:

enter image description here

After marking the first element to gone:

enter image description here

Longspur answered 28/4, 2017 at 19:29 Comment(1)
Thank you Nicolas for your response. I have updated the question a little bit with additional explanation. Layout behavior is correct when RecyclerView is displayed for the first time. It looks like there is an issue when ViewHolder with visible header is recycled. I hope I'm not missing something obvious here. Please take a look UPDATE 2 section above. I am using 1.0.2 version of course.Damaraland
S
-1

What worked for me was set the inital visibilty of an object to "gone" and change its visibity via code.

Some like this:

My xml:

<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/tvMyobject"
    style="@style/myStyle"
    tools:text="@string/dummy_text"
    android:textAlignment="center"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/anyotherobject"
    android:visibility="gone"
/>

My activity:

if (isSomethingToShow) {
    tvMyobject.setVisibility(View.VISIBLE);
    tvMyobject.setText(R.string.my_string_to_show);
}
Sapid answered 12/5, 2020 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.