How to add elements to a GridLayout dynamically wi

2019-05-28 03:43发布

问题:

This question already has an answer here:

  • Android GridLayout with dynamic number of columns per row 2 answers

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?

回答1:

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;