android 自动换行 layout

 

I'm trying to create an activity that presents some data to the user. The data is such that it can be divided into 'words', each being a widget, and sequence of 'words' would form the data ('sentence'?), the ViewGroup widget containing the words. As space required for all 'words' in a 'sentence' would exceed the available horizontal space on the display, I would like to wrap these 'sentences' as you would a normal piece of text.

The following code:

publicclassWrapTestextendsActivity{/** Called when the activity is first created. */@Overridepublicvoid onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);LinearLayout l =newLinearLayout(this);LinearLayout.LayoutParams lp =newLinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);LinearLayout.LayoutParams mlp =newLinearLayout.LayoutParams(newViewGroup.MarginLayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT));
		mlp.setMargins(0,0,2,0);for(int i =0; i <10; i++){TextView t =newTextView(this);
			t.setText("Hello");
			t.setBackgroundColor(Color.RED);
			t.setSingleLine(true);
			l.addView(t, mlp);}

		setContentView(l, lp);}}

yields something like the left picture, but I would want a layout presenting the same widgets like in the right one.

non-wrapping wrapping

Is there such a layout or combination of layouts and parameters, or do I have to implement my own ViewGroup for this?

shareimprove this question
 

11 Answers

up vote60down voteaccepted

I made my own layout that does what I want, but it is quite limited at the moment. Comments and improvement suggestions are of course welcome.

The activity:

package se.fnord.xmms2.predicate;import se.fnord.android.layout.PredicateLayout;import android.app.Activity;import android.graphics.Color;import android.os.Bundle;import android.widget.TextView;publicclassPredicateextendsActivity{/** Called when the activity is first created. */@Overridepublicvoid onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);PredicateLayout l =newPredicateLayout(this);for(int i =0; i <10; i++){TextView t =newTextView(this);
            t.setText("Hello");
            t.setBackgroundColor(Color.RED);
            t.setSingleLine(true);
            l.addView(t,newPredicateLayout.LayoutParams(2,0));}

        setContentView(l);}}

Or in an XML layout:

<se.fnord.android.layout.PredicateLayout
    android:id="@+id/predicate_layout"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"/>

And the Layout:

package se.fnord.android.layout;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/**
 * ViewGroup that arranges child views in a similar way to text, with them laid
 * out one line at a time and "wrapping" to the next line as needed.
 * 
 * Code licensed under CC-by-SA
 *  
 * @author Henrik Gustafsson
 * @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
 * @license http://creativecommons.org/licenses/by-sa/2.5/
 *
 */publicclassPredicateLayoutextendsViewGroup{privateint line_height;publicPredicateLayout(Context context){super(context);}publicPredicateLayout(Context context,AttributeSet attrs){super(context, attrs);}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){assert(MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED);finalint width =MeasureSpec.getSize(widthMeasureSpec);// The next line is WRONG!!! Doesn't take into account requested MeasureSpec mode!int height =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint count = getChildCount();int line_height =0;int xpos = getPaddingLeft();int ypos = getPaddingTop();for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalLayoutParams lp =(LayoutParams) child.getLayoutParams();
                child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(height,MeasureSpec.UNSPECIFIED));finalint childw = child.getMeasuredWidth();
                line_height =Math.max(line_height, child.getMeasuredHeight()+ lp.height);if(xpos + childw > width){
                    xpos = getPaddingLeft();
                    ypos += line_height;}

                xpos += childw + lp.width;}}this.line_height = line_height;if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED){
            height = ypos + line_height;}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){if(ypos + line_height < height){
                height = ypos + line_height;}}
        setMeasuredDimension(width, height);}@OverrideprotectedLayoutParams generateDefaultLayoutParams(){returnnewLayoutParams(1,1);// default of 1px spacing}@Overrideprotectedboolean checkLayoutParams(LayoutParams p){return(p instanceofLayoutParams);}@Overrideprotectedvoid onLayout(boolean changed,int l,int t,int r,int b){finalint count = getChildCount();finalint width = r - l;int xpos = getPaddingLeft();int ypos = getPaddingTop();for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalint childw = child.getMeasuredWidth();finalint childh = child.getMeasuredHeight();finalLayoutParams lp =(LayoutParams) child.getLayoutParams();if(xpos + childw > width){
                    xpos = getPaddingLeft();
                    ypos += line_height;}
                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.width;}}}}

