Android - LinearLayout poziomy z zawijającymi elementami podrzędnymi

89

Czy istnieje właściwość do ustawienia dla LinearLayout systemu Android, która umożliwi prawidłowe zawijanie formantów podrzędnych?

Czyli - mam zmienną liczbę dzieci i chciałbym ułożyć je poziomo jak:

Przykład: Control1, Control2, Control3, ...

Robię to ustawiając:

ll.setOrientation (LinearLayout.HORIZONTAL);
foreach (Dziecko c u dzieci)
  ll.addView (c);

Jeśli jednak mam dużą liczbę dzieci, ostatnie zostaje odcięte, zamiast przejść do następnej linii.

Masz jakiś pomysł, jak to naprawić?

nikib3ro
źródło
Sprawdź to repozytorium. <br/> github.com/ranvijaySingh-Webonise/AdjustableLayout <br/> <br/> Jeśli chcesz coś takiego. <br/> <br/> ! [Przykład regulowanego układu ] ( i.stack.imgur.com /68dHW.png )
Rana Ranvijay Singh
Sprawdź to: stackoverflow.com/questions/37147677/ ...
Aryan Najafi

Odpowiedzi:

58

Od maja 2016 Google stworzył własne, FlexBoxLayoutktóre powinno rozwiązać Twój problem.

Repozytorium GitHub można znaleźć tutaj: https://github.com/google/flexbox-layout

mysteryhobo
źródło
7
@fractalwrench jak można dołączyć do odpowiedzi „istotne części” biblioteki Java?
TWiStErRob
Dziękuję Ci bardzo!
DmitryKanunnikoff
1
To prawdopodobnie powinna być obecnie zalecana metoda i akceptowana odpowiedź.
Magnus W
56

To powinno być to, czego chcesz:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 *
 * @author RAW
 */
public class FlowLayout extends ViewGroup {

    private int line_height;

    public static class LayoutParams extends ViewGroup.LayoutParams {

        public final int horizontal_spacing;
        public final int vertical_spacing;

        /**
         * @param horizontal_spacing Pixels between items, horizontally
         * @param vertical_spacing Pixels between items, vertically
         */
        public LayoutParams(int horizontal_spacing, int vertical_spacing) {
            super(0, 0);
            this.horizontal_spacing = horizontal_spacing;
            this.vertical_spacing = vertical_spacing;
        }
    }

    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);

        final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
        final int count = getChildCount();
        int line_height = 0;

        int xpos = getPaddingLeft();
        int ypos = getPaddingTop();

        int childHeightMeasureSpec;
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
        } else {
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }


        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec);
                final int childw = child.getMeasuredWidth();
                line_height = Math.max(line_height, child.getMeasuredHeight() + lp.vertical_spacing);

                if (xpos + childw > width) {
                    xpos = getPaddingLeft();
                    ypos += line_height;
                }

                xpos += childw + lp.horizontal_spacing;
            }
        }
        this.line_height = line_height;

        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
            height = ypos + line_height;

        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            if (ypos + line_height < height) {
                height = ypos + line_height;
            }
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(1, 1); // default of 1px spacing
    }

    @Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(
        android.view.ViewGroup.LayoutParams p) {
        return new LayoutParams(1, 1, p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        if (p instanceof LayoutParams) {
            return true;
        }
        return false;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        final int width = r - l;
        int xpos = getPaddingLeft();
        int ypos = getPaddingTop();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final int childw = child.getMeasuredWidth();
                final int childh = child.getMeasuredHeight();
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (xpos + childw > width) {
                    xpos = getPaddingLeft();
                    ypos += line_height;
                }
                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.horizontal_spacing;
            }
        }
    }
}

i plik XML

/* you must write your package name and class name */
<org.android.FlowLayout
                android:id="@+id/flow_layout"
                android:layout_marginLeft="5dip"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"/>
