Search code examples
androidmarginhorizontalscrollviewandroid-chips

Why ChipGroup inside HorizontalScrollView with Chip's marginEnd not working?


I have Chips inside ChipGroup and this ChipGroup inside HorizontalScrollView. But my problem is marginEnd on last Chip not working!

I'm tried change layout params programmatically, but not successfull

<HorizontalScrollView
    android:id="@+id/chips_container"
    android:layout_width="0dp"
    android:layout_height="56dp"
    android:layout_gravity="center_vertical"
    android:visibility="gone"
    app:layout_constraintBottom_toTopOf="@+id/dividerSmartReplies"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    tools:visibility="visible">

    <com.google.android.material.chip.ChipGroup
        android:id="@+id/chip_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        app:chipSpacing="8dp"
        app:singleLine="true"
        app:singleSelection="true">

        <com.google.android.material.chip.Chip
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="wrap_content"
            android:layout_height="42dp"
            android:layout_marginStart="8dp"
            android:textColor="@color/selector_smart_reply_chip_text"
            app:checkedIconVisible="false"
            app:chipBackgroundColor="@color/selector_smart_reply_chip"
            app:chipCornerRadius="21dp"
            app:chipStrokeColor="@color/app_base_blue"
            app:chipStrokeWidth="1dp" />

        <com.google.android.material.chip.Chip
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="wrap_content"
            android:layout_height="42dp"
            android:textColor="@color/selector_smart_reply_chip_text"
            app:checkedIconVisible="false"
            app:chipBackgroundColor="@color/selector_smart_reply_chip"
            app:chipCornerRadius="21dp"
            app:chipStrokeColor="@color/app_base_blue"
            app:chipStrokeWidth="1dp" />

        <com.google.android.material.chip.Chip
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="wrap_content"
            android:layout_height="42dp"
            android:textColor="@color/selector_smart_reply_chip_text"
            app:checkedIconVisible="false"
            app:chipBackgroundColor="@color/selector_smart_reply_chip"
            app:chipCornerRadius="21dp"
            app:chipStrokeColor="@color/app_base_blue"
            app:chipStrokeWidth="1dp" />

        <com.google.android.material.chip.Chip
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="wrap_content"
            android:layout_height="42dp"
            android:layout_marginEnd="8dp"
            android:textColor="@color/selector_smart_reply_chip_text"
            app:checkedIconVisible="false"
            app:chipBackgroundColor="@color/selector_smart_reply_chip"
            app:chipCornerRadius="21dp"
            app:chipStrokeColor="@color/app_base_blue"
            app:chipStrokeWidth="1dp" />
    </com.google.android.material.chip.ChipGroup>

</HorizontalScrollView>

I need extra space after last Chip. ChipGroup's padding worked but it does not expected UI result.


Solution

  • I figured out this issue! ChipGroup extends FlowLayout. FlowLayout, in onMeasure() method, does't take into account marginRight of last child to finalWidth.
    My final solution:

    class SmartRepliesGroup @JvmOverloads constructor(
        context: Context,
        attributeSet: AttributeSet? = null,
        defStyle: Int = 0
    ) : ChipGroup(context, attributeSet, defStyle) {
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            val width = MeasureSpec.getSize(widthMeasureSpec)
            val widthMode = MeasureSpec.getMode(widthMeasureSpec)
            val height = MeasureSpec.getSize(heightMeasureSpec)
            val heightMode = MeasureSpec.getMode(heightMeasureSpec)
            val maxWidth =
                if (widthMode != -2147483648 && widthMode != 1073741824) 2147483647 else width
            var childLeft = this.paddingLeft
            var childTop = this.paddingTop
            var childBottom = childTop
            var maxChildRight = 0
            val maxRight = maxWidth - this.paddingRight
    
            var finalWidth = 0
            var lastChildMarginRight = 0
            while (finalWidth < this.childCount) {
                val child = this.getChildAt(finalWidth)
                if (child.visibility != 8) {
                    this.measureChild(child, widthMeasureSpec, heightMeasureSpec)
                    val lp = child.layoutParams
                    var leftMargin = 0
                    var rightMargin = 0
                    if (lp is MarginLayoutParams) {
                        leftMargin += lp.leftMargin
                        rightMargin += lp.rightMargin
                        lastChildMarginRight = rightMargin
                    }
    
                    var childRight = childLeft + leftMargin + child.measuredWidth
                    if (childRight > maxRight && !this.isSingleLine) {
                        childLeft = this.paddingLeft
                        childTop = childBottom + this.lineSpacing
                    }
    
                    childRight = childLeft + leftMargin + child.measuredWidth
                    childBottom = childTop + child.measuredHeight
                    if (childRight > maxChildRight) {
                        maxChildRight = childRight
                    }
    
                    childLeft += leftMargin + rightMargin + child.measuredWidth + this.itemSpacing
                }
                ++finalWidth
            }
    
            finalWidth =
                getMeasuredDimension(width, widthMode, maxChildRight + lastChildMarginRight)
            val finalHeight = getMeasuredDimension(height, heightMode, childBottom)
            this.setMeasuredDimension(finalWidth, finalHeight)
        }
    
        private fun getMeasuredDimension(size: Int, mode: Int, childrenEdge: Int): Int =
            when (mode) {
                -2147483648 -> Math.min(childrenEdge, size)
                1073741824 -> size
                else -> childrenEdge
            }
    }