With the result:

Wrapped widgets

shareimprove this answer
 
   
Looks painful. I'm coming to you for programmatic layout questions.... –  Will Feb 19 '09 at 0:12
5  
What license do you want this code snippet to have? I'd like to use it in a project (and have fixed the LayoutParam.WRAP_CONTENT problem here: staticfree.info/clip/2009-10-20T132442 ) –  Steve PomeroyOct 20 '09 at 17:25
6  
For some reason I'm getting "ClassCastException android.view.view cannot be cast to android.view.viewgroup" when trying to inflate the view in the Android layout editor. Any idea on how to fix this? –  twig Dec 18 '11 at 2:26
1  
For those who come by this answer - onMeasure() contains some bad logic and won't work as expected. Imagine that this ViewGroup's parent sends a measure request where it doesn't care about height, it will send 0 as height and 0 as measuring mode. PredicateLayout will get 0 as it's own height and will force children to be AT_MOST 0 height. –  devmiles.com Jul 17 '12 at 12:08 
1  
Instead of adding the comment in the code, couldn't you just correct the error? :) –  Henrik Gustafsson Jul 17 '12 at 13:25

I implemented something very similar to this, but using what I think is a little more standard way to handle spacing and padding. Please let me know what you guys think, and feel free to reuse without attribution:

package com.asolutions.widget;import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import com.asolutions.widget.R;publicclassRowLayoutextendsViewGroup{publicstaticfinalint DEFAULT_HORIZONTAL_SPACING =5;publicstaticfinalint DEFAULT_VERTICAL_SPACING =5;privatefinalint horizontalSpacing;privatefinalint verticalSpacing;privateList<RowMeasurement> currentRows =Collections.emptyList();publicRowLayout(Context context,AttributeSet attrs){super(context, attrs);TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.RowLayout);
        horizontalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_horizontalSpacing,
                DEFAULT_HORIZONTAL_SPACING);
        verticalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_verticalSpacing,
                DEFAULT_VERTICAL_SPACING);
        styledAttributes.recycle();}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){finalint widthMode =MeasureSpec.getMode(widthMeasureSpec);finalint heightMode =MeasureSpec.getMode(heightMeasureSpec);finalint maxInternalWidth =MeasureSpec.getSize(widthMeasureSpec)- getHorizontalPadding();finalint maxInternalHeight =MeasureSpec.getSize(heightMeasureSpec)- getVerticalPadding();List<RowMeasurement> rows =newArrayList<RowMeasurement>();RowMeasurement currentRow =newRowMeasurement(maxInternalWidth, widthMode);
        rows.add(currentRow);for(View child : getLayoutChildren()){LayoutParams childLayoutParams = child.getLayoutParams();int childWidthSpec = createChildMeasureSpec(childLayoutParams.width, maxInternalWidth, widthMode);int childHeightSpec = createChildMeasureSpec(childLayoutParams.height, maxInternalHeight, heightMode);
            child.measure(childWidthSpec, childHeightSpec);int childWidth = child.getMeasuredWidth();int childHeight = child.getMeasuredHeight();if(currentRow.wouldExceedMax(childWidth)){
                currentRow =newRowMeasurement(maxInternalWidth, widthMode);
                rows.add(currentRow);}
            currentRow.addChildDimensions(childWidth, childHeight);}int longestRowWidth =0;int totalRowHeight =0;for(int index =0; index < rows.size(); index++){RowMeasurement row = rows.get(index);
            totalRowHeight += row.getHeight();if(index < rows.size()-1){
                totalRowHeight += verticalSpacing;}
            longestRowWidth =Math.max(longestRowWidth, row.getWidth());}
        setMeasuredDimension(widthMode ==MeasureSpec.EXACTLY ?MeasureSpec.getSize(widthMeasureSpec): longestRowWidth
                + getHorizontalPadding(), heightMode ==MeasureSpec.EXACTLY ?MeasureSpec.getSize(heightMeasureSpec): totalRowHeight + getVerticalPadding());
        currentRows =Collections.unmodifiableList(rows);}privateint createChildMeasureSpec(int childLayoutParam,int max,int parentMode){int spec;if(childLayoutParam ==LayoutParams.FILL_PARENT){
            spec =MeasureSpec.makeMeasureSpec(max,MeasureSpec.EXACTLY);}elseif(childLayoutParam ==LayoutParams.WRAP_CONTENT){
            spec =MeasureSpec.makeMeasureSpec(max, parentMode ==MeasureSpec.UNSPECIFIED ?MeasureSpec.UNSPECIFIED
                    :MeasureSpec.AT_MOST);}else{
            spec =MeasureSpec.makeMeasureSpec(childLayoutParam,MeasureSpec.EXACTLY);}return spec;}@Overrideprotectedvoid onLayout(boolean changed,int leftPosition,int topPosition,int rightPosition,int bottomPosition){finalint widthOffset = getMeasuredWidth()- getPaddingRight();int x = getPaddingLeft();int y = getPaddingTop();Iterator<RowMeasurement> rowIterator = currentRows.iterator();RowMeasurement currentRow = rowIterator.next();for(View child : getLayoutChildren()){finalint childWidth = child.getMeasuredWidth();finalint childHeight = child.getMeasuredHeight();if(x + childWidth > widthOffset){
                x = getPaddingLeft();
                y += currentRow.height + verticalSpacing;if(rowIterator.hasNext()){
                    currentRow = rowIterator.next();}}
            child.layout(x, y, x + childWidth, y + childHeight);
            x += childWidth + horizontalSpacing;}}privateList<View> getLayoutChildren(){List<View> children =newArrayList<View>();for(int index =0; index < getChildCount(); index++){View child = getChildAt(index);if(child.getVisibility()!=View.GONE){
                children.add(child);}}return children;}protectedint getVerticalPadding(){return getPaddingTop()+ getPaddingBottom();}protectedint getHorizontalPadding(){return getPaddingLeft()+ getPaddingRight();}privatefinalclassRowMeasurement{privatefinalint maxWidth;privatefinalint widthMode;privateint width;privateint height;publicRowMeasurement(int maxWidth,int widthMode){this.maxWidth = maxWidth;this.widthMode = widthMode;}publicint getHeight(){return height;}publicint getWidth(){return width;}publicboolean wouldExceedMax(int childWidth){return widthMode ==MeasureSpec.UNSPECIFIED ?false: getNewWidth(childWidth)> maxWidth;}publicvoid addChildDimensions(int childWidth,int childHeight){
            width = getNewWidth(childWidth);
            height =Math.max(height, childHeight);}privateint getNewWidth(int childWidth){return width ==0? childWidth : width + horizontalSpacing + childWidth;}}}

