Как динамически добавлять элементы в GridLayout с разной шириной столбцов для каждой строки в зависимости от размера элемента?

У меня есть array тегов переменного размера, которые я хочу показать внутри gridlayout. Проблема в том, что теги различаются по длине, и размещение их в statically определенной сетке выглядит несколько запутанным, даже если некоторые теги намного больше других.

Поэтому я хотел бы иметь возможность помещать тег за тегом до тех пор, пока не останется места для полного тега, а затем перейти к следующей строке. В основном что-то вроде этого:

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

Я думаю, вы, ребята, поняли суть.

Прямо сейчас у меня есть что-то вроде этого, но это не совсем то, что мне нужно.

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);
}

Это больше похоже на:

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

Поэтому я также пытаюсь получить ширину каждого TextView, чтобы я мог вычислять пробелы вручную и добавлять элементы соответственно, но это также не удается, поскольку размеры все еще 0, потому что они не нарисованы. Так что, похоже, мне придется использовать API соответственно, чтобы получить желаемое поведение.

Я все еще новичок в этом GridLayout, и API довольно большой, поэтому, ребята, не могли бы вы мне помочь?


person CantThinkOfAnything    schedule 12.02.2016    source источник
comment
Я реализовал то же самое, но по какой-то причине рисовал долго или должен был рисовать перед добавлением в flowlayout. Вы можете использовать этот виджет github.com/ApmeM/android-flowlayout . Также вы можете попробовать менеджер компоновки сетки в шахматном порядке inducesmile.com/android/ .   -  person AmirG    schedule 12.02.2016
comment
Вы реализовали код, который я разместил? Или вы реализовали подобное решение? Потому что он работает довольно гладко на моем телефоне.   -  person CantThinkOfAnything    schedule 12.02.2016
comment
Аналогичное решение, но я беру текст с сервера и динамически создаю кнопки или теги в вашем случае. Однако я не мог бы знать размер каждого элемента, если бы они не были нарисованы первыми. Поэтому я как бы сделал хак, чтобы нарисовать их на заднем плане, а затем я начал добавлять их к реальному макету потока, добавляя их в строку, если на экране достаточно места, чтобы показать, если нет, я создаю новую строку. Очевидно, это неэффективно, поэтому я перестал использовать этот виджет, но мне нравится приходить и смотреть, как людям удается решить эту проблему :).   -  person AmirG    schedule 12.02.2016
comment
ах, я вижу, хе-хе, спасибо за библиотеку, хотя, проверю ее, если она работает лучше, я мог бы переключиться   -  person CantThinkOfAnything    schedule 12.02.2016
comment
Я думаю, что единственное, что мне нужно было сделать, это ваш метод createNewTag(), чтобы получить оттуда ширину элемента без необходимости сначала рисовать его на экране, хорошая работа. Библиотека забавная, в ней есть несколько методов для управления гравитацией предметов и тому подобное. Хорошего дня !.   -  person AmirG    schedule 12.02.2016
comment
@AmirG да, действительно, после правильного измерения вида вы можете получить ширину и высоту. ваш подход должен был сработать после этого.   -  person CantThinkOfAnything    schedule 12.02.2016


Ответы (1)


Хорошо, мне удалось решить свою проблему, подсчитав, сколько места нужно и сколько места осталось. Когда для вставки тега остается слишком мало места, он переходит к следующей строке.

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);
    }
}

Тег создается и измеряется следующим образом:

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 вычисляется следующим образом:

DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
deviceWidth = metrics.widthPixels;
deviceHeight = metrics.heightPixels;
person CantThinkOfAnything    schedule 12.02.2016