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:
public class WrapTest extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout l = new LinearLayout(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams mlp = new LinearLayout.LayoutParams( new ViewGroup.MarginLayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); mlp.setMargins(0, 0, 2, 0); for (int i = 0; i < 10; i++) { TextView t = new TextView(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.
Is there such a layout or combination of layouts and parameters, or do I have to implement my own ViewGroup for this?
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; public class Predicate extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PredicateLayout l = new PredicateLayout(this); for (int i = 0; i < 10; i++) { TextView t = new TextView(this); t.setText("Hello"); t.setBackgroundColor(Color.RED); t.setSingleLine(true); l.addView(t, new PredicateLayout.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/ * */ public class PredicateLayout extends ViewGroup { private int line_height; public PredicateLayout(Context context) { super(context); } public PredicateLayout(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); // The next line is WRONG!!! Doesn't take into account requested MeasureSpec mode! int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); final int count = getChildCount(); int line_height = 0; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); 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), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); final int 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; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){ if (ypos + line_height < height){ height = ypos + line_height; } } setMeasuredDimension(width, height); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(1, 1); // default of 1px spacing } @Override protected boolean checkLayoutParams(LayoutParams p) { return (p instanceof LayoutParams); } @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.width; } } } }
With the result:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With