This also requires an entry under /res/values/attrs.xml, which you can create if it's not already there.

<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="RowLayout"><attr name="android:verticalSpacing"/><attr name="android:horizontalSpacing"/></declare-styleable></resources>

Usage in an xml layout looks like this:

<?xml version="1.0" encoding="utf-8"?><com.asolutions.widget.RowLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:padding="10dp"
    android:horizontalSpacing="10dp"
    android:verticalSpacing="20dp"><FrameLayout android:layout_width="30px" android:layout_height="40px" android:background="#F00"/><FrameLayout android:layout_width="60px" android:layout_height="40px" android:background="#F00"/><FrameLayout android:layout_width="70px" android:layout_height="20px" android:background="#F00"/><FrameLayout android:layout_width="20px" android:layout_height="60px" android:background="#F00"/><FrameLayout android:layout_width="10px" android:layout_height="40px" android:background="#F00"/><FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/><FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/><FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/></com.asolutions.widget.RowLayout>
shareimprove this answer
 
   
I am using this. I am adding items dynamically to that layout. It gets added but every view is way to small say tiny. why? –  Neutralizer Sep 16 '11 at 19:24
2  
This is working perfectly for me, declaring the layout and children in XML. Thanks! –  David Caunt Feb 7 '12 at 23:24
2  
Works for me as well. Nice job Micah! Thanks for sharing this solution! –  mthama Jun 11 '12 at 19:14
   
Among all the solutions I tried, this one was the only one that didn't throuw a ClassCastException when using it from a xml layout file. –  mdelolmo Jul 5 '12 at 7:30
   
Great job! Works well! –  devmiles.com Jul 17 '12 at 12:59

Here is my simplified, code-only version:

