How to add elements to a GridLayout dynamically with different column widths for every row based on element size? [duplicate]
Asked Answered
G

1

3

I have a variable-size array of tags that I want to show inside a gridlayout. The problem is that the tags vary in length and it looks kind of messy to put them inside a statically defined grid even when some tags are much bigger than others.

So I would like to be able to put tag after tag until there is no more space left for a full tag and then go to the next row. Basically something like this:

| *** ****** ****** ** ***** |
| ** ***** *** ********* *** |
| ********* ***** ***        |
| ************** ********    |
| ****** ******** ********   |
| *****************          |
| ************** ***** ***** |

I think you guys get the point.

Right now, I got something like this but its not quite doing what I need.

int total = tags.size();
int column = 3;
int row = total / column;
suggestedTagsLayout.setColumnCount(column);
suggestedTagsLayout.setRowCount(row + 1);

for (int i = 0, c = 0, r = 0; i < total; i++, c++) {
    if (c == column) {
        c = 0;
        r++;
    }
    TextView tag = createNewTag(tags.get(i));
    tag.setBackgroundColor(Color.BLUE);
    tag.setLayoutParams(new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT));

    GridLayout.Spec rowSpan = GridLayout.spec(GridLayout.UNDEFINED, 1);
    GridLayout.Spec colspan = GridLayout.spec(GridLayout.UNDEFINED, 1);
    if (r == 0 && c == 0) {
        logger.e("spec");
        colspan = GridLayout.spec(GridLayout.UNDEFINED, 1);
        rowSpan = GridLayout.spec(GridLayout.UNDEFINED, 1);
    }

    logger.d("\n\ntag = " + tag.getText().toString());

    int width = tag.getWidth();
    int height = tag.getHeight();

    logger.d("width = " + width);
    logger.d("height = " + height);

    width = tag.getMeasuredWidth();
    height = tag.getMeasuredHeight();

    logger.d("getMeasuredWidth = " + width);
    logger.d("getMeasuredHeight = " + height);

    GridLayout.LayoutParams gridParam = new GridLayout.LayoutParams(
            rowSpan, colspan);
    suggestedTagsLayout.addView(tag, gridParam);
}

This looks more like :

| ***** **** ******** **** |
| ****       ********      |
| *******    ******        |

So I am also trying to get the width of each TextView so that I can calculate the spaces manually and add items accordingly but this is also failing as the dimensions are still 0 because they are not drawn. So it seems I will have to use the API accordingly to get the desired behaviour.

I am still new to this GridLayout and the api is quite large so could you guys help me out?

Godfree answered 12/2, 2016 at 11:50 Comment(6)
I implemented the same thing but for some reason took long to draw or had to draw before adding to the "flowlayout" . You can use this widget github.com/ApmeM/android-flowlayout . Also you could try a staggered grid layout manager inducesmile.com/android/….Sabrinasabsay
You implemented the code i posted? Or did you implement a similar solution? Because it is working pretty smoothly on my phone.Godfree
A similar solution but i'm taking the text from server and build dynamically the buttons or tags in your case. However i couldn't know the size of each item if they weren't drew first. So i kind of made hack to draw them in the background and then i start adding then to the real "flow layout", adding them to the row if it had enough space on the screen to show if not i make a new row. Obviously this is not efficient so i ended using that widget but i like to come and look how people have manage to solve this :).Sabrinasabsay
ah i see heheh,, thanks for the library tho,, will check that one out,, if it works better i might switchGodfree
I think the only thing that i needed to do was your createNewTag() method to get from there the item width without having to draw it on screen first, good job. The library is fun and have some methods for controlling the gravity of items and that kind of stuff. Have a nice day !.Sabrinasabsay
@Sabrinasabsay yes indeed,, after correctly meassuring the view, you can get the width and height. your aproach should have worked after that.Godfree
G
3

Allright,, I managed to solve my own problem by calculating how much space is needed and how much space is left. When there is too little space left for the tag to be inserted, it will go to the next row.

