我怎样才能做一些像Android中的FlowLayout?

我怎样才能做一些像Android中的FlowLayout

如果你看到我在Devoxx大学日(在parleys.com上提供 )的演讲,你将学习如何自己做。 在演讲期间,我写了一个FlowLayout实现在舞台上,以展示编写自定义布局是多么简单。

实现在这里托pipe。

我没有足够的信誉发表评论罗曼盖伊的答案,但这就是答案应该是(我创build一个帐户只是为了分享我的编辑)。

无论如何,我看到其他人已经发现他非常酷的FlowLayout解决scheme有一些问题。 我可以自己find一个,我和其他人一样,看到有些孩子被剪断。 在algorithm的细节看来,在高度的计算似乎是一个非常简单的错误。 当最后一个孩子被放在一个新的线上,那么身高就没有被正确地计算出来。 我清理了一下计算(有一个奇怪的使用“高度”与currentHeight)。

以下更改修复了“如果在新行上最后一个孩子被剪辑”的问题:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthLimit = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight(); int widthMode = MeasureSpec.getMode(widthMeasureSpec); boolean growHeight = widthMode != MeasureSpec.UNSPECIFIED; int width = 0; int currentWidth = getPaddingLeft(); int currentHeight = getPaddingTop(); int maxChildHeight = 0; boolean breakLine = false; boolean newLine = false; int spacing = 0; final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); LayoutParams lp = (LayoutParams) child.getLayoutParams(); spacing = mHorizontalSpacing; if (lp.horizontalSpacing >= 0) { spacing = lp.horizontalSpacing; } if (growHeight && (breakLine || ((currentWidth + child.getMeasuredWidth()) > widthLimit))) { newLine = true; currentHeight += maxChildHeight + mVerticalSpacing; width = Math.max(width, currentWidth - spacing); currentWidth = getPaddingLeft(); maxChildHeight = 0; } else { newLine = false; } maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); lp.x = currentWidth; lp.y = currentHeight; currentWidth += child.getMeasuredWidth() + spacing; breakLine = lp.breakLine; } if (newLine == false) { width = Math.max(width, currentWidth - spacing); } width += getPaddingRight(); int height = currentHeight + maxChildHeight + getPaddingBottom(); setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); } 

您应该使用FlexboxLayout flexWrap="wrap"属性来使用FlexboxLayout

 <com.google.android.flexbox.FlexboxLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:flexWrap="wrap"> <!-- contents go here --> </com.google.android.flexbox.FlexboxLayout> 

有关构build说明,请参阅github回购。

FlexboxLayout的可视化表示 更多关于这个 – https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html

就像之前的答案之一,我从这里开始解决: http : //hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html

我把它扩展到了如下的儿童高度。

 import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; // Custom layout that wraps child views to a new line public class FlowLayout extends ViewGroup { private int marginHorizontal; private int marginVertical; public FlowLayout(Context context) { super(context); init(); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { // Specify the margins for the children marginHorizontal = getResources().getDimensionPixelSize(R.dimen.activity_half_horizontal_margin); marginVertical = getResources().getDimensionPixelSize(R.dimen.activity_half_vertical_margin); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int lowestBottom = 0; int lineHeight = 0; int myWidth = resolveSize(100, widthMeasureSpec); int wantedHeight = 0; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } child.measure(getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width), getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height)); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); lineHeight = Math.max(childHeight, lineHeight); if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line childLeft = getPaddingLeft(); childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point lineHeight = childHeight; } childLeft += childWidth + marginHorizontal; if (childHeight + childTop > lowestBottom) { // New lowest point lowestBottom = childHeight + childTop; } } wantedHeight += childTop + lineHeight + getPaddingBottom(); setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int lowestBottom = 0; int myWidth = right - left; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line childLeft = getPaddingLeft(); childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point } child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childLeft += childWidth + marginHorizontal; if (childHeight + childTop > lowestBottom) { // New lowest point lowestBottom = childHeight + childTop; } } } } 

我用这个作为包装多行TextEdits的解决scheme。 希望它有帮助!

这里是自定义类,您可以通过添加dynamic视图(也称为FlowLayout)来实现布局。

在这里输入图像描述

 import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /* Created By Dhavalkumar Solanki * */ public class FlowLayout extends ViewGroup { private int line_height_space; public static class LayoutParams extends ViewGroup.LayoutParams { public int horizontal_spacing; public 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_space = 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_space = Math.max(line_height_space, child.getMeasuredHeight() + lp.vertical_spacing); if (xpos + childw > width) { xpos = getPaddingLeft(); ypos += line_height_space; } xpos += childw + lp.horizontal_spacing; } } this.line_height_space = line_height_space; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { height = ypos + line_height_space; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { if (ypos + line_height_space < height) { height = ypos + line_height_space; } } setMeasuredDimension(width, height); } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(1, 1); // default of 1px spacing } @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_space; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + lp.horizontal_spacing; } } } } 

例如:

text_view.xml

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tool="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="5dp"> <TextView android:id="@+id/tvText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="19sp" android:background="@drawable/unselected_tag" android:textColor="@color/colorPrimary" tool:text="Temp" /> </RelativeLayout> 

