Create grid (n × n) in Android ConstraintLayout with variable number of n - android

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:
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. :)

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.)
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.
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.

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

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/

Related

Sort views in layout according to number of views

I am struggling at one thing. Let's say I have linear layout with set height and width is match_parent. I will have a set number of views 1 to 6 and I don't know at runtime how much I will receive from server. The problem is, how can I sort them in a layout so they scale their width accordingly to number of views present ? If there are more than 3 views I need to put them in two lines.
I was thinking about using layout weight, but can't think about solution that can put them to two lines. Any ideas ?
You have to dynamically add views to linear layout.
First create container layout in xml.
<LinearLayout
android:id="#+id/containerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
Then check,
if(list.size()<=3)
{
Then assign weight to container. i.e weight=list.size
for(int i=0;i<size;i++)
{
TextView textview = new TextView(this);
textview.setText(brandName);
textview.setWeight(1f);
container.addView(textview);
}
}
else
{
int totalRows= (list.size/3)+(list.size%3);
int count=0;
for(int i=0;i<totalRows;i++)
{
LinearLayout newLL = new LinearLayout(mContext);
newLL.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
newLL.setOrientation(LinearLayout.HORIZONTAL);
for(int j=count;j<count+3;j++)
{
count++;
TextView textview = new TextView(this);
textview.setText(brandName);
newLL.addView(textview);
}
container.addView(newLL);
}
}
You have to do something like this.This is not actual code.
You should use listview in your rooted LinearLayout and design single list item in an other xml layout , create a custom adapter for your single list item and set the adapter to your list view . this answer will help you how to create custome adapter

"Inteligent" resize on layout?