package com.superliminal.android.util;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/**
     * A view container with layout behavior like that of the Swing FlowLayout.
     * Originally from http://nishantvnair.wordpress.com/2010/09/28/flowlayout-in-android/ itself derived from
     * http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
     *
     * @author Melinda Green
     */publicclassFlowLayoutextendsViewGroup{privatefinalstaticint PAD_H =2, PAD_V =2;// Space between child views.privateint mHeight;publicFlowLayout(Context context){super(context);}publicFlowLayout(Context context,AttributeSet attrs){super(context, attrs);}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){assert(MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED);finalint width =MeasureSpec.getSize(widthMeasureSpec)- getPaddingLeft()- getPaddingRight();int height =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint count = getChildCount();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);
            mHeight =0;for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){
                    child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST), childHeightMeasureSpec);finalint childw = child.getMeasuredWidth();
                    mHeight =Math.max(mHeight, child.getMeasuredHeight()+ PAD_V);if(xpos + childw > width){
                        xpos = getPaddingLeft();
                        ypos += mHeight;}
                    xpos += childw + PAD_H;}}if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED){
                height = ypos + mHeight;}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){if(ypos + mHeight < height){
                    height = ypos + mHeight;}}
            height +=5;// Fudge to avoid clipping bottom of last row.
            setMeasuredDimension(width, height);}// end onMeasure()@Overrideprotectedvoid onLayout(boolean changed,int l,int t,int r,int b){finalint width = r - l;int xpos = getPaddingLeft();int ypos = getPaddingTop();for(int i =0; i < getChildCount(); i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalint childw = child.getMeasuredWidth();finalint childh = child.getMeasuredHeight();if(xpos + childw > width){
                        xpos = getPaddingLeft();
                        ypos += mHeight;}
                    child.layout(xpos, ypos, xpos + childw, ypos + childh);
                    xpos += childw + PAD_H;}}}// end onLayout()}
shareimprove this answer
 
   
I added a view on the layout and give the class that name, and then added child buttons to that view. But the height of them i cannot control? they all look to have a padding top/bottom that i dont want. –  Miguel Aug 8 '14 at 15:47 
   
You can set PAD_V = 0, or add constructor arguments to customize padding. –  Melinda Green Aug 8 '14 at 22:51

There is a problem with the first Answer:

child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(height,MeasureSpec.AT_MOST));

In a ListView, for example, the list items are passed a heightMeasureSpec of 0 (UNSPECIFIED) and so, here, the MeasureSpec of size 0 (AT_MOST) is passed to all of the children. This means that the whole PredicateLayout is invisible (height 0).

As a quick fix, I changed the child height MeasureSpec like this:

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

and then

child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST), 
    childHeightMeasureSpec);

which seems to work for me although does not handle EXACT mode which would be much more tricky.

shareimprove this answer
 
2  
This also fixes the issue when the layout is put in a scrollview. Thanks for solving this one so I didn't have to work it out for myself. –  skorulis Apr 13 '11 at 9:58

The android-flowlayout project by ApmeM support line breaks, too.

enter image description here

shareimprove this answer
 
   
worked perfectly –  user2758776 Dec 18 '13 at 0:03

I have updated this sample to fix a bug, now, each line can have a different height !

