4

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.

Zain
  • 37,492
  • 7
  • 60
  • 84
Alireza Noorali
  • 3,129
  • 2
  • 33
  • 80

4 Answers4

5

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

MariosP
  • 8,300
  • 1
  • 9
  • 30
1

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

Payam Monsef
  • 296
  • 3
  • 6
1

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

Zain
  • 37,492
  • 7
  • 60
  • 84
  • It doesn't have an acceptable behavior if you delete `button11` and `button12`. Can you make it better, please? – Alireza Noorali Jul 18 '21 at 07:06
  • @AlirezaNoorali Just to make sure, you always have no more than 10 buttons in the flow and want to get rid of `button11` and `button12`? – Zain Jul 18 '21 at 07:19
  • As I said in my question post, the number of views depends on the API response, so views can be much more than 10. – Alireza Noorali Jul 18 '21 at 08:14
  • @AlirezaNoorali I updated the answer with your requirement. Please have a look at the `UPDATE` section – Zain Jul 18 '21 at 12:45
0

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

Hongyuan
  • 100
  • 6