I'm having an issue working with layouts, I've a linear layout (could be a relative layout or a table layout) which will contain an undefined number of buttons when the activity is loaded. This means, the quantity of buttons will be determined when the activity is being created. The thing is, I'm trying to fit them all in one line (with a center gravity) without changing each buttons' width UNTIL one of them reaches the margin of the screen. In other words, I want the buttons JUST to resize when at least one of them reaches the margin of the screen. That is because, I can't determine the space they're going to use because they are not created.
My actual linear layout:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:id="#+id/linearLayout_1"
android:layout_above="#+id/linearLayout_2"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="30dp"
android:orientation="horizontal"
android:gravity="center"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true">
</LinearLayout>
Piece of code that creates the buttons:
protected void hacerVisiblesRespuesta(){
ViewGroup linearLayout = (ViewGroup) findViewById(R.id.linearLayout);
assert linearLayout != null;
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,40, getResources().getDisplayMetrics());
for(int i = 0; i < longuitudPalabra; i++){
String boton = "btn_rsp" + Integer.toString(i+1);
Button bt = new Button(this);
bt.setText("");
bt.setId(getResourceId(boton,"id",getPackageName()));
bt.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT
, height
, 1.0f));
bt.setTextColor(Color.BLACK);
bt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
clickBotonRespuesta(v);
}
});
bt.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bt.setBackground(getDrawable(R.drawable.bgbtnrsp));
}else{
//bt.setBackgroundDrawable(getDrawable(R.drawable.bgbtnrsp));
}
bt.setTextSize(20);
Typeface typeFace= Typeface.createFromAsset(getAssets(),"fonts/Montserrat-Regular.ttf");
bt.setTypeface(typeFace);
linearLayout.addView(bt);
}
}
I've tried many things, one of them was to make the buttons' width variable with weight property. The thing is if there are a small quantity of buttons, lets say 4, their width ended up enormous. Is there any way to achieve this through code? Thanks.
have you tried this?
button.setLayoutParams (new LayoutParams(50, LayoutParams.WRAP_CONTENT)

Decreasing row/column count of gridlayout in android

I need a dynamic gridlayout that can be toggled between 3 by 3 and 4 by 4. I can setRowCount and setColumnCount from 3 to 4 but not from 4 to 3. It will display following issue:
Caused by: java.lang.IllegalArgumentException: rowCount must be
greater than or equal to the maximum of all grid indices (and spans)
defined in the LayoutParams of each child.
Is there any work around to achieve this using gridlayout?
I realize this question is quite old, but for people who are still encountering this exception today, I'll offer an explanation that may shed some light upon how downsizing a GridLayout works and why I believe it is/was throwing an exception for the OP.
In Short:
Child views of the GridLayout can, after downsizing, occupy cells that are not within the GridLayout's grid, which is causing the IllegalArgumentException mentioned by the OP. To avoid this, remove child views that will occupy cells outside of the GridLayout's grid before actually calling setRowCount() or setColumnCount(). This can be done via GridLayout.removeView(aboutToBeIllegalChild); or by wiping the entire layout using GridLayout.removeAllViews();.
In Long:
All that calling GridLayout.setRowCount() does, is specify a new number of rows that the layout should contain. It does not, however, mess with the child views that the GridLayout currently contains, nor it's specified Spec (what column(s) and row(s) the child view occupies).
What the exception is basically telling us, and the docs confirm, is that a GridLayout does not allow any of its child views to occupy cells that are outside of the GridLayouts grid. As an example, the layout will not allow a child view to occupy cell (5, 1) when the grid is only 4 x 1.
This leads us to why the original poster was successful at dynamically increasing the GridLayout's dimensions, while being unsuccessful at decreasing it. When enlarging the dimensions, any child views that were already attached to the GridLayout with specified cells, would still be placed in legal cells if the grid received extra rows or columns dynamically. When reducing the dimensions of the grid, child views that were placed in cells that would disappear as a consequence of removing rows or columns, would now be considered illegal.
To work around this, you must either remove those (about to be) illegal child views from its parent GridLayout beforehand by calling GridLayout.removeView(aboutToBeIllegalChild); or simply wipe the entire GridLayout by calling GridLayout.removeAllViews();.
Hope this helps!
Based on Teun Kooijman answer you can just change Spec in GridLayout.LayoutParams and keep all Views inside the GridLayout:
private void changeColumnCount(int columnCount) {
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
//new GridLayout.LayoutParams created with Spec.UNSPECIFIED
//which are package visible
view.setLayoutParams(new GridLayout.LayoutParams());
}
gridLayout.setColumnCount(columnCount);
}
}
You can also change Spec in other way by accessing GridLayout.LayoutParams.rowSpec and GridLayout.LayoutParams.columnSpec
For me, the issue was to change the number of columns of the GridView when the app changes the orientation. I achieved it by putting the bellow code in public void onConfigurationChanged(Configuration newConfig).
if (mGridLayout.getColumnCount() != getResources().getInteger(R.integer.nav_columns)) {
final int viewsCount = mGridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = mGridLayout.getChildAt(i);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
int colIndex = i%getResources().getInteger(R.integer.nav_columns);
int rowIndex = i/getResources().getInteger(R.integer.nav_columns);
layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
layoutParams.width = 0;
layoutParams.columnSpec = GridLayout.spec(colIndex,1,GridLayout.FILL,1f);
layoutParams.rowSpec = GridLayout.spec(rowIndex);
view.setLayoutParams(layoutParams);
}
mGridLayout.setColumnCount(getResources().getInteger(R.integer.nav_columns));
}
The layout parameter values may need change depending on your need.
According to #Hensin 's answer, I have modify his codes for show how to copy the previous grid items layout parameters as following:
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
GridLayout.LayoutParams oldParams = (GridLayout.LayoutParams) view.getLayoutParams();
GridLayout.LayoutParams newParams = new GridLayout.LayoutParams();
newParams.width = oldParams.width;
newParams.height = oldParams.height;
newParams.setMargins(oldParams.leftMargin, oldParams.topMargin, oldParams.rightMargin, oldParams.bottomMargin);
view.setLayoutParams(newParams);
}
gridLayout.setColumnCount(columnCount);
}
You can now re order your items with almost the same layout parameters

Dynamically add view in vertical and horizontal way

