Create grid (n × n) in Android ConstraintLayout with variable number of n
Asked Answered
D

4

5

I want to create a square grid inside ConstraintLayout. My first thought was to create a horizontal chain, give some margin value and set to all single view size attributes width = match_constraint, height = match_constraint and set the ratio to 1:1. It works and it looks like:

enter image description here

And it's easy when a size of the grid is 2×2 - there are 4 elements so it's easy. But what I should do when I had to create a grid 7×7? We have 49 views so setting all of these views could be tricky. I want to do this in constraint layout because I want to have a flexible layout. :)

Dewees answered 19/7, 2018 at 19:4 Comment(5)
have you considered using a RecyclerView with a span count of 7?Infanticide
hmm actually not :) but it's some idea is true. But I'm not sure that it's the best way to handle this problemDewees
it depends on your needs but I doubt you will fit 49 square views on a single screen at the same timeInfanticide
thats why a use a "match_constraint" with ratio 1:1 to create a sqare. 7x7 grid is also for example in 2048 gameDewees
Like @Infanticide said, having 49 views on screen is a bad idea. It's a lot of measuring, layout etc. If you're going to display content like images in the squares consider RecyclerView with a GridLayoutManager. If you need to create a game board(e.g. a chess board) it's a better solution to have a single custom view and override it's onDraw() and draw some squares yourself. Both solutions would be much faster than having 49 views on the screen at once.Interlocution
W
7

Since you say that you have a variable number of squares, I assume that you are willing to create the n*n grid in code. Here is an approach to creating the grid. This is just one way and there are probably others.

First, create a layout with ConstraintLayout as the root view. In that layout, define a widget that has width and height of match_constraints and is constrained by the parent. This will give you a square widget regardless of the device orientation. (I use a View here so it can be seen, but it is better to use a Space widget although it probably doesn't really matter.)

enter image description here

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout 
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/gridFrame"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="16dp"
        android:background="@android:color/holo_blue_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Here is the code for the activity that creates a 7*7 grid. We will use the on-screen view from the layout as the "parent" view to contain the squares.

enter image description here

MainActivity.java

public class MainActivity extends AppCompatActivity {
    int mRows = 7;
    int mCols = 7;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ConstraintLayout layout = findViewById(R.id.layout);

        int color1 = getResources().getColor(android.R.color.holo_red_light);
        int color2 = getResources().getColor(android.R.color.holo_blue_light);
        TextView textView;
        ConstraintLayout.LayoutParams lp;
        int id;
        int idArray[][] = new int[mRows][mCols];
        ConstraintSet cs = new ConstraintSet();

        // Add our views to the ConstraintLayout.
        for (int iRow = 0; iRow < mRows; iRow++) {
            for (int iCol = 0; iCol < mCols; iCol++) {
                textView = new TextView(this);
                lp = new ConstraintLayout.LayoutParams(ConstraintSet.MATCH_CONSTRAINT,
                                                       ConstraintSet.MATCH_CONSTRAINT);
                id = View.generateViewId();
                idArray[iRow][iCol] = id;
                textView.setId(id);
                textView.setText(String.valueOf(id));
                textView.setGravity(Gravity.CENTER);
                textView.setBackgroundColor(((iRow + iCol) % 2 == 0) ? color1 : color2);
                layout.addView(textView, lp);
            }
        }

        // Create horizontal chain for each row and set the 1:1 dimensions.
        // but first make sure the layout frame has the right ratio set.
        cs.clone(layout);
        cs.setDimensionRatio(R.id.gridFrame, mCols + ":" + mRows);
        for (int iRow = 0; iRow < mRows; iRow++) {
            for (int iCol = 0; iCol < mCols; iCol++) {
                id = idArray[iRow][iCol];
                cs.setDimensionRatio(id, "1:1");
                if (iRow == 0) {
                    // Connect the top row to the top of the frame.
                    cs.connect(id, ConstraintSet.TOP, R.id.gridFrame, ConstraintSet.TOP);
                } else {
                    // Connect top to bottom of row above.
                    cs.connect(id, ConstraintSet.TOP, idArray[iRow - 1][0], ConstraintSet.BOTTOM);
                }
            }
            // Create a horiontal chain that will determine the dimensions of our squares.
            // Could also be createHorizontalChainRtl() with START/END.
            cs.createHorizontalChain(R.id.gridFrame, ConstraintSet.LEFT,
                                     R.id.gridFrame, ConstraintSet.RIGHT,
                                     idArray[iRow], null, ConstraintSet.CHAIN_PACKED);
        }

        cs.applyTo(layout);
    }
}

Just change mRows and mCols and the grid will adjust itself. If your grid will always be square, you will not need to set the ratio of the grid container in the code. You can also place your grid within a more complicated layout. Just make sure that the grid container has the right dimensions and you are good to go.