Randy Sugianto „Yuku”
źródło
3
Działa lepiej i znacznie bardziej elegancko niż przyjęta odpowiedź kape123.
Martin Konecny
Dobre rozwiązanie, ale zła odpowiedź. Uwzględnij odpowiedź zamiast po prostu linkować bez wyjaśnienia.
Aeyoun
5
Wygląda na to, że żaden z upvoterów nie użył tego kodu. Ponieważ jeśli to zrobisz, zrywa z rozszerzeniem ClassCastException. Przyczyną tego wyjątku jest niepełne skopiowanie z innego pytania Stackoverflow, stackoverflow.com/q/549451/1741542 . Jeśli dodasz generateLayoutParams(ViewGroup.LayoutParams p)metodę, będzie działać zgodnie z oczekiwaniami.
Olaf Dietsche
9
nie konstruktora new LayoutParams(1, 1, p), nie wiem dlaczego ludzie upvote bez kodu stara
Faisal Naseera
Dziękuję za odpowiedź, ale jak mogę tego użyć, jeśli chcę owinąć dzieci od prawej do lewej?
TheTallWitch
42

Dla każdego, kto potrzebuje takiego zachowania:

private void populateLinks(LinearLayout ll, ArrayList<Sample> collection, String header) {

    Display display = getWindowManager().getDefaultDisplay();
    int maxWidth = display.getWidth() - 10;

    if (collection.size() > 0) {
        LinearLayout llAlso = new LinearLayout(this);
        llAlso.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        llAlso.setOrientation(LinearLayout.HORIZONTAL);

        TextView txtSample = new TextView(this);
        txtSample.setText(header);

        llAlso.addView(txtSample);
        txtSample.measure(0, 0);

        int widthSoFar = txtSample.getMeasuredWidth();
        for (Sample samItem : collection) {
            TextView txtSamItem = new TextView(this, null,
                    android.R.attr.textColorLink);
            txtSamItem.setText(samItem.Sample);
            txtSamItem.setPadding(10, 0, 0, 0);
            txtSamItem.setTag(samItem);
            txtSamItem.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    TextView self = (TextView) v;
                    Sample ds = (Sample) self.getTag();

                    Intent myIntent = new Intent();
                    myIntent.putExtra("link_info", ds.Sample);
                    setResult("link_clicked", myIntent);
                    finish();
                }
            });

            txtSamItem.measure(0, 0);
            widthSoFar += txtSamItem.getMeasuredWidth();

            if (widthSoFar >= maxWidth) {
                ll.addView(llAlso);

                llAlso = new LinearLayout(this);
                llAlso.setLayoutParams(new LayoutParams(
                        LayoutParams.FILL_PARENT,
                        LayoutParams.WRAP_CONTENT));
                llAlso.setOrientation(LinearLayout.HORIZONTAL);

                llAlso.addView(txtSamItem);
                widthSoFar = txtSamItem.getMeasuredWidth();
            } else {
                llAlso.addView(txtSamItem);
            }
        }

        ll.addView(llAlso);
    }
}
nikib3ro
źródło
Jakiś sposób to zrobić w plikach układu?
Gusdor
1
Hej, czy możesz powiedzieć, co to jest „Sample samItem”
ShreeshaDas
1

Szukam rozwiązania podobnego, ale prostszego problemu, jakim jest zawijanie podrzędnej treści tekstowej w układzie poziomym. Rozwiązanie kape123 działa dobrze. Ale znajdź prostszy sposób rozwiązania tego problemu, używając ClickableSpan. Może przydałoby się to w jakimś prostym przypadku. skrawek:

        String[] stringSource = new String[sourceList.size()];
        for (int i = 0; c < sourceList.size(); i++) {
            String text = sourceList.get(i);
            stringSource[i] = text;
        }

        SpannableString totalContent = new SpannableString(TextUtils.join(",", stringSource));
        int start = 0;
        for (int j = 0; j < stringSource.length(); j++) {
            final String text = stringSource[j];
            ClickableSpan span = new ClickableSpan() {

        @Override
                public void updateDrawState(TextPaint ds) {
                    ds.setUnderlineText(true);
                    ds.setColor(getResources().getColor(R.color.green));
                }
                @Override
                public void onClick(View widget) {
                    // the text clicked
                }
            };
    int end = (start += text.length());
            totalContent.setSpan(span, start, end, 0);
            star = end + 1;
        }

        TextView wrapperView = (TextView) findViewById(horizontal_container_id);
        wrapperView.setMovementMethod(LinkMovementMethod.getInstance());

        wrapperView.setText(totalContent, BufferType.SPANNABLE);
    }