I'm trying to create Buttons in LinearLayout dynamically, and I want to add those in vertical and horizontal way.
At first, add a button A in the layout, and if there's enough space between button A and screen edge, add button B to the right of button A (horizontally). Otherwise, add button B below button A (vertically).
My current layout :
<LinearLayout
android:id="#+id/btn_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</LinearLayout>
in class :
LinearLayout btnLayout = (LinearLayout) findViewById(R.id.btn_layout);
btnLayout.removeAllViewsInLayout();
for(Tag tag : tagList.getChildTags()) {
Button button = new Button(this);
button.setId(tag.getId());
button.setText(tag.getName());
btnLayout.addView(button);
}
In this case, if I set orientation as horizontal, then some of buttons are not showing (cut-off by screen), and if I set as vertical, it looks pretty bad.
Is there any way to solve this problem? Thanks everyone in advance!
You can achieve this but not in a trivial way. I'll explain how I do something similar (in my case, I add TextViews) to TableRows, if they fit.
With this approach you'll have to use a TableLayout and add TableRows to it with your Buttons. So you might replace your "#+id/btn_layout" LinearLayout to be a TableLayout instead.
Firstly, to get the screen's width, use something like this:
final Display display = getWindowManager().getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final WindowManager.LayoutParams params = getWindow().getAttributes();
// Your screen's width will be stored within your params.width value
You'll use this to know if the current Button still fits the screen's width within the current TableRow or it has to be added to a new one. So now, use something like this to create your buttons:
int currentRowsWidth = 0;
TableLayout tl = (TableLayout) findViewById(R.id.my_table_layout);
TableRow currentRow = new TableRow();
for (Tag tag : tagList.getChildTags()) {
Button button = new Button(this);
button.setId(tag.getId());
button.setText(tag.getName());
// There's where you check whether it still fits the current `TableRow` or not
if (currentRowsWidth + button.getWidth() < params.width) {
currentRowsWidth += button.getWidth();
currentRow.addView(button);
}
else {
// It doesn't fit, add the currentRow to the table and start a new one
tl.add(currentRow);
currentRow = new TableRow();
currentRow.addView(button);
currentRowsWidth = button.getWidth();
}
}
It might happen that once you get out of the loop there are still Buttons to add in the currentView, simply test it:
if (currentRow.getChildCound() > 0)
tl.add(currentRow);
I'm writing this from head, so some things might not compile at first time, but I hope you get the idea.

how to set textview dynamically according to numbers of strings

I am getting list of phone companies from web service and i have to set it to textview but the problem is i am not getting alignment as above image.How to achieve it.
From what I understand, you want to add text views one beside the other, but when they overflow (go out of the screen) the next text view should be placed in the next line.
Doing this is not trivial. Implementing something like this (optimally and correctly) requires understanding of how android draws views (onMeasure and onLayout). However if you do not care about efficiency that much (mainly because you are going to do it only for a small portion of the view) then here is my quick hack:
mContainer = (RelativeLayout) findViewById(R.id.container);
// first layout all the text views in a relative layout without any params set.
// this will let the system draw them independent of one another and calculate the
// width of each text view for us.
for (int i = 0; i < 10; i++) {
TextView tv = new TextView(getApplicationContext());
tv.setText("Text View " + i);
tv.setId(i+1);
tv.setPadding(10, 10, 20, 10);
mContainer.addView(tv);
}
// post a runnable on the layout which will do the layout again, but this time
// using the width of the individual text views, it will place them in correct position.
mContainer.post(new Runnable() {
#Override
public void run() {
int totalWidth = mContainer.getWidth();
// loop through each text view, and set its layout params
for (int i = 0; i < 10; i++) {
View child = mContainer.getChildAt(i);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// this text view can fit in the same row so lets place it relative to the previous one.
if(child.getWidth() < totalWidth) {
if(i > 0) { // i == 0 is in correct position
params.addRule(RelativeLayout.RIGHT_OF, mContainer.getChildAt(i-1).getId());
params.addRule(RelativeLayout.ALIGN_BOTTOM, mContainer.getChildAt(i-1).getId());
}
}
else {
// place it in the next row.
totalWidth = mContainer.getWidth();
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.BELOW, mContainer.getChildAt(i-1).getId());
}
child.setLayoutParams(params);
totalWidth = totalWidth - child.getWidth();
}
mContainer.requestLayout();
}
});
Basically, I let the system do the layout and measurement for me in the first round(s) of drawing. Then using the widths of each text view now available, I reset the layout params based on the wrapping logic and do the layout again.
Try it with text of different size, it will auto adjust. I would say this solution is pretty hacky but it works. If you are not satisfied with it take a look at this.
use
android:textAlignment="textStart"

Categories

Resources