private void fillSuggestedTagsLayout() {
    // get all strings to insert in tag
    ArrayList<String> tagsText = getTagsList();

    // maps for connecting string to textview and string to textview width
    HashMap<String, TextView> tagMap = new HashMap<>();
    HashMap<String, Integer> tagWidthMap = new HashMap<>();

    // total width
    float totalWidth = 0;

    // for each string
    for (String s : tagsText) {
        // create textview
        TextView txtView = createNewTag(s, false);
        // store textview with string
        tagMap.put(s, txtView);
        // store width also
        tagWidthMap.put(s, txtView.getMeasuredWidth());

        logger.d("width of txtView = " + txtView.getMeasuredWidth());
        // count all textview widths in order to calculate amount of rows needed for display
        totalWidth += txtView.getMeasuredWidth();
    }

    // gridlayout width to calculate rows needed
    final float layoutWidth = suggestedTagsLayout.getWidth();
    logger.e("gridlayout width = " + layoutWidth);
    logger.e("total = " + totalWidth);
    // amount of rows equals to totalwidth of elements / layout width
    final float rows = totalWidth / layoutWidth;

    int rowsRounded = (int) rows;
    // rows needed could be 1,2 or something meaning that we need extra space.
    // every decimal will need to get rounded up. 1.2 becomes 2 for example
    if (rows > rowsRounded) {
        rowsRounded++;
    }

    // total amount of elements
    int total = tagsText.size();

    // column count, 200 in order to have great precision in position of elements
    final int columns = 200;

    // amount of space needed per column
    final float dpPerColumn = layoutWidth / (float) columns;

    // set layout specs
    suggestedTagsLayout.setColumnCount(columns);
    suggestedTagsLayout.setRowCount(rowsRounded);


    for (int item = 0, column = 0, row = 0; item < total; item++) {
        // get string
        String s = tagsText.get(item);
        // get txtview
        TextView tag = tagMap.get(s);
        tag.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));

        // calculate amount of columns needed for tag.
        // tagwidth/sizePerColumn
        float colsToSpan = tagWidthMap.get(s) / dpPerColumn;
        // again, round up above in order to accomodate space needed
        int colsToSpanRounded = (int) colsToSpan;
        if (colsToSpan < colsToSpanRounded) {
            colsToSpanRounded++;
        }

        // now that we know the amount space needed for tag,
        // check if there is enough space on this row
        if ((column + colsToSpanRounded) > columns) {
            column = 0;
            row++;
        }

        // put tag on row N, span 1 row only
        GridLayout.Spec rowSpan = GridLayout.spec(row, 1);
        // put tag on column N, span N rows
        GridLayout.Spec colSpan = GridLayout.spec(column, colsToSpanRounded);
        logger.d("tag: " + s + " is taking " + colsToSpanRounded + " columns");
        logger.d("c = " + column + "   colsToSpan =" + colsToSpanRounded);
        logger.d("spanning between " + column + " and " + (column + colsToSpanRounded));
        logger.d("                                ");

        // increment column
        column += colsToSpanRounded;

        GridLayout.LayoutParams gridParam = new GridLayout.LayoutParams(
                rowSpan, colSpan);
        // add tag
        suggestedTagsLayout.addView(tag, gridParam);
    }
}

The tag is created and meassured with the following:

private TextView createNewTag(final String tagText, boolean withImage) {
    TextView textView = new TextView(getActivity());
    textView.setPadding(8,8,8,8);
    textView.setTypeface(Typeface.DEFAULT);
    textView.setText(tagText, TextView.BufferType.SPANNABLE);
    textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
    textView.setBackground(getActivity().getResources().getDrawable(R.drawable.tag_background));

    if(withImage) {
        Drawable img = getActivity().getResources().getDrawable(R.drawable.delete_tag_icon);
        textView.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
    }

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    textView.measure(widthMeasureSpec, heightMeasureSpec);

    logger.d(tagText);
    return textView;
}

deviceWith is calculated by doing:

DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
deviceWidth = metrics.widthPixels;
deviceHeight = metrics.heightPixels;
Godfree answered 12/2, 2016 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.