Alireza Noorali
Alireza Noorali

Reputation: 3265

Arranging ConstraintLayout Flow References From Bottom To Top And Right To Left

I need to fill ConstraintLayout Flow view from Bottom To Top And Right To Left. This is my code:

<androidx.constraintlayout.helper.widget.Flow
    android:id="@+id/flow"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:orientation="vertical"
    app:constraint_referenced_ids="tv1,tv2,tv3,tv4,tv5,tv6,tv7,tv8,tv9,tv10"
    app:flow_horizontalGap="8dp"
    app:flow_verticalGap="8dp"
    app:flow_verticalStyle="packed"
    app:flow_wrapMode="chain"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHeight_percent=".5"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

And this is the result:

enter image description here

(In fact I'm going to set references dynamically due to the API response, so assume that the views are not in XML layout)

But I need the below one:

enter image description here

Any help is appreciated.

Upvotes: 4

Views: 2350

Answers (4)

Zain
Zain

Reputation: 40878

You can keep the normal order of the Ids as-is in layout, and create a custom Flow class that rearranges the Ids to the customized order you want (Bottom to Top, Right to Left):

class CustomFlow(context: Context, attrs: AttributeSet?) : Flow(context, attrs) {

    init {
        val newIds = IntArray(referencedIds.size)
        Log.d("LOG_TAG", "init BEFORE: ${Arrays.toString(referencedIds)}")
        for ((i, item) in (referencedIds).withIndex())
            newIds[referencedIds.size - i - 1] = item

        referencedIds = newIds
        Log.d("LOG_TAG", "init AFTER: ${Arrays.toString(referencedIds)}")
    }

}

And use CustomFlow in your layout.

Demo layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <com.example.android.constraintlayoutflow.CustomFlow
        android:id="@+id/flow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10, button11, button12"
        app:flow_horizontalGap="8dp"
        app:flow_maxElementsWrap="4"
        app:flow_verticalGap="8dp"
        app:flow_verticalStyle="packed"
        app:flow_wrapMode="chain"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="1"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="2"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="3"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button4"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="4"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button5"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="5"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button6"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="6"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button7"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="7"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button8"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="8"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button9"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="9"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button10"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="10"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button11"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button12"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:textSize="18sp"
        tools:ignore="MissingConstraints" />


</androidx.constraintlayout.widget.ConstraintLayout>

Preview:

enter image description here

UPDATE

It doesn't have an acceptable behavior if you delete button11 and button12. Can you make it better, please?

So, you want to keep it with the same behavior by removing button11 and button12.

I.e. you don't need to have extra buttons or blank fillers of the Flow (this can be a workaround by setting their visibility to INVISIBLE).

But here, I will get rid of them with below changes:

  • Modify the custom Flow to handle whether the column is a full-sized one or not.
  • Reverse the layout with android:rotationY="180" to simulate RTL direction, and do a reverse back for each individual element to recover the issue of the reversed text liked described in this answer.
  • Assign app:flow_verticalBias="1" in order to bias the elements starting from the bottom.

Demo:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    android:rotationY="180">


    <com.example.android.constraintlayoutflow.CustomFlow
        android:id="@+id/flow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10"
        app:flow_horizontalGap="8dp"
        app:flow_verticalBias="1"
        app:flow_verticalGap="8dp"
        app:flow_verticalStyle="packed"
        app:flow_wrapMode="chain"
        tools:flow_maxElementsWrap="4"
        tools:ignore="MissingConstraints" />

    <FrameLayout
        android:id="@+id/button1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="1"
            android:textSize="18sp" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="2"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="3"
            android:textSize="18sp" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/button4"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="4"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button5"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="5"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button6"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="6"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button7"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="7"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button8"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="8"
            android:textSize="18sp" />
    </FrameLayout>


    <FrameLayout
        android:id="@+id/button9"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="9"
            android:textSize="18sp" />
    </FrameLayout>

    <FrameLayout
        android:id="@+id/button10"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:rotationY="180"
        tools:ignore="MissingConstraints">

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:text="10"
            android:textSize="18sp" />
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Custom Flow:

class CustomFlow(context: Context, attrs: AttributeSet?) : Flow(context, attrs) {

    init {
        val size = referencedIds.size
        val newIds = IntArray(size)
        Log.d("LOG_TAG", "init BEFORE: ${Arrays.toString(referencedIds)}")

        val COL_SIZE = 4
        setMaxElementsWrap(COL_SIZE)
        val extraItemCount = size % COL_SIZE
        val fullSize = extraItemCount == 0
        val colCount = if (fullSize) size / COL_SIZE else size / COL_SIZE + 1

        var counter = 1
        var baseIndex = -1
        for ((i, item) in (referencedIds).withIndex()) {
            val col: Int = i / COL_SIZE + 1

            var newI: Int

            if (fullSize || col < colCount) {
                newI = col * COL_SIZE - i - 1 + (col - 1) * COL_SIZE

            } else {
                if (baseIndex == -1) baseIndex = i
                newI = baseIndex + extraItemCount - counter
                counter++
            }
            newIds[newI] = item

        }

        referencedIds = newIds
        Log.d("LOG_TAG", "init AFTER: ${Arrays.toString(referencedIds)}")
    }


}

Result:

enter image description here

Upvotes: 1

MariosP
MariosP

Reputation: 9113

To arrange items from Bottom-to-Top and Right-to-Left you can use the FlexboxLayout Google Library. All you need is to use the below attributes in FlexboxLayout:

  1. app:flexDirection="column_reverse" This will draw each item from Bottom to Top.

  2. app:flexWrap="wrap_reverse" This will draw each item from Right to Left.

  3. app:justifyContent="flex_start" This will control the alignment of each item and we use flex_start to align it from bottom.

Xml sample is like below:

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

    <com.google.android.flexbox.FlexboxLayout
        android:id="@+id/flexLayout"
        android:layout_width="wrap_content"
        android:layout_height="300dp"
        app:flexDirection="column_reverse"
        app:flexWrap="wrap_reverse"
        app:justifyContent="flex_start"
        android:background="@android:color/white">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="2"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv3"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="3"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv4"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="4"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv5"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="5"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv6"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="6"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv7"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="7"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv8"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="8"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv9"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="9"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv10"
            android:layout_width="120dp"
            android:layout_height="65dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:layout_margin="5dp"
            android:text="10"
            android:textSize="18sp" />

    </com.google.android.flexbox.FlexboxLayout>

</HorizontalScrollView>

or Programmatically:

val flexLayout: FlexboxLayout = findViewById<FlexboxLayout>(R.id.flexLayout)
val itemWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120f, resources.displayMetrics).toInt()
val itemHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 65f, resources.displayMetrics).toInt()
val itemMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt()

