I have a GridView that I have filled with 64 60x60px png's. I want the GridView to display them all as close to a perfect square as I can so I have set the numColumns in the XML to 8 so now I have an 8x8 gird.
Here is what it looks like:
My images actually have a small border at the very edge though that is being cropped off. Here I drew on the top left image what they should look like when displayed:
Here is my XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/textFieldFU"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<GridView
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="600dp"
android:numColumns="8"
android:verticalSpacing="10dp"
android:horizontalSpacing="0dp"
android:stretchMode="columnWidth"
android:gravity="center"
/>
</RelativeLayout>
When I was using 40x40px and 50x50px size png's they worked fine, but they were too small to easily see my little symbols. I have changed everything in the XML that I could think of but no matter how much spacing I give or where I give it, the images stay cropped even when there is ample room.
How can I make the GridView display the full, un-cropped images?
For the love of all that is holy, I'm really dumb. I had forgotten that in my "ImageAdapter" class I had set had used the ImageView setLayoutParams method and set them to (50, 50). Sorry for wasting you good peoples time.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv;
if (convertView != null) {
iv = (ImageView) convertView;
} else {
iv = new ImageView(context);
******iv.setLayoutParams(new GridView.LayoutParams(50, 50));******
iv.setScaleType(ScaleType.CENTER);
iv.setPadding(0, 0, 0, 0);
}
iv.setImageResource(images[position]);
return iv;
}
To use flowlayout make a java class called FlowLayout to be a custom control in android.
.../src/FlowLayout.java:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class FlowLayout extends ViewGroup
{
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private int horizontalSpacing = 20;
private int verticalSpacing = 20;
private int orientation = 0;
private int innerPadding = 12;
public FlowLayout(Context context)
{
super(context);
}
public FlowLayout(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
}
public FlowLayout(Context context, AttributeSet attributeSet, int defStyle)
{
super(context, attributeSet, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft();
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft()+innerPadding;
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int size;
int mode;
if (orientation == HORIZONTAL)
{
size = sizeWidth;
mode = modeWidth;
}
else
{
size = sizeHeight;
mode = modeHeight;
}
int lineThicknessWithSpacing = 0;
int lineThickness = 0;
int lineLengthWithSpacing = 0;
int lineLength;
int prevLinePosition = 0;
int controlMaxLength = 0;
int controlMaxThickness = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++)
{
final View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
child.measure
(
MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth),
MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight)
);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int hSpacing = this.getHorizontalSpacing(lp);
int vSpacing = this.getVerticalSpacing(lp);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childLength;
int childThickness;
int spacingLength;
int spacingThickness;
if (orientation == HORIZONTAL)
{
childLength = childWidth;
childThickness = childHeight;
spacingLength = hSpacing;
spacingThickness = vSpacing;
}
else
{
childLength = childHeight;
childThickness = childWidth;
spacingLength = vSpacing;
spacingThickness = hSpacing;
}
lineLength = lineLengthWithSpacing + childLength;
lineLengthWithSpacing = lineLength + spacingLength;
boolean newLine = lp.newLine || (mode != MeasureSpec.UNSPECIFIED && lineLength > size);
if (newLine)
{
prevLinePosition = prevLinePosition + lineThicknessWithSpacing;
lineThickness = childThickness;
lineLength = childLength;
lineThicknessWithSpacing = childThickness + spacingThickness;
lineLengthWithSpacing = lineLength + spacingLength;
}
lineThicknessWithSpacing = Math.max(lineThicknessWithSpacing, childThickness + spacingThickness);
lineThickness = Math.max(lineThickness, childThickness);
int posX;
int posY;
if (orientation == HORIZONTAL)
{
posX = innerPadding + getPaddingLeft() + lineLength - childLength;
posY = getPaddingTop() + prevLinePosition;
}
else
{
posX = getPaddingLeft() + prevLinePosition;
posY = innerPadding + getPaddingTop() + lineLength - childHeight;
}
lp.setPosition(posX, posY);
controlMaxLength = Math.max(controlMaxLength, lineLength);
controlMaxThickness = prevLinePosition + lineThickness;
}
if (orientation == HORIZONTAL)
this.setMeasuredDimension(resolveSize(controlMaxLength, widthMeasureSpec), resolveSize(controlMaxThickness, heightMeasureSpec));
else
this.setMeasuredDimension(resolveSize(controlMaxThickness, widthMeasureSpec), resolveSize(controlMaxLength, heightMeasureSpec));
}
private int getVerticalSpacing(LayoutParams lp)
{
int vSpacing;
if (lp.verticalSpacingSpecified())
vSpacing = lp.verticalSpacing;
else
vSpacing = this.verticalSpacing;
return vSpacing;
}
private int getHorizontalSpacing(LayoutParams lp)
{
int hSpacing;
if (lp.horizontalSpacingSpecified())
hSpacing = lp.horizontalSpacing;
else
hSpacing = this.horizontalSpacing;
return hSpacing;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
final int count = getChildCount();
for (int i = 0; i < count; i++)
{
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
}
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime)
{
return super.drawChild(canvas, child, drawingTime);
}
#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 attributeSet)
{
return new LayoutParams(getContext(), attributeSet);
}
#Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new LayoutParams(p);
}
public static class LayoutParams extends ViewGroup.LayoutParams
{
private static int NO_SPACING = -1;
private int x;
private int y;
private int horizontalSpacing = NO_SPACING;
private int verticalSpacing = NO_SPACING;
private boolean newLine = false;
public LayoutParams(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
this.readStyleParameters(context, attributeSet);
}
public LayoutParams(int width, int height)
{
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams layoutParams)
{
super(layoutParams);
}
public boolean horizontalSpacingSpecified()
{
return horizontalSpacing != NO_SPACING;
}
public boolean verticalSpacingSpecified()
{
return verticalSpacing != NO_SPACING;
}
public void setPosition(int x, int y)
{
this.x = x;
this.y = y;
}
private void readStyleParameters(Context context, AttributeSet attributeSet)
{
TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout_LayoutParams);
try
{
horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_horizontalSpacing, NO_SPACING);
verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_verticalSpacing, NO_SPACING);
newLine = a.getBoolean(R.styleable.FlowLayout_LayoutParams_layout_newLine, false);
}
finally
{
a.recycle();
}
}
}
}
Then you create custom attributes for your views that are going to be inside the flow layout view.
.../res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout_LayoutParams">
<attr name="layout_newLine" format="boolean"/>
<attr name="layout_horizontalSpacing" format="dimension"/>
<attr name="layout_verticalSpacing" format="dimension"/>
</declare-styleable>
</resources>
Then in the xml layout you just add:
<[PATH_TO_CLASS].FlowLayout
xmlns:flowLayout="http://schemas.android.com/apk/res/za.co.lawdata.searchworks"
android:id="#+id/flow_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
flowLayout:layout_verticalSpacing="50dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
flowLayout:layout_newLine="true"
flowLayout:layout_horizontalSpacing="50dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"/>
</[PATH_TO_CLASS].FlowLayout>
And replace [PATH_TO_CLASS] with your package path eg: com.example.appname
flowLayout:layout_verticalSpacing="50dp" will set the vertical space between the item.
The default is set in the java class.
flowLayout:layout_horizontalSpacing="50dp" will set the horizontal space between the item.
The default is set in the java class.
flowLayout:layout_newLine="true" will put the item on a new line.
This is an edit from this git: https://github.com/ApmeM/android-flowlayout
Related
For some reason my custom viewgroup is not showing all children when i add weight to my Horizontalscrollview but it works perfect when i removed the weight here is my xml. Does anyone have any idea what is going on?
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="7"
android:orientation="vertical">
</LinearLayout>
<HorizontalScrollView
android:id="#+id/handScrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_weight="2"
android:background="#color/colorAccent">
<com.example.example.cardgame.bin.template.HandViewLayout
android:id="#+id/handView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/black_grey">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/cardview_light_background"
android:text="View1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/cardview_dark_background"
android:text="View2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/face_down"
android:text="View3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/colorAccent"
android:text="View4" />
</com.example.example.cardgame.bin.template.HandViewLayout>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
However when i set my Horizontal scrollview to wrap_content or match_parent, it works as desired, why is it not working when i set weight attribute?
Here is my Class HandViewLayout
public class HandViewLayout extends ViewGroup {
private int mLeftWidth;
public float coverCard = 0;
private int mRightWidth;
private final Rect mTmpContainerRect = new Rect();
private final Rect mTmpChildRect = new Rect();
public OnClickListenerViewGroup onClickListenerViewGroup;
public HandViewLayout(Context context) {
super(context);
setHorizontalScrollBarEnabled(true);
setVerticalScrollBarEnabled(true);
}
public void setCoverCard(float coverCard) {
this.coverCard = -Math.abs(coverCard);
}
public float getCoverCard() {
return this.coverCard;
}
public HandViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HandViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setOnClickListenerViewGroup(OnClickListenerViewGroup onClickListenerViewGroup) {
this.onClickListenerViewGroup = onClickListenerViewGroup;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
// These are the far left and right edges in which we are performing layout.
int leftPos = getPaddingLeft();
int rightPos = r - l - getPaddingRight();
// This is the middle region inside of the gutter.
final int middleLeft = leftPos + mLeftWidth;
final int middleRight = rightPos - mRightWidth;
// These are the top and bottom edges in which we are performing layout.
final int parentTop = getPaddingTop();
final int parentBottom = b - t - getPaddingBottom();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
final int index = i;
child.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
onClickListenerViewGroup.onClickListenerViewGroup(index);
}
});
if (i > 0) {
final View previousChild = getChildAt(i - 1);
mTmpContainerRect.left = middleLeft + lp.leftMargin + previousChild.getLeft() + previousChild.getWidth() + (int) coverCard;
mTmpContainerRect.right = middleRight - lp.rightMargin;
mTmpContainerRect.top = parentTop + lp.topMargin;
mTmpContainerRect.bottom = parentBottom - lp.bottomMargin;
} else {
mTmpContainerRect.left = middleLeft + lp.leftMargin;
mTmpContainerRect.right = middleRight - lp.rightMargin;
mTmpContainerRect.top = parentTop + lp.topMargin;
mTmpContainerRect.bottom = parentBottom - lp.bottomMargin;
}
// Use the child's gravity and size to determine its final
// frame within its container.
Gravity.apply(lp.gravity, width, height, mTmpContainerRect, mTmpChildRect);
// Place the child.
child.layout(mTmpChildRect.left, mTmpChildRect.top,
mTmpChildRect.right, mTmpChildRect.bottom);
}
}
#Override
protected boolean awakenScrollBars() {
return true;
}
#Override
protected int computeVerticalScrollExtent() {
return super.computeVerticalScrollExtent();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
mLeftWidth = 0;
mRightWidth = 0;
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// Log.e("child Text", i + ": " + ((TextView) child).getText().toString());
if (child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// last one show all
if (i == getChildCount() - 1) {
maxWidth += child.getMeasuredWidth();
} else {
maxWidth += child.getMeasuredWidth() + coverCard;
}
Log.d("Spacer", "spacer, " + i + "," + maxWidth);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}
setMeasuredDimension(resolveSizeAndState(maxWidth, heightMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState));
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new HandViewLayout.LayoutParams(getContext(), attrs);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
public static class LayoutParams extends MarginLayoutParams {
public int gravity = Gravity.TOP | Gravity.START;
public static int POSITION_MIDDLE = 0;
public static int POSITION_LEFT = 1;
public static int POSITION_RIGHT = 2;
public int position = POSITION_MIDDLE;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray atts = c.obtainStyledAttributes(attrs, R.styleable.HandViewLayout);
gravity = atts.getInt(R.styleable.HandViewLayout_android_layout_gravity, gravity);
position = atts.getInt(R.styleable.HandViewLayout_layout_position, position);
atts.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
}
Attributes xml
<declare-styleable name="HandViewLayout">
<attr name="layoutColor" format="color" />
<attr name="android:layout_gravity" />
<attr name="layout_position">
<enum name="middle" value="0" />
<enum name="left" value="1" />
<enum name="right" value="2" />
</attr>
</declare-styleable>
PIC
I need to create a basic Tag Cloud. My aim is to add multiple TextView(Tags) that automatically fits themselves in a new line if the number exceeds the device width. (Like Instagram).
Right now when the width of the layout containing (TextView)tags exceeds the (device width) the TextView(s) at the end wraps itself.
I tried using RelativeLayout but i am unable to figure out the logic.
I viewed this post but if there is a better or simply a cleaner solution.
Thanks for your precious time.
It is not necessary that the result looks like Instagram.
The cleaner solution is to write your own custom ViewGroup class. Find the sample below.
For complete descriptive explanation visit, How to Create Custom Layout in Android by Extending ViewGroup Class.
public class TagLayout extends ViewGroup {
public TagLayout(Context context) {
super(context);
}
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
int curWidth, curHeight, curLeft, curTop, maxHeight;
//get the available size of child view
final int childLeft = this.getPaddingLeft();
final int childTop = this.getPaddingTop();
final int childRight = this.getMeasuredWidth() - this.getPaddingRight();
final int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();
final int childWidth = childRight - childLeft;
final int childHeight = childBottom - childTop;
maxHeight = 0;
curLeft = childLeft;
curTop = childTop;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
return;
//Get the maximum size of the child
child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
curWidth = child.getMeasuredWidth();
curHeight = child.getMeasuredHeight();
//wrap is reach to the end
if (curLeft + curWidth >= childRight) {
curLeft = childLeft;
curTop += maxHeight;
maxHeight = 0;
}
//do the layout
child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight);
//store the max height
if (maxHeight < curHeight)
maxHeight = curHeight;
curLeft += curWidth;
}
}
}
To use the TagLayout, you can add it to your activity/fragment layout declaration.
main_activity.xml
<com.javatechig.taglayout.TagLayout
android:id="#+id/tagLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Now we can have a custom view to allow some level of customization for each tag item layout.
tag_layout.xml
<TextView
android:id="#+id/tagTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="#a000"
android:padding="10dp"
android:textColor="#fff" />
MainActivity.java
Finally, from activity class you can add the tag items as follows.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TagLayout tagLayout = (TagLayout) findViewById(R.id.tagLayout);
LayoutInflater layoutInflater = getLayoutInflater();
String tag;
for (int i = 0; i <= 20; i++) {
tag = "#tag" + i;
View tagView = layoutInflater.inflate(R.layout.tag_layout, null, false);
TextView tagTextView = (TextView) tagView.findViewById(R.id.tagTextView);
tagTextView.setText(tag);
tagLayout.addView(tagView);
}
}
}
Result
I believe the solution above always consider the whole screen width, so if the developer sets android:layout_width parameter for example it won't respect its value or even parent's margin and padding values.
I've fixed it as follows:
private int mScreenWidth = 0;
private int mAvailableWidth = -1;
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
Display display = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point deviceSize = new Point();
display.getSize(deviceSize);
mScreenWidth = deviceSize.x;
}
private void calculateAvailableWidth() {
if(getLayoutParams() != null && getLayoutParams().width > 0) {
mAvailableWidth = getLayoutParams().width;
return;
}
mAvailableWidth = mScreenWidth;
ViewGroup parent = this;
while(parent != null) {
mAvailableWidth -= parent.getPaddingLeft() + parent.getPaddingRight();
if(parent.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)parent.getLayoutParams();
mAvailableWidth -= layoutParams.leftMargin + layoutParams.rightMargin;
}
if(parent.getParent() instanceof ViewGroup)
parent = (ViewGroup)parent.getParent();
else
parent = null;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int currentRowWidth = 0;
int currentRowHeight = 0;
int maxItemWidth = 0;
int maxWidth = 0;
int maxHeight = 0;
if(mAvailableWidth == -1)
calculateAvailableWidth();
for(int i = 0; i < count; i++) {
View child = getChildAt(i);
if(child.getVisibility() == GONE)
continue;
try {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
catch(Exception e) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
int childWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
int childHeight = child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom();
maxItemWidth = Math.max(maxItemWidth, childWidth);
if(currentRowWidth + childWidth < mAvailableWidth) {
currentRowWidth += childWidth;
maxWidth = Math.max(maxWidth, currentRowWidth);
currentRowHeight = Math.max(currentRowHeight, childHeight);
}
else {
currentRowWidth = childWidth;
maxHeight += currentRowHeight;
}
}
if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
mAvailableWidth = maxItemWidth;
maxWidth = maxItemWidth;
}
maxHeight += currentRowHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(maxWidth, maxHeight);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
int currentLeft = getPaddingLeft();
int currentTop = getPaddingTop();
int currentRight;
int currentBottom;
int parentWidth = this.getPaddingRight() - this.getPaddingLeft() + right;
for(int i = 0; i < count; i++) {
View child = getChildAt(i);
if(child.getVisibility() == View.GONE)
return;
int currentWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
int currentHeight = child.getMeasuredHeight() + child.getPaddingBottom() + child.getPaddingTop();
if(currentLeft + currentWidth > parentWidth) {
currentLeft = getPaddingLeft();
currentTop += currentHeight;
}
currentBottom = currentTop + currentHeight;
currentRight = currentLeft + currentWidth;
child.layout(currentLeft, currentTop, currentRight, currentBottom);
currentLeft += currentWidth;
}
}
This way you are able to set android:layout_width="250dp" and get a result like this:
Set android:layout_width="match_parent" and get a result like this:
Or event use android:layout_width="wrap_content and get a result like this:
Hope it helps.
I need to display two buttons in one row, but if the first button takes full width (if no room) then the second button must be shifted down (not overlap/flattened).
I tried the following code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Buttonttttttttttttttttttttsdfssdfsdfsdf"
android:id="#+id/button"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="#+id/button2"/>
</LinearLayout>
</LinearLayout>
but have the following problem:
Could you please help, how to solve this problem? Maybe using a RelativeLayout?
I had a similar query as yours. And this is the solution that I ended up using.
import java.util.ArrayList;
import java.util.Collection;
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;
public class WritingLayout extends ViewGroup
{
private int[] rowHeights;
public WritingLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int maxInternalWidth = MeasureSpec.getSize(widthMeasureSpec) - getHorizontalPadding();
int maxInternalHeight = MeasureSpec.getSize(heightMeasureSpec) - getVerticalPadding();
List<RowMeasurement> rows = new ArrayList<RowMeasurement>();
RowMeasurement currentRow = null;
for (View child : getLayoutChildren())
{
LayoutParams childLayoutParams = (LayoutParams)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 == null) || currentRow.isWouldExceedMax(childWidth))
{
currentRow = new RowMeasurement(maxInternalWidth, widthMode);
rows.add(currentRow);
}
currentRow.addChildDimensions(childWidth, childHeight);
}
int longestRowWidth = 0;
int totalRowHeight = 0;
int rowCount = rows.size();
this.rowHeights = new int[rowCount];
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
RowMeasurement row = rows.get(rowIndex);
int rowHeight = row.getHeight();
this.rowHeights[rowIndex] = rowHeight;
totalRowHeight = totalRowHeight + rowHeight;
longestRowWidth = Math.max(longestRowWidth, row.getWidth());
}
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? MeasureSpec.getSize(widthMeasureSpec) : (longestRowWidth + getHorizontalPadding()), (heightMode == MeasureSpec.EXACTLY) ? MeasureSpec.getSize(heightMeasureSpec) : (totalRowHeight + getVerticalPadding()));
}
private int createChildMeasureSpec(int childLayoutParam, int max, int parentMode)
{
int spec;
if (childLayoutParam == ViewGroup.LayoutParams.FILL_PARENT)
spec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY);
else if (childLayoutParam == ViewGroup.LayoutParams.WRAP_CONTENT)
spec = MeasureSpec.makeMeasureSpec(max, ((parentMode == MeasureSpec.UNSPECIFIED) ? MeasureSpec.UNSPECIFIED : MeasureSpec.AT_MOST));
else
spec = MeasureSpec.makeMeasureSpec(childLayoutParam, MeasureSpec.EXACTLY);
return spec;
}
#Override
protected LayoutParams generateDefaultLayoutParams()
{
return new LayoutParams();
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams layoutParams)
{
return (layoutParams instanceof LayoutParams);
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new LayoutParams(getContext(), attrs);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new LayoutParams(p);
}
#Override
protected void onLayout(boolean changed, int leftPosition, int topPosition, int rightPosition, int bottomPosition)
{
int widthOffset = getMeasuredWidth() - getPaddingRight();
int x = getPaddingLeft();
int y = getPaddingTop();
int rowIndex = 0;
for (View child : getLayoutChildren())
{
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
LayoutParams layoutParams = (LayoutParams)child.getLayoutParams();
if ((x + childWidth) > widthOffset)
{
x = getPaddingLeft()+rightPosition-childWidth;
y = y + this.rowHeights[rowIndex];
rowIndex = rowIndex + 1;
}
int _y;
if (layoutParams.centerVertical)
_y = y + ((this.rowHeights[rowIndex] - childHeight) / 2);
else
_y = y;
child.layout(x, _y, x + childWidth, _y + childHeight);
x = x + childWidth;
}
}
private Collection<View> getLayoutChildren()
{
int count = getChildCount();
Collection<View> children = new ArrayList<View>(count);
for (int index = 0; index < count; index++)
{
View child = getChildAt(index);
if (child.getVisibility() != View.GONE)
children.add(child);
}
return children;
}
private int getVerticalPadding()
{
return getPaddingTop() + getPaddingBottom();
}
private int getHorizontalPadding()
{
return getPaddingLeft() + getPaddingRight();
}
public static class LayoutParams extends MarginLayoutParams
{
boolean centerVertical;
public LayoutParams()
{
this(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
public LayoutParams(int width, int height)
{
super(width, height);
setCenterVertical(false);
}
public LayoutParams(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.layout_centerVertical });
setCenterVertical(attributes.getBoolean(0, false));
attributes.recycle();
}
public LayoutParams(ViewGroup.LayoutParams source)
{
super(source);
}
public LayoutParams(MarginLayoutParams source)
{
super(source);
}
public LayoutParams(LayoutParams source)
{
this(source.width, source.height);
}
public void setCenterVertical(boolean centerVertical)
{
this.centerVertical = centerVertical;
}
}
private static class RowMeasurement
{
private int maxWidth;
private int widthMode;
private int width;
private int height;
RowMeasurement(int maxWidth, int widthMode)
{
this.maxWidth = maxWidth;
this.widthMode = widthMode;
}
int getWidth()
{
return this.width;
}
int getHeight()
{
return this.height;
}
boolean isWouldExceedMax(int childWidth)
{
return ((this.widthMode != MeasureSpec.UNSPECIFIED) && (getNewWidth(childWidth) > this.maxWidth));
}
void addChildDimensions(int childWidth, int childHeight)
{
this.width = getNewWidth(childWidth);
this.height = Math.max(this.height, childHeight);
}
private int getNewWidth(int childWidth)
{
return ((this.width == 0) ? childWidth : (this.width + childWidth));
}
}
}
Use this WritingLayout instead of the layout that you are using now.
How can I do something like a FlowLayout in Android?
You should use FlexboxLayout with flexWrap="wrap" attribute.
<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>
For build instructions, see the github repo.
implementation 'com.google.android:flexbox:2.0.1'
More about this - https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
I don't have enough reputation to post a comment to Romain Guy's answer but that's where this answer should be (I created an account just to share my edit).
Anyway, I see other people have found out his pretty cool FlowLayout solution has some issues.
I could find one myself and I saw, as others, that some children were clipped.
Looking in details at the algorithm it seems to be a very simple mistake in the calculation of the height. When the very last child is the one being put on a new line, then the height was not properly computed.
I cleaned up a bit the computation (there was a weird use of "height" vs. currentHeight).
The following change fixes the problem of "last child is clipped if on a new line":
#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));
}
There is a library from Google, called "flexbox-layout". You should check it out.
To use it in RecyclerView, you can use something like that:
val layoutManager = FlexboxLayoutManager(activity)
layoutManager.flexDirection = FlexDirection.ROW
layoutManager.flexWrap = FlexWrap.WRAP
layoutManager.justifyContent = JustifyContent.FLEX_START
layoutManager.alignItems = AlignItems.FLEX_START
recyclerView.layoutManager=layoutManager
Here is the custom class where you can achive layout like following with adding dynamicaly view (Also called 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;
}
}
}
}
Example :
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"));
}
}
});
}
}
}
There is now built in support in ConstraintLayout using a Flow widget. It has many options that can be used to achieve many type of flows.
Example:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:constraint_referenced_ids="item_1,item_2,item_3"
app:flow_horizontalBias="0"
app:flow_horizontalGap="10dp"
app:flow_horizontalStyle="packed"
app:flow_verticalGap="8dp"
app:flow_wrapMode="aligned"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/item_1"
android:layout_width="50dp"
android:layout_height="50dp" />
<View
android:id="#+id/item_2"
android:layout_width="50dp"
android:layout_height="50dp" />
<View
android:id="#+id/item_3"
android:layout_width="50dp"
android:layout_height="50dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Take a look in this post:
https://medium.com/#tapanrgohil/constraintlayout-flow-bye-bye-to-linerlayout-78fd7fa9b679
And here:
https://www.bignerdranch.com/blog/constraintlayout-flow-simple-grid-building-without-nested-layouts/
Like one of the previous answers, I started with the solution here: http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html
I extended it to account for varying heights of children as below.
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;
}
}
}
}
I used this as a solution for wrapping multi-line TextEdits. Hope it helps!
A revision to #MattNotEquals() FlowLayout that supports MarginLayoutParams.
This is just a minimalist implementation of MarginLayoutParms to support left, right, top, and bottom margins.
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);
}
}
}
Nice simple self-contained FlowLayout code here (just a few concise gist.github files):
http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html
However, the activity there out of the box didn't work for me to load the custom layout.
I found this work-around [ using the 2-param .inflate() call from this example ]:
#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;
}
Please help! I tried everything! :(
I've got a schedule Class, which is simply a custom ViewGroup (with custom onMeasure() and onLayout()), which enables me to place childs(=events) with LayoutParams for column/row start and column/row end. The number of childs and their LayoutParams depend on database entries.
Now I'm trying to add childs (events) from my database. I'd have to use a Cursor Adapter, so my schedule Class has to extend ListView, right? I tried that but the newView() method of the adapter is never called. Why not??
My custom ListView doesn't ask the adapter for childs, no childs are added. I also can't add the childs by hand calling schedule.addView() if I extend from AdapterView.
I'd be really (really) happy if someone could help!
Regards,
cody
This is my custom ViewGroup:
public class Schedule extends ViewGroup {
private int columns;
private int rows;
private float preferredCellWidth;
private float preferredCellHeight;
private String[] rowTimes;
private Paint paint;
public Schedule(Context context, int columns, int rows, float preferredCellWidth, float preferredCellHeight, String[] rowTimes) {
super(context);
this.columns = columns;
this.rows = rows;
this.preferredCellWidth = preferredCellWidth;
this.preferredCellHeight = preferredCellHeight;
this.rowTimes = rowTimes;
init(context);
}
private void init(Context context) {
Log.i("Schedule", "initSchedule...");
setPaint();
setWillNotDraw(false);
}
private void setPaint() {
paint = new Paint();
paint.setTextSize(preferredCellHeight*2/3);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.white));
}
public Schedule(Context context, AttributeSet attrs) {
super(context, attrs);
readAttr(context, attrs);
init(context);
}
public Schedule(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
readAttr(context, attrs);
init(context);
}
private void readAttr(Context c, AttributeSet attrs) {
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ScheduleLayout);
this.columns = a.getInt(R.styleable.ScheduleLayout_columns, 1);
this.rows = a.getInt(R.styleable.ScheduleLayout_rows, 1);
this.preferredCellWidth = a.getDimension(R.styleable.ScheduleLayout_preferredCellWidth, 1);
this.preferredCellHeight = a.getDimension(R.styleable.ScheduleLayout_preferredCellHeight, 1);
a.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
//Log.i(this.toString(),"onDraw ..."+" this.getLeft()="+this.getLeft()+", this.getWidth()="+this.getWidth());
super.onDraw(canvas);
for (int i = 0; i < rows; i++) {
int line = (int) Math.round(this.getTop()+ (i+1) * preferredCellHeight);
canvas.drawText(this.rowtimes[i], this.getLeft()+5, line-3, paint);
canvas.drawLine(this.getLeft(), line, this.getWidth(), line, paint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("Schedule", "onMeasure...");
float width = (MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight()) / columns;
float height = (MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom()) / rows;
float cellWidth = preferredCellWidth;
float cellHeight = preferredCellHeight;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
cellWidth = width;
} else if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
cellWidth = Math.min(preferredCellWidth, width);
}
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
cellHeight = height;
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
cellHeight = Math.min(preferredCellHeight, height);
}
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cwidth = (int) Math.round(cellWidth * lp.getWidth());
int cheight = (int) Math.round(cellHeight * lp.getHeight());
child.measure(
MeasureSpec.makeMeasureSpec(cwidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(cheight, MeasureSpec.EXACTLY)
);
}
}
setMeasuredDimension(
(int) Math.round(cellWidth * columns + getPaddingLeft() + getPaddingRight()),
(int) Math.round(cellHeight * rows + getPaddingTop() + getPaddingBottom())
);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed)
return;
int cellWidth = ((r-l) - getPaddingLeft() - getPaddingRight()) / columns;
int cellHeight = ((b-t) - getPaddingTop() - getPaddingBottom()) / rows;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cl = (int) Math.round(getPaddingLeft() + lp.columnStart * cellWidth);
int cr = (int) Math.round(getPaddingLeft() + lp.columnEnd * cellWidth);
int ct = (int) Math.round(getPaddingTop() + lp.rowStart * cellHeight);
int cb = (int) Math.round(getPaddingTop() + lp.rowEnd * cellHeight);
child.layout(cl, ct, cr, cb);
}
}
}
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
Log.i("Schedule", "checkLayoutParams...");
if (p instanceof LayoutParams) {
LayoutParams lp = (LayoutParams) p;
if (lp.columnEnd > columns || lp.columnStart < 0)
return false;
if (lp.rowEnd > rows || lp.rowStart < 0)
return false;
return lp.columnEnd > lp.columnStart && lp.rowEnd > lp.rowStart;
} else
return false;
}
public android.widget.AbsListView.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new android.widget.AbsListView.LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends android.view.ViewGroup.LayoutParams {
public int columnStart;
public int columnEnd;
public int rowStart;
public int rowEnd;
public LayoutParams(int columnStart, int rowStart, int columnEnd, int rowEnd) {
super(WRAP_CONTENT, WRAP_CONTENT);
this.columnStart = columnStart;
this.columnEnd = columnEnd;
this.rowStart = rowStart;
this.rowEnd = rowEnd;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(WRAP_CONTENT, WRAP_CONTENT);
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.EventLayout);
this.columnStart = a.getInt(R.styleable.EventLayout_event_columnStart, 0);
this.columnEnd = a.getInt(R.styleable.EventLayout_event_columnEnd, this.columnStart + 1);
this.rowStart = a.getInt(R.styleable.EventLayout_event_rowStart, 0);
this.rowEnd = a.getInt(R.styleable.EventLayout_event_rowEnd, this.rowStart + 1);
a.recycle();
}
public int getWidth() {
return columnEnd - columnStart;
}
public int getHeight() {
return rowEnd - rowStart;
}
}
And this is the event-layout - event.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center" >
<TextView android:id="#+id/text_event_name"
style="#style/Event_TextView1" />
<TextView android:id="#+id/text_event_name2"
style="#style/Event_TextView2" />
</LinearLayout>
<TextView android:id="#+id/text_event_weeks"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
<TextView android:id="#+id/text_event_room"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" />
In my Activity I've got that code:
Schedule schedule = new Schedule(this, 4, rowTimes.length, 15, 15, rowTimes);
Cursor cursor = dbManager.getEvents(day);
MySimpleCurserAdapter adapter = ... ??
// schedule.setAdapter not working...
How can I add events to the schedule with the data from the cursor?
You should not need to be extending ListView. You just want to add an instance of ListView to your layout.
It sounds like you might want to be using a SimpleCursorAdaptor, where you can map items in your custom view to the data model objects you want them to display.
See Binding to Data with Adapter and Hello ListView for some examples of the right ways to use adapters and ListViews.