lannyf
źródło
Przepraszamy, ale wygląda na to, że OP musi zawijać widoki, a nie tekst.
Saito Mea
1

Zmodyfikowana wersja kodu z odpowiedzi Randy'ego Sugianto 'Yuku i to, z czym w końcu poszedłem:

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.View.MeasureSpec.*
import android.view.ViewGroup
import androidx.core.content.withStyledAttributes
import androidx.core.view.children
import *.*.*.R


class FlowLayout(context: Context, attributeSet: AttributeSet) : ViewGroup(context, attributeSet) {

    private var lineHeight: Int = 0

    private var horizontalSpacing = 0F
    private var verticalSpacing = 0F

    init {
        context.withStyledAttributes(attributeSet, R.styleable.FlowLayout) {
            horizontalSpacing = getDimension(R.styleable.FlowLayout_horizontalSpacing, 0F)
            verticalSpacing = getDimension(R.styleable.FlowLayout_verticalSpacing, 0F)
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

        val width = getSize(widthMeasureSpec) - paddingLeft - paddingRight
        var height = getSize(heightMeasureSpec) - paddingTop - paddingBottom

        var xPosition = paddingLeft
        var yPosition = paddingTop

        val childHeightMeasureSpec = makeMeasureSpec(
            height, if (getMode(heightMeasureSpec) == AT_MOST) AT_MOST else UNSPECIFIED
        )

        children.forEach { child ->
            if (child.visibility != GONE) {
                val layoutParams = child.layoutParams as LayoutParamsWithSpacing
                child.measure(makeMeasureSpec(width, AT_MOST), childHeightMeasureSpec)
                val childWidth = child.measuredWidth
                lineHeight =
                        Math.max(lineHeight, child.measuredHeight + layoutParams.verticalSpacing)

                if (xPosition + childWidth > width) {
                    xPosition = paddingLeft
                    yPosition += lineHeight
                }

                xPosition += childWidth + layoutParams.horizontalSpacing
            }
        }

        if (getMode(heightMeasureSpec) == UNSPECIFIED ||
            getMode(heightMeasureSpec) == AT_MOST && yPosition + lineHeight < height
        ) {
            height = yPosition + lineHeight
        }

        setMeasuredDimension(width, height)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        val width = right - left
        var xPosition = paddingLeft
        var yPosition = paddingTop

        children.forEach { child ->
            if (child.visibility != View.GONE) {
                val layoutParams = child.layoutParams as LayoutParamsWithSpacing
                val childWidth = child.measuredWidth
                if (xPosition + childWidth > width) {
                    xPosition = paddingLeft
                    yPosition += lineHeight
                }
                child.layout(
                    xPosition, yPosition, xPosition + childWidth,
                    yPosition + child.measuredHeight
                )
                xPosition += layoutParams.horizontalSpacing
                xPosition += childWidth
            }
        }
    }

    override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams =
        LayoutParamsWithSpacing(1, 1)

    override fun generateLayoutParams(layoutParams: LayoutParams) =
        LayoutParamsWithSpacing(horizontalSpacing.toInt(), verticalSpacing.toInt())

    override fun checkLayoutParams(layoutParams: LayoutParams) =
        layoutParams is LayoutParamsWithSpacing

    class LayoutParamsWithSpacing(val horizontalSpacing: Int, val verticalSpacing: Int) :
        ViewGroup.LayoutParams(0, 0)
}

W pliku style / attrs.xml:

<resources>
    <declare-styleable name="FlowLayout">
        <attr name="horizontalSpacing" format="dimension" />
        <attr name="verticalSpacing" format="dimension" />
    </declare-styleable>
</resources>

Stosowanie:

<*.*.*.*.FlowLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:horizontalSpacing="8dp"
    app:verticalSpacing="8dp">