import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/**
 * ViewGroup that arranges child views in a similar way to text, with them laid
 * out one line at a time and "wrapping" to the next line as needed.
 * 
 * Code licensed under CC-by-SA
 *  
 * @author Henrik Gustafsson
 * @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
 * @license http://creativecommons.org/licenses/by-sa/2.5/
 *
 * Updated by Aurélien Guillard
 * Each line can have a different height
 * 
 */publicclassFlowLayoutextendsViewGroup{publicstaticclassLayoutParamsextendsViewGroup.LayoutParams{publicfinalint horizontal_spacing;publicfinalint vertical_spacing;/**
         * @param horizontal_spacing Pixels between items, horizontally
         * @param vertical_spacing Pixels between items, vertically
         */publicLayoutParams(int horizontal_spacing,int vertical_spacing){super(0,0);this.horizontal_spacing = horizontal_spacing;this.vertical_spacing = vertical_spacing;}}publicFlowLayout(Context context){super(context);}publicFlowLayout(Context context,AttributeSet attrs){super(context, attrs);}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){assert(MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED);finalint width =MeasureSpec.getSize(widthMeasureSpec)- getPaddingLeft()- getPaddingRight();int height =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint 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);}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.EXACTLY){
            childHeightMeasureSpec =MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);}else{
            childHeightMeasureSpec =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalLayoutParams lp =(LayoutParams) child.getLayoutParams();
                child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST), childHeightMeasureSpec);finalint childw = child.getMeasuredWidth();if(xpos + childw > width){
                    xpos = getPaddingLeft();
                    ypos += line_height;}

                xpos += childw + lp.horizontal_spacing;
                line_height = child.getMeasuredHeight()+ lp.vertical_spacing;}}if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED){
            height = ypos + line_height;}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){if(ypos + line_height < height){
                height = ypos + line_height;}}
        setMeasuredDimension(width, height);}@OverrideprotectedViewGroup.LayoutParams generateDefaultLayoutParams(){returnnewLayoutParams(1,1);// default of 1px spacing}@Overrideprotectedboolean checkLayoutParams(ViewGroup.LayoutParams p){if(p instanceofLayoutParams){returntrue;}returnfalse;}@Overrideprotectedvoid onLayout(boolean changed,int l,int t,int r,int b){finalint count = getChildCount();finalint width = r - l;int xpos = getPaddingLeft();int ypos = getPaddingTop();int lineHeight =0;for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalint childw = child.getMeasuredWidth();finalint childh = child.getMeasuredHeight();finalLayoutParams lp =(LayoutParams) child.getLayoutParams();if(xpos + childw > width){
                    xpos = getPaddingLeft();
                    ypos += lineHeight;}

                lineHeight = child.getMeasuredHeight()+ lp.vertical_spacing;

                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.horizontal_spacing;}}}}
shareimprove this answer
 
   
This works Good. –  Miguel Aug 7 '14 at 20:28

My slightly modified version based on posted here previously:

  • It works in Eclipse layout editor
  • It centers all the items horizontally

    import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;publicclassFlowLayoutextendsViewGroup{privateint line_height;publicstaticclassLayoutParamsextendsViewGroup.LayoutParams{publicfinalint horizontal_spacing;publicfinalint vertical_spacing;/**
         * @param horizontal_spacing Pixels between items, horizontally
         * @param vertical_spacing Pixels between items, vertically
         */publicLayoutParams(int horizontal_spacing,int vertical_spacing,ViewGroup.LayoutParams viewGroupLayout){super(viewGroupLayout);this.horizontal_spacing = horizontal_spacing;this.vertical_spacing = vertical_spacing;}/**
         * @param horizontal_spacing Pixels between items, horizontally
         * @param vertical_spacing Pixels between items, vertically
         */publicLayoutParams(int horizontal_spacing,int vertical_spacing){super(0,0);this.horizontal_spacing = horizontal_spacing;this.vertical_spacing = vertical_spacing;}}publicFlowLayout(Context context){super(context);}publicFlowLayout(Context context,AttributeSet attrs){super(context, attrs);}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){assert(MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED);finalint width =MeasureSpec.getSize(widthMeasureSpec)- getPaddingLeft()- getPaddingRight();int height =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint 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++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalLayoutParams lp =(LayoutParams) child.getLayoutParams();
                child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST), childHeightMeasureSpec);finalint 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;}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){if(ypos + line_height < height){
                height = ypos + line_height;}}
        setMeasuredDimension(width, height);}@OverrideprotectedViewGroup.LayoutParams generateDefaultLayoutParams(){returnnewLayoutParams(1,1);// default of 1px spacing}@Overrideprotected android.view.ViewGroup.LayoutParams generateLayoutParams(
            android.view.ViewGroup.LayoutParams p){returnnewLayoutParams(1,1, p);}@Overrideprotectedboolean checkLayoutParams(ViewGroup.LayoutParams p){if(p instanceofLayoutParams){returntrue;}returnfalse;}@Overrideprotectedvoid onLayout(boolean changed,int l,int t,int r,int b){finalint count = getChildCount();finalint width = r - l;int xpos = getPaddingLeft();int ypos = getPaddingTop();int lastHorizontalSpacing =0;int rowStartIdx =0;for(int i =0; i < count; i++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalint childw = child.getMeasuredWidth();finalint childh = child.getMeasuredHeight();finalLayoutParams lp =(LayoutParams) child.getLayoutParams();if(xpos + childw > width){finalint freeSpace = width - xpos + lastHorizontalSpacing;
                    xpos = getPaddingLeft()+ freeSpace /2;for(int j = rowStartIdx; j < i;++j){finalView drawChild = getChildAt(j);
                        drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight());
                        xpos += drawChild.getMeasuredWidth()+((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing;}
    
                    lastHorizontalSpacing =0;
                    xpos = getPaddingLeft();
                    ypos += line_height;
                    rowStartIdx = i;} 
    
                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.horizontal_spacing;
                lastHorizontalSpacing = lp.horizontal_spacing;}}if(rowStartIdx < count){finalint freeSpace = width - xpos + lastHorizontalSpacing;
            xpos = getPaddingLeft()+ freeSpace /2;for(int j = rowStartIdx; j < count;++j){finalView drawChild = getChildAt(j);
                drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight());
                xpos += drawChild.getMeasuredWidth()+((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing;}}}}