activity_flow_layou_demo.xml

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tvTitleBusiness" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Business Interest " android:textColor="@color/colorPrimary" android:textSize="25sp" /> <com.example.tristateandroid2.radardemo.FlowLayout android:id="@+id/flowBusiness" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.example.tristateandroid2.radardemo.FlowLayout> </LinearLayout> <LinearLayout android:layout_marginTop="@dimen/activity_horizontal_margin" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tvTitlePrivate" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Private Interest " android:textColor="@color/colorPrimary" android:textSize="25sp" /> <com.example.tristateandroid2.radardemo.FlowLayout android:id="@+id/flowPrivate" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.example.tristateandroid2.radardemo.FlowLayout> </LinearLayout> </LinearLayout> </ScrollView> </RelativeLayout> 

FlowLayouDemo.java

 import android.graphics.Color; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import java.util.ArrayList; public class FlowLayouDemo extends AppCompatActivity { private TextView tvTitleBusiness; private FlowLayout flowBusiness; private TextView tvTitlePrivate; private FlowLayout flowPrivate; private ArrayList<TagModel> arrayList; private void findViews() { tvTitleBusiness = (TextView) findViewById(R.id.tvTitleBusiness); flowBusiness = (FlowLayout) findViewById(R.id.flowBusiness); tvTitlePrivate = (TextView) findViewById(R.id.tvTitlePrivate); flowPrivate = (FlowLayout) findViewById(R.id.flowPrivate); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_flow_layou_demo); findViews(); addLayouts(); } private void addLayouts() { if (arrayList == null) { arrayList = new ArrayList<>(); } flowBusiness.removeAllViews(); flowPrivate.removeAllViews(); for (int i = 0; i < 75; i++) { final boolean[] selected = {false}; View view = this.getLayoutInflater().inflate(R.layout.text_view, null); final TextView textView = (TextView) view.findViewById(R.id.tvText); if (i % 5 == 0) { arrayList.add(new TagModel(i, false, "Business VIEW : " + i)); textView.setText("Busi VIEW To IS : " + i); } else { arrayList.add(new TagModel(i, false, "TEXT IS : " + i)); textView.setText("Busi IS : " + i); } textView.setBackgroundResource(R.drawable.unselected_tag); textView.setTextColor(Color.parseColor("#3F51B5")); textView.setTag(i); if(i<=50){ flowBusiness.addView(view); }else { textView.setText("Priv View : "+i); flowPrivate.addView(view); } textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (selected[0]) { selected[0] = false; textView.setBackgroundResource(R.drawable.unselected_tag); textView.setTextColor(Color.parseColor("#3F51B5")); } else { selected[0] = true; textView.setBackgroundResource(R.drawable.selected_tag); textView.setTextColor(Color.parseColor("#FFFFFF")); } } }); } } } 

对支持MarginLayoutParams的@MattNotEquals() FlowLayout进行修订。

这只是MarginLayoutParms支持左,右,上,下边距的简约实现。

 import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * Original version courtesy of MattNotEquals() at http://stackoverflow.com/a/34169798/4515489 - 4/13/17. * 7/15/17 Revised to support MarginLayoutParams. */ public class FlowLayout extends ViewGroup { // Custom layout that wraps child views to a new line. public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int lowestBottom = 0; int lineHeight = 0; int myWidth = resolveSize(100, widthMeasureSpec); int wantedHeight = 0; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } child.measure(getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width), getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height)); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); lineHeight = Math.max(childHeight, lineHeight); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); childLeft += lp.leftMargin; childTop += lp.topMargin; if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line childLeft = getPaddingLeft() + lp.leftMargin; childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point lineHeight = childHeight; } childLeft += childWidth + lp.rightMargin; if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point lowestBottom = childTop + childHeight + lp.bottomMargin; } } wantedHeight += lowestBottom + getPaddingBottom(); // childTop + lineHeight + getPaddingBottom(); setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int lowestBottom = 0; int myWidth = right - left; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); childLeft += lp.leftMargin; childTop += lp.topMargin; if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line childLeft = getPaddingLeft() + lp.leftMargin; childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point } child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childLeft += childWidth + lp.rightMargin; if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point lowestBottom = childTop + childHeight + lp.bottomMargin; } } } @Override public boolean shouldDelayChildPressedState() { return false; } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FlowLayout.LayoutParams(getContext(), attrs); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { if (lp instanceof LayoutParams) { return new LayoutParams((LayoutParams) lp); } else if (lp instanceof MarginLayoutParams) { return new LayoutParams((MarginLayoutParams) lp); } else return super.generateLayoutParams(lp); } /** * Per-child layout information for layouts that support margins. */ public static class LayoutParams extends MarginLayoutParams { public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(@NonNull ViewGroup.LayoutParams source) { super(source); } public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) { super(source); } public LayoutParams(@NonNull LayoutParams source) { super(source); } } } 

很好的简单的自包含的FlowLayout代码在这里(只是几个简洁的gist.github文件)

http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html

但是,开箱即用的活动并不适合我加载自定义布局。

我发现这个解决方法[使用这个例子中的2-param .inflate()调用]

 @Override protected void onCreate(Bundle savedInstanceState) { // .. setContentView(R.layout.main_res_layout_activity_main); ViewGroup flowContainer = getFlowLayoutView(); // .. } ViewGroup getFlowLayoutView() { LayoutInflater inflater = getLayoutInflater(); ViewGroup flowLayout = (ViewGroup) inflater.inflate( R.layout.main_res_layout_activity_main, (FlowLayout) findViewById(R.id.flow_container) ); return flowLayout; } 

不,根据我所知,没有办法使小部件包装。