Waziristan answered 20/7, 2018 at 14:32 Comment(7)
It's ver good answer. I jus looking for something like this. Thank you so much! ;)Dewees
Now i saw that is work but there is a problem with performance. My app lag's and i have in logs: I/Choreographer: Skipped 133 frames! The application may be doing too much work on its main thread. I/Choreographer: Skipped 65 frames! The application may be doing too much work on its main thread. When i unccoment this everythink is correctDewees
@Dewees That is a lot of work to do on the main thread. If you are creating the matrix over and over, it may be a problem. If you are doing it once in a while, it shouldn't be a problem. Is this on an emulator? An emulator would be more sensitive than an actual device. I take that you aren't getting an ANR error, so that would also be OK if this is something that is occasionally done. If performance really is an issue (laggy and noticeable to users) and more then just a logcat complaint, you may want to consider ways to speed things up.Waziristan
no is on regular device ( Nexus 5) and I create a matrix once. But yes this peraformance is relly issue (laggy). What do you mean about speed things up? Or do you think create this on other thread could be a good idea? (as post action)Dewees
@Dewees Try to get an idea about where the time is being spent. There are some performance measurement tools in Studio that should be able to help. I don't have much experience with these, so I can't help there. You could try moving some work off the main thread, but you have to use the main thread for UI manipulation. You could also consider creating building blocks of 2,3...7... rows in XML and use code to add and interlink these. Just some ideas, but measurement will be the key; otherwise, you may just spin your wheels.Waziristan
I'll try to do it only in xml. Just put it manually (yes it wasn't nice job), and I also have some lags and skipped frames. So i think that constraintlayout is not god layout to implement grid :(Dewees
Awesome solution. Thank you @Waziristan :) It helped me really a lot. (note: in case somebody is looking how to do this non-programmatically see below: https://mcmap.net/q/2002257/-create-grid-n-215-n-in-android-constraintlayout-with-variable-number-of-n)Scanties
C
0

Best idea is to create two views linear layouts, one that has horizontalAlignment and Another that has vertical alignment. Group with vertical alignment is one that you call in your layout and pass to it as an attribute a number(7). This group will add horizontal group 7 times to itself. Each horizontal layout will in-turn take a number (7) again. And that will add 7 squares. Trick is to see that each square will have same weight. And each horizontal row will have same weight. That way u will get grids of right size provides you insert Verical layout in square ViewGroup

Chrysotile answered 20/7, 2018 at 4:4 Comment(0)
E
0

If I got it right I think the best way is to use the Flow widget

androidx.constraintlayout.helper.widget.Flow

and put the id of all views which should be included in the grid in the following field:

app:constraint_referenced_ids

more info can be found here:

https://bignerdranch.com/blog/constraintlayout-flow-simple-grid-building-without-nested-layouts/

Epicurean answered 17/6, 2021 at 5:42 Comment(0)
S
0

I liked @cheticamp's answer so much, that I was curious how this would look like in xml, in case you don't want to do it programmatically, because this might make it easier to adjust and see a preview if you have mostly static content.

Hence I tried it out and here I would like to share the code with you. Note, that for the sake of simplicity I will only show a 3x3 grid...

<?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:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".ui.grid">

    <Space
        android:id="@+id/grid_frame"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.5" />

    <!-- ROW 0 -->

    <TextView
        android:id="@+id/tile_00"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#F44336"
        android:gravity="center"
        android:text="1"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_01"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@id/grid_frame"
        app:layout_constraintTop_toTopOf="@id/grid_frame" />

    <TextView
        android:id="@+id/tile_01"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#03A9F4"
        android:gravity="center"
        android:text="2"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_02"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_00"
        app:layout_constraintTop_toTopOf="@id/grid_frame" />

    <TextView
        android:id="@+id/tile_02"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#F44336"
        android:gravity="center"
        android:text="3"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="@id/grid_frame"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_01"
        app:layout_constraintTop_toTopOf="@id/grid_frame" />

    <!-- ROW 1 -->

    <TextView
        android:id="@+id/tile_10"
        layout="@layout/fragment_tile"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#03A9F4"
        android:gravity="center"
        android:text="4"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_11"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintStart_toStartOf="@id/grid_frame"
        app:layout_constraintTop_toBottomOf="@id/tile_00" />

    <TextView
        android:id="@+id/tile_11"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#F44336"
        android:gravity="center"
        android:text="5"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_12"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_10"
        app:layout_constraintTop_toBottomOf="@id/tile_01" />

    <TextView
        android:id="@+id/tile_12"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#03A9F4"
        android:gravity="center"
        android:text="6"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="@id/grid_frame"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_11"
        app:layout_constraintTop_toBottomOf="@id/tile_02" />

    <!-- ROW 2 -->

    <TextView
        android:id="@+id/tile_20"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#F44336"
        android:gravity="center"
        android:text="7"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_21"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintStart_toStartOf="@id/grid_frame"
        app:layout_constraintTop_toBottomOf="@id/tile_10" />

    <TextView
        android:id="@+id/tile_21"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#03A9F4"
        android:gravity="center"
        android:text="8"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/tile_22"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_20"
        app:layout_constraintTop_toBottomOf="@id/tile_11" />

    <TextView
        android:id="@+id/tile_22"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#F44336"
        android:gravity="center"
        android:text="9"
        android:textSize="24sp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="@id/grid_frame"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@id/tile_21"
        app:layout_constraintTop_toBottomOf="@id/tile_12" />

</androidx.constraintlayout.widget.ConstraintLayout>

... resulting in...

3x3 grid

Notes:

  • you can nicely see the constraints as tiny little arrows in the picture
  • the horizontal chain can also be created nicely in Android Studio by selecting all tile in a row and using the context menu -> chains -> create horizontal chain resulting in exactly the constraints used
Scanties answered 27/8, 2024 at 8:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.