    <!-- ... -->

</*.*.*.*.FlowLayout>
janosch
źródło
0
//this method will add image view to liner grid and warp it if no space in new child LinearLayout grid 
private void addImageToLinyerLayout(LinearLayout ll , ImageView v)
{
    //set the padding and margin and weight 
    v.setPadding(5, 5, 5, 5);

    Display display = getWindowManager().getDefaultDisplay();
    int maxWidth = display.getWidth() - 10;
    int maxChildeNum = (int) ( maxWidth / (110)) ; 
    Toast.makeText(getBaseContext(), "c" + v.getWidth() ,
            Toast.LENGTH_LONG).show();
    //loop through all child of the LinearLayout
    for (int i = 0; i < ll.getChildCount(); i++) {
        View chidv = ll.getChildAt(i);
        Class c = chidv.getClass();
        if (c == LinearLayout.class) {
            //here we are in the child lay out check to add the imageView if there is space 
            //Available else we will add it to new linear layout 
            LinearLayout chidvL = (LinearLayout)chidv; 
            if(chidvL.getChildCount() < maxChildeNum)
            {
                chidvL.addView(v);
                return;
            }
        } else{
           continue;
        } 
    }

    //if you reached here this means there was no roam for adding view so we will 
    //add new linear layout 
    LinearLayout childLinyer = new LinearLayout(this);
    childLinyer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
            LayoutParams.WRAP_CONTENT));

    childLinyer.setOrientation(LinearLayout.HORIZONTAL);
    ll.addView(childLinyer);
    childLinyer.addView(v);

}

powyższa metoda doda obraz imgeview obok siebie, jak agrid i do układu

  <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" 
    android:id="@+id/imageslayout"
    ></LinearLayout>

zamieszczam to rozwiązanie, aby komuś pomogło i zaoszczędziło trochę czasu i używam go w mojej aplikacji

M.Ali El-Sayed
źródło
0

Skończyło się na użyciu TagView :

<com.cunoraz.tagview.TagView
        android:id="@+id/tag_group"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />




  TagView tagGroup = (TagView)findviewById(R.id.tag_view);
 //You can add one tag
 tagGroup.addTag(Tag tag);
 //You can add multiple tag via ArrayList
 tagGroup.addTags(ArrayList<Tag> tags);
 //Via string array
 addTags(String[] tags);


   //set click listener
      tagGroup.setOnTagClickListener(new OnTagClickListener() {
            @Override
            public void onTagClick(Tag tag, int position) {
            }
        });

   //set delete listener
            tagGroup.setOnTagDeleteListener(new OnTagDeleteListener() {
            @Override
            public void onTagDeleted(final TagView view, final Tag tag, final int position) {
            }
        });

wprowadź opis obrazu tutaj

Alberto M.
źródło
Dzięki za odniesienie do tego. To pierwszy raz, kiedy pobrałem i użyłem biblioteki Git. Wymagało to trochę badań, ale skończyłem i ten TagView jest niesamowity. Dokładnie to, czego szukałem!
John Ward
Przy okazji, jakich reguł użyłeś do tworzenia losowych przypisań kolorów. Trudno dostrzec wzór.
John Ward
Zrobiłem zrzut ekranu z przykładowej aplikacji. Możesz spojrzeć na tę konkretną linię github.com/Cutta/TagView/blob/master/app/src/main/java/cuneyt/…, gdzie tworzona jest nowa klasa TagClass. W konstruktorze TagClass przypisany jest losowy kolor github.com/Cutta/TagView/blob/master/app/src/main/java/cuneyt/… do późniejszego wykorzystania.
Alberto M
Wspaniale jest zobaczyć samo wyjście, ale czy umożliwia ono wyrównanie dzieci do środka linii?
Kamalakannan J
powinieneś zapytać programistę i / lub otworzyć problem na github! Sam tego nie rozwinąłem
Alberto M,
0

Google oferuje własne rozwiązanie: klasę FlowLayout

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.google.android.material.internal;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.R.styleable;

@RestrictTo({Scope.LIBRARY_GROUP})
public class FlowLayout extends ViewGroup {
  private int lineSpacing;
  private int itemSpacing;
  private boolean singleLine;

  public FlowLayout(Context context) {
    this(context, (AttributeSet)null);
  }

  public FlowLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.singleLine = false;
    this.loadFromAttributes(context, attrs);
  }

  @TargetApi(21)
  public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    this.singleLine = false;
    this.loadFromAttributes(context, attrs);
  }

  private void loadFromAttributes(Context context, AttributeSet attrs) {
    TypedArray array = context.getTheme().obtainStyledAttributes(attrs, styleable.FlowLayout, 0, 0);
    this.lineSpacing = array.getDimensionPixelSize(styleable.FlowLayout_lineSpacing, 0);
    this.itemSpacing = array.getDimensionPixelSize(styleable.FlowLayout_itemSpacing, 0);
    array.recycle();
  }

  protected int getLineSpacing() {
    return this.lineSpacing;
  }

  protected void setLineSpacing(int lineSpacing) {
    this.lineSpacing = lineSpacing;
  }

  protected int getItemSpacing() {
    return this.itemSpacing;
  }

  protected void setItemSpacing(int itemSpacing) {
    this.itemSpacing = itemSpacing;
  }

  protected boolean isSingleLine() {
    return this.singleLine;
  }

  public void setSingleLine(boolean singleLine) {
    this.singleLine = singleLine;
  }

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int maxWidth = widthMode != -2147483648 && widthMode != 1073741824 ? 2147483647 : width;
    int childLeft = this.getPaddingLeft();
    int childTop = this.getPaddingTop();
    int childBottom = childTop;
    int maxChildRight = 0;
    int maxRight = maxWidth - this.getPaddingRight();

    int finalWidth;
    for(finalWidth = 0; finalWidth < this.getChildCount(); ++finalWidth) {
      View child = this.getChildAt(finalWidth);
      if (child.getVisibility() != 8) {
        this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
        LayoutParams lp = child.getLayoutParams();
        int leftMargin = 0;
        int rightMargin = 0;
        if (lp instanceof MarginLayoutParams) {
          MarginLayoutParams marginLp = (MarginLayoutParams)lp;
          leftMargin += marginLp.leftMargin;
          rightMargin += marginLp.rightMargin;
        }

        int childRight = childLeft + leftMargin + child.getMeasuredWidth();
        if (childRight > maxRight && !this.isSingleLine()) {
          childLeft = this.getPaddingLeft();
          childTop = childBottom + this.lineSpacing;
        }

        childRight = childLeft + leftMargin + child.getMeasuredWidth();
        childBottom = childTop + child.getMeasuredHeight();
        if (childRight > maxChildRight) {
          maxChildRight = childRight;
        }

        childLeft += leftMargin + rightMargin + child.getMeasuredWidth() + this.itemSpacing;
      }
    }

    finalWidth = getMeasuredDimension(width, widthMode, maxChildRight);
    int finalHeight = getMeasuredDimension(height, heightMode, childBottom);
    this.setMeasuredDimension(finalWidth, finalHeight);
  }

  private static int getMeasuredDimension(int size, int mode, int childrenEdge) {
    switch(mode) {
    case -2147483648:
      return Math.min(childrenEdge, size);
    case 1073741824:
      return size;
    default:
      return childrenEdge;
    }
  }

  protected void onLayout(boolean sizeChanged, int left, int top, int right, int bottom) {
    if (this.getChildCount() != 0) {
      boolean isRtl = ViewCompat.getLayoutDirection(this) == 1;
      int paddingStart = isRtl ? this.getPaddingRight() : this.getPaddingLeft();
      int paddingEnd = isRtl ? this.getPaddingLeft() : this.getPaddingRight();
      int childStart = paddingStart;
      int childTop = this.getPaddingTop();
      int childBottom = childTop;
      int maxChildEnd = right - left - paddingEnd;

      for(int i = 0; i < this.getChildCount(); ++i) {
        View child = this.getChildAt(i);
        if (child.getVisibility() != 8) {
          LayoutParams lp = child.getLayoutParams();
          int startMargin = 0;
          int endMargin = 0;
          if (lp instanceof MarginLayoutParams) {
            MarginLayoutParams marginLp = (MarginLayoutParams)lp;
            startMargin = MarginLayoutParamsCompat.getMarginStart(marginLp);
            endMargin = MarginLayoutParamsCompat.getMarginEnd(marginLp);
          }

          int childEnd = childStart + startMargin + child.getMeasuredWidth();
          if (!this.singleLine && childEnd > maxChildEnd) {
            childStart = paddingStart;
            childTop = childBottom + this.lineSpacing;
          }

          childEnd = childStart + startMargin + child.getMeasuredWidth();
          childBottom = childTop + child.getMeasuredHeight();
          if (isRtl) {
            child.layout(maxChildEnd - childEnd, childTop, maxChildEnd - childStart - startMargin, childBottom);
          } else {
            child.layout(childStart + startMargin, childTop, childEnd, childBottom);
          }

          childStart += startMargin + endMargin + child.getMeasuredWidth() + this.itemSpacing;
        }
      }

    }
  }
}

Ta klasa działa podobnie do opisanej powyżej klasy FlowLayout Ale nie powinieneś dodawać żadnej nowej klasy do swojego projektu, a projektant działa lepiej z tą klasą niż z niestandardową

Alexander Gavriliuk
źródło
0

Chciałem bardzo prostego rozwiązania, które byłoby elastyczne (dlatego używam LinearLayouts). Oto, co wymyśliłem.

https://github.com/ShalakoSnell/Wrapping_Linear_Layout

Uwaga: Dołączyłem przykładową metodę używającą textviews (zobacz textViewArrayListForExample ()) XML to tylko widok nadrzędny LinearLayout z id i orientacją pionową, nic więcej nie jest wymagane. Aby użyć: przekaż tablicę widoków, które są opakowane w LinearLayouts, wraz z widokiem nadrzędnym i kontekstem. (patrz viewAdapterArrayList (ArrayList textViews))

Przekazywanie tablicy LinearLayouts sprawia, że ​​to podejście jest tak elastyczne, ponieważ umożliwia dodawanie różnych typów widoków. Więc w pierwszym LinearLayout mógłbyś mieć tekst, aw drugim mógłbyś mieć obraz, a następnie w trzecim przycisk i tak dalej ...

Przykładem portret przykładem krajobrazu 50dp marże w verticalLinearLayout (przepraszam nie mogę dodać jeszcze zdjęć ... zobaczyć linki.

MainActivity.java

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

        new WrappingLinearLayout(
                viewAdapterArrayList(textViewArrayListForExample()), // <-- replace this with you own array of LinearLayouts
                (LinearLayout) findViewById(R.id.verticalLinearLayout),
                this);
    }

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/verticalLinearLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</LinearLayout>

WrappingLinearLayout.Java

package com.example.wrapping_linear_layout;

import android.content.Context;
import android.widget.LinearLayout;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;

public class WrappingLinearLayout {

    public WrappingLinearLayout(@NotNull final ArrayList<LinearLayout> views, @NotNull final LinearLayout verticalLinearLayout, @NotNull final Context context) {
        verticalLinearLayout.post(new Runnable() {
            @Override
            public void run() {
                execute(views, verticalLinearLayout, context);
            }
        });

    }

    private void execute(@NotNull ArrayList<LinearLayout> views, @NotNull final LinearLayout verticalLinearLayout, @NotNull final Context context) {

        ArrayList<LinearLayout> horizontalLinearLayouts = new ArrayList<>();
        LinearLayout horizontalLinearLayout = new LinearLayout(context);
        horizontalLinearLayouts.add(horizontalLinearLayout);
        int verticalLinearLayoutWidth = verticalLinearLayout.getMeasuredWidth()
                - (verticalLinearLayout.getPaddingLeft()
                        + verticalLinearLayout.getPaddingRight());

        int totalWidthOfViews = 0;

        for (LinearLayout view : views) {

            view.measure(0, 0);
            int currentViewWidth = view.getMeasuredWidth();
            if (totalWidthOfViews + view.getMeasuredWidth() > verticalLinearLayoutWidth) {
                horizontalLinearLayout = new LinearLayout(context);
                horizontalLinearLayouts.add(horizontalLinearLayout);
                totalWidthOfViews = 0;
            }

            totalWidthOfViews += currentViewWidth;

            horizontalLinearLayout.addView(view);
        }

        for (LinearLayout linearLayout : horizontalLinearLayouts) {
            verticalLinearLayout.addView(linearLayout);
        }
    }

}

Dodatkowy kod, na przykład przypadek użycia:

    private ArrayList<LinearLayout> viewAdapterArrayList(ArrayList<TextView> textViews) {
        ArrayList<LinearLayout> views = new ArrayList<>();
        for (TextView textView : textViews) {
            LinearLayout linearLayout = new LinearLayout(this);
            linearLayout.addView(textView);
            views.add(linearLayout);
        }
        return views;
    }

    private ArrayList<TextView> textViewArrayListForExample() {
        ArrayList<TextView> textViews = new ArrayList<>();
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        for (int i = 0; i < 40; i++) {
            TextView textView = new TextView(this);
            textView.setText("View " + i + " |");
            if (i < 20) {
                if (i % 5 == 0) {
                    textView.setText("View longer view " + i + " |");
                } else if (i % 7 == 0) {
                    textView.setText("View different length view " + i + " |");
                } else if (i % 9 == 0) {
                    textView.setText("View very long view that is so long it's really long " + i + " |");
                }
            }
            textView.setMaxLines(1);
            textView.setBackground(new ColorDrawable(Color.BLUE));
            textView.setTextColor(Color.WHITE);
            textView.setLayoutParams(layoutParams);
            textView.setPadding(20, 2, 20, 2);
            layoutParams.setMargins(10, 2, 10, 2);
            textViews.add(textView);
        }
        return textViews;
    }
}
rsmediapc
źródło
0

W przeszłości wiele niestandardowych rozwiązań i bibliotek próbowało i faktycznie rozwiązało ten problem.

Począwszy od Constraint Layout 2.0tego możemy teraz użyćFlow

Przepływ to nowy wirtualny układ do tworzenia łańcuchów, które mogą przechodzić do następnej linii lub nawet do innej sekcji ekranu, gdy zabraknie im miejsca. Jest to przydatne, gdy układasz wiele elementów w łańcuchu, ale nie masz pewności, jak duży będzie kontener w czasie wykonywania. Możesz użyć tego do zbudowania układu na podstawie dynamicznego rozmiaru w aplikacji, takiego jak szerokość ekranu podczas obracania.

Oto jak będzie wyglądał plik XML:

<androidx.constraintlayout.helper.widget.Flow
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintTop_toTopOf="parent"
   app:flow_wrapMode="chain"
   app:constraint_referenced_ids="card1, card2, card3"
   />

Uwaga app:constraint_referenced_idsi app:flow_wrapModewłaściwości.

Przekazujemy widoki za pomocą pierwszego i wybieramy, jak je zawinąć drugim.

app:flow_wrapMode akceptuje 3 różne opcje:

brak :create a single chain, overflowing if the content doesn’t fit

łańcuch :on overflow, create add another chain for the overflow elements

wyrównaj :similar to chain, but align rows into columns

Więcej informacji można znaleźć w poście dla programistów Androida

i oficjalne dokumenty

Sarantis Tofas
źródło