for (i in 0..9) {
    val tv = TextView(this)
    tv.text = (i + 1).toString()
    tv.setBackgroundColor(ContextCompat.getColor(this, android.R.color.holo_orange_dark))
    tv.gravity = Gravity.CENTER
    val params = FlexboxLayout.LayoutParams(itemWidth, itemHeight)
    params.setMargins(itemMargin, itemMargin, itemMargin, itemMargin)
    tv.layoutParams = params
    flexLayout.addView(tv)
}

Result:

flexbox_layout

Upvotes: 5

Payam Monsef
Payam Monsef

Reputation: 336

I don't have experience with Flow but as i know, you wanna handle this dynamicly, so if you be able to handle the order of items from server, this will help you a lot.

It can be useful for any item size(not just 10) as long as you want to show them in 4 rows.

Im going to use GridLayoutManager with spanSizeLookup to handle this.

val items = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val layoutManager = GridLayoutManager(context, 4, GridLayoutManager.HORIZONTAL, false)

val targetPosition = (items.size / 4) * 4
val spanSizeLookup: GridLayoutManager.SpanSizeLookup by lazy {
            object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (position == targetPosition) (items.size % 4) + 1 else 1
                }
            }
        }

layoutManager.spanSizeLookup = spanSizeLookup
recyclerView.layoutManager = layoutManager
adapter.setData(items)
recyclerView.adapter = adapter

This will cause the target item to fill the empty height of recycler view in order to achieve what you want.

The last step you need to do is wrap your root of existing xml item in a FrameLayout and give that root , bottom layout gravity like this:

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_margin="6dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        />
</FrameLayout>

I know its not completely what you want, but i hope you can manage to sort your array and use this method to create this dynamically , like the picture above

Best regards

Upvotes: 1

Hongyuan
Hongyuan

Reputation: 102

You don't really need to set constraint_referenced_ids in numerical order, but your layout order. And you can use invisible View/Space as space placeholder.

So I edit following code for your reference:

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

    <TextView
        android:id="@+id/space1"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:text=""
        android:visibility="invisible" />

    <TextView
        android:id="@+id/A"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="8" />

    <TextView
        android:id="@+id/B"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="4" />

    <TextView
        android:id="@+id/space2"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text=""
        android:visibility="invisible" />

    <TextView
        android:id="@+id/C"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="7" />

    <TextView
        android:id="@+id/D"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:text="3" />

    <TextView
        android:id="@+id/E"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:text="10" />

    <TextView
        android:id="@+id/F"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="6" />

    <TextView
        android:id="@+id/G"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="2" />

    <TextView
        android:id="@+id/H"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="9" />

    <TextView
        android:id="@+id/I"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:text="5" />

    <TextView
        android:id="@+id/J"
        style="@style/text_style"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:text="1" />

    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="space1,A,B,space2,C,D,E,F,G,H,I,J"
        app:flow_horizontalGap="8dp"
        app:flow_horizontalStyle="packed"
        app:flow_maxElementsWrap="3"
        app:flow_verticalGap="8dp"
        app:flow_verticalStyle="packed"
        app:flow_wrapMode="aligned"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Preview:

enter image description here

Upvotes: 0

Related Questions