shareimprove this answer
 

Some of the different answers here would give me a ClassCastException in the Exclipse layout editor. In my case, I wanted to use ViewGroup.MarginLayoutParams rather than making my own. Either way, make sure to return the instance of LayoutParams that your custom layout class needs in generateLayoutParams. This is what mine looks like, just replace MarginLayoutParams with the one your ViewGroup needs:

@OverridepublicLayoutParams generateLayoutParams(AttributeSet attrs){returnnewMarginLayoutParams(getContext(), attrs);}@Overrideprotectedboolean checkLayoutParams(ViewGroup.LayoutParams p){return p instanceofMarginLayoutParams;}

It looks like this method gets called to to assign a LayoutParams object for each child in the ViewGroup.

shareimprove this answer
 
LinearLayout row =newLinearLayout(this);//get the size of the screenDisplay display = getWindowManager().getDefaultDisplay();this.screenWidth = display.getWidth();// deprecatedfor(int i=0; i<this.users.length; i++){

        row.setLayoutParams(newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));this.tag_button =newButton(this);this.tag_button.setLayoutParams(newLayoutParams(LayoutParams.WRAP_CONTENT,70));//get the size of the button textPaint mPaint =newPaint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(tag_button.getTextSize());
        mPaint.setTypeface(Typeface.create(Typeface.SERIF,Typeface.NORMAL));float size = mPaint.measureText(tag_button.getText().toString(),0, tag_button.getText().toString().length());
        size = size+28;this.totalTextWidth += size;if(totalTextWidth < screenWidth){
            row.addView(tag_button);}else{this.tags.addView(row);
            row =newLinearLayout(this);
            row.setLayoutParams(newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
            row.addView(tag_button);this.totalTextWidth = size;}}
shareimprove this answer
 

I adapted some of the ode above and implemented a flow layout which centers all child views, horizontal and vertical. It fits my needs.

publicclassCenteredFlowLayoutextendsViewGroup{privateint lineHeight;privateint centricHeightPadding;privatefinalint halfGap;privateboolean disableCenterVertical =false;publicstaticfinalList<View> LINE_CHILDREN =newArrayList<View>();publicstaticclassLayoutParamsextendsViewGroup.LayoutParams{publicfinalint horizontalSpacing;publicfinalint verticalSpacing;publicLayoutParams(int horizontalSpacing,int verticalSpacing){super(0,0);this.horizontalSpacing = horizontalSpacing;this.verticalSpacing = verticalSpacing;}}publicCenteredFlowLayout(Context context){super(context);
    halfGap = getResources().getDimensionPixelSize(R.dimen.half_gap);}@Overrideprotectedvoid onMeasure(int widthMeasureSpec,int heightMeasureSpec){finalint width =MeasureSpec.getSize(widthMeasureSpec)- getPaddingLeft()- getPaddingRight();int height =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint maxHeight =MeasureSpec.getSize(heightMeasureSpec)- getPaddingTop()- getPaddingBottom();finalint count = getChildCount();int lineHeight =0;int xAxis = getPaddingLeft();int yAxis = 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++){finalView child = getChildAt(i);if(child.getVisibility()!= GONE){finalCentricFlowLayout.LayoutParams lp =(LayoutParams) child.getLayoutParams();
        child.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.AT_MOST), childHeightMeasureSpec);finalint childMeasuredWidth = child.getMeasuredWidth();
        lineHeight =Math.max(lineHeight, child.getMeasuredHeight()+ lp.verticalSpacing);if(xAxis + childMeasuredWidth > width){
          xAxis = getPaddingLeft();
          yAxis += lineHeight;}elseif(i +1== count){
          yAxis += lineHeight;}

        xAxis += childMeasuredWidth + lp.horizontalSpacing;}}this.lineHeight = lineHeight;if(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED){
      height = yAxis + lineHeight;}elseif(MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST){if(yAxis + lineHeight < height){
        height = yAxis + lineHeight;}}if(maxHeight ==0){
      maxHeight = height + getPaddingTop();}if(disableCenterVertical){
      centricHeightPadding =0;}else{
      centricHeightPadding =(maxHeight - height)/2;}
    setMeasuredDimension(width, disableCenterVertical ? height + getPaddingTop(): maxHeight);}@OverrideprotectedCentricFlowLayout.LayoutParams generateDefaultLayoutParams(){returnnewCentricFlowLayout.LayoutParams(halfGap, halfGap);}@Overrideprotectedboolean checkLayoutParams(ViewGroup.LayoutParams p){if(p instanceofLayoutParams){returntrue;}returnfalse;}@Overrideprotectedvoid onLayout(boolean changed,int l,int t,int r,int b){finalint count = getChildCount();finalint width = r - l;int yAxis = centricHeightPadding + getPaddingTop()+ getPaddingBottom();View child;int measuredWidth;int lineWidth = getPaddingLeft()+ getPaddingRight();CentricFlowLayout.LayoutParams lp;int offset;
    LINE_CHILDREN.clear();for(int i =0; i < count; i++){
      child = getChildAt(i);
      lp =(LayoutParams) child.getLayoutParams();if(GONE != child.getVisibility()){
        measuredWidth = child.getMeasuredWidth();if(lineWidth + measuredWidth + lp.horizontalSpacing > width){
          offset =(width - lineWidth)/2;
          layoutHorizontalCentricLine(LINE_CHILDREN, offset, yAxis);
          lineWidth = getPaddingLeft()+ getPaddingRight()+ measuredWidth + lp.horizontalSpacing;
          yAxis += lineHeight;
          LINE_CHILDREN.clear();
          LINE_CHILDREN.add(child);}else{
          lineWidth += measuredWidth + lp.horizontalSpacing;
          LINE_CHILDREN.add(child);}}}
    offset =(width - lineWidth)/2;
    layoutHorizontalCentricLine(LINE_CHILDREN, offset, yAxis);}privatevoid layoutHorizontalCentricLine(finalList<View> children,finalint offset,finalint yAxis){int xAxis = getPaddingLeft()+ getPaddingRight()+ offset;for(View child : children){finalint measuredWidth = child.getMeasuredWidth();finalint measuredHeight = child.getMeasuredHeight();finalCentricFlowLayout.LayoutParams lp =(LayoutParams) child.getLayoutParams();
      child.layout(xAxis, yAxis, xAxis + measuredWidth, yAxis + measuredHeight);
      xAxis += measuredWidth + lp.horizontalSpacing;}}publicvoid disableCenterVertical(){
    disableCenterVertical =true;
    requestLayout();}}
shareimprove this answer
 
   
I edited this post. In a previous version there was a bug regarding the vertical alignment of the view. – Thomas R. Jun 24 '14 at 15:44

Try setting both of lp's LayoutParams to be WRAP_CONTENT.

Setting mlp to be WRAP_CONTENTWRAP_CONTENT ensures that your TextView(s) t are just wide and tall enough enough to hold "Hello" or whatever String you put in them. I think l may not be aware of how wide your t's are. The setSingleLine(true) may be contributing too.

shareimprove this answer
 
   
As far as I know, LinearLayout does not do any wrapping. WRAP_CONTENT on both axes would make the LinearLayout-widget one line high and extend way off the screen. And from what I understand, the setSingleLine on the text-widget only affects the size and shape of that widget, not its parents layout. – Henrik Gustafsson Feb 18 '09 at 10:22
   
@Will, I think you misunderstood the meaning of WRAP_CONTENT. –  jfritz42 Nov 14 '12 at 18:29

 

posted @ 2015-04-10 10:24  清澈见底  阅读(300)  评论(0编辑  收藏  举报