activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.testlayout.MyViewGroup
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|right"
android:text="Hide" />
</com.testlayout.MyViewGroup>
</RelativeLayout>
PercentFrameLayout.java
public class MyViewGroup extends ViewGroup {
private int xPercent = 0;
private int yPercent = 0;
private int widthPercent = 100;
private int heightPercent = 100;
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private RectF mBorderRect = new RectF();
Paint mBgPaint = new Paint();
public void setPosition(int xPercent, int yPercent, int widthPercent, int heightPercent) {
this.xPercent = xPercent;
this.yPercent = yPercent;
this.widthPercent = widthPercent;
this.heightPercent = heightPercent;
}
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = getDefaultSize(Integer.MAX_VALUE, widthMeasureSpec);
final int height = getDefaultSize(Integer.MAX_VALUE, heightMeasureSpec);
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
final int childWidthMeasureSpec =
MeasureSpec.makeMeasureSpec(width * widthPercent / 100, MeasureSpec.AT_MOST);
final int childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(height * heightPercent / 100, MeasureSpec.AT_MOST);
for (int i = 0; i < getChildCount(); ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = right - left;
final int height = bottom - top;
// Sub-rectangle specified by percentage values.
final int subWidth = width * widthPercent / 100;
final int subHeight = height * heightPercent / 100;
final int subLeft = left + width * xPercent / 100;
final int subTop = top + height * yPercent / 100;
for (int i = 0; i < getChildCount(); ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// Center child both vertically and horizontally.
final int childLeft = subLeft + (subWidth - childWidth) / 2;
final int childTop = subTop + (subHeight - childHeight) / 2;
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
// mBorderRect.left = childLeft-100;
// mBorderRect.top = childTop-100;
// mBorderRect.bottom = childHeight+100;
// mBorderRect.right = childWidth+100;
}
}
}
}
What if you put a RelativeLayout inside your MyViewGroup layout like:
<com.testlayout.MyViewGroup
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</com.testlayout.MyViewGroup>
You just need to set the gravity to the viewgroup
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.testlayout.MyViewGroup
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:gravity="top|right">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|right"
android:text="Hide" />
</com.testlayout.MyViewGroup>
</RelativeLayout>
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 wrote a custom ViewGroup that organizes the subviews in matrix style (equally spaced vertically and horizontally). But I have a problem that is driving me crazy. The viewgroup draw correctly first levels child but them every subviews are invisible.
This is the code of layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:matrix="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.vincejin.timekiller.viewgroups.MatrixLayout
android:id="#+id/matrix"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#efefef"
matrix:ml_columns="2"
matrix:ml_horizontal_space="5dp"
matrix:ml_square="true"
matrix:ml_vertical_space="5dp" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#628465" >
<TextView
android:id="#+id/txv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/text_default" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#763965" >
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#872438" >
</LinearLayout>
</com.vincejin.timekiller.viewgroups.MatrixLayout>
</LinearLayout>
The result is this
http://i40.tinypic.com/256tsw1.png
As you can see, the first layout has a textview inside but is not showed...
Here is the code of the viewgroup
Code:
package com.vincejin.timekiller.viewgroups;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.vincejin.timekiller.R;
public class MatrixLayout extends ViewGroup {
private boolean square;
private int columns ;
private int horizontalSpace ;
private int verticalSpace;
private int cellHeight;
private int cellWidth;
public MatrixLayout(Context context) {
super(context);
}
public MatrixLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.MatrixLayout);
square = arr.getBoolean(R.styleable.MatrixLayout_ml_square,false);
columns = arr.getInteger(R.styleable.MatrixLayout_ml_columns, 4);
horizontalSpace = arr.getDimensionPixelSize(R.styleable.MatrixLayout_ml_horizontal_space, 3);
verticalSpace = arr.getDimensionPixelSize(R.styleable.MatrixLayout_ml_vertical_space, 3);
arr.recycle();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
adjustChildren(l, t, r, b);
}
private void adjustChildren(int l, int t, int r, int b) {
int count = getChildCount();
int gone = 0;
for (int i = 0; i < count; i++) {
View currentChild = getChildAt(i);
if (currentChild.getVisibility() == View.GONE) {
gone++;
return;
}
Rect currentCellPosition = calculatePositionOf(l,t,r,b,coordinatesFromIndex(i
- gone));
currentChild.layout(
currentCellPosition.left,
currentCellPosition.top,
currentCellPosition.right,
currentCellPosition.bottom);
}
}
private Rect calculatePositionOf(int parentLeft, int parentTop, int parentRight, int parentBottom, int[] coordinates) {
int row = coordinates[0];
int col = coordinates[1];
Rect result = new Rect();
result.left = parentLeft + (col * (cellWidth + horizontalSpace));
result.right = result.left + cellWidth;
result.top = parentTop + (row * (cellHeight + verticalSpace));
result.bottom = result.top + cellHeight;
return result;
}
private int[] coordinatesFromIndex(int index) {
int[] result = new int[2];
result[0] = index / columns;
result[1] = index % columns;
return result;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
if (square) {
w = Math.min(w, h);
h = Math.min(w, h);
}
// Cell size calculus
int rows = getRowsCount();
cellWidth = (w / (columns)) ;
if(rows==0)
cellHeight = 0;
else
cellHeight = (h/ rows) ;
setMeasuredDimension(w, h);
}
private int getRowsCount() {
int childCount = getChildCount();
int gone = 0;
for (int i = 0; i < childCount; i++)
if (getChildAt(i).getVisibility() == View.GONE)
gone++;
if((childCount-gone)%columns==0)
return (childCount - gone) / columns;
return ((childCount - gone) / columns) + 1;
}
public boolean isSquare() {
return square;
}
public void setSquare(boolean square) {
this.square = square;
}
public int getColumns() {
return columns;
}
public void setColumns(int columns) {
this.columns = columns;
}
}
Here is the stylable
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MatrixLayout">
<attr name="ml_columns" format="integer" />
<attr name="ml_square" format="boolean" />
<attr name="ml_vertical_space" format="dimension" />
<attr name="ml_horizontal_space" format="dimension" />
</declare-styleable>
</resources>
I solved with the help of pskink .
It was necessary to measure each child in the onMeasure method, so i added the follows rows at the end of the method.
cellWidthMeasure = MeasureSpec.makeMeasureSpec(cellWidth, MeasureSpec.EXACTLY);
cellHeightMeausure = MeasureSpec.makeMeasureSpec(cellHeight, MeasureSpec.EXACTLY);
int childCount = getChildCount();
for(int i=0;i<childCount;i++){
View child= getChildAt(i);
if(child.getVisibility()!=View.GONE)
child.measure(cellWidthMeasure,cellHeightMeausure);
}
I am uncertain of what type of layout to use for this certain scenario.
I basically want to have a horizontal linear layout that i can add views to. in this case buttons (displaying tags in an application) But each view will have a different width bases on the name of the tag it is displaying, so i want to add say 10 tags, I need a layout that will fit as many as it can on the 1st line, and then if it doesn't fit, automatically overflow to the next line.
Basically how a text view works with text, if the text is longer than the width it goes to the next line, except I want to do this with non-clickable buttons.
I thought of a grid layout, but then it would have the same no of "tags" on each line when you could have 2 tags with a long name on the first line and 7 with a short name on the second line.
Something that looks a bit like this:
I basically want the look of how stack overflow does it below here.
Answer: Your own custom Layout :)
I know this is a late answer to this question. But it might help the OP or someone for sure.
You can extend ViewGroup to create a custom layout like this one below. The advantage of this is you get the keep the view hierarchy flat.
MyFlowLayout
public class MyFlowLayout extends ViewGroup {
public MyFlowLayout(Context context) {
super(context);
}
public MyFlowLayout(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
public MyFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int realWidth = MeasureSpec.getSize(widthMeasureSpec);
int currentHeight = 0;
int currentWidth = 0;
int currentChildHookPointx = 0;
int currentChildHookPointy = 0;
int childCount = this.getChildCount();
for(int i = 0; i < childCount; i++) {
View child = getChildAt(i);
this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
//check if child can be placed in the current row, else go to next line
if(currentChildHookPointx + childWidth > realWidth) {
//new line
currentWidth = Math.max(currentWidth, currentChildHookPointx);
//reset for new line
currentChildHookPointx = 0;
currentChildHookPointy += childHeight;
}
int nextChildHookPointx;
int nextChildHookPointy;
nextChildHookPointx = currentChildHookPointx + childWidth;
nextChildHookPointy = currentChildHookPointy;
currentHeight = Math.max(currentHeight, currentChildHookPointy + childHeight);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.x = currentChildHookPointx;
lp.y = currentChildHookPointy;
currentChildHookPointx = nextChildHookPointx;
currentChildHookPointy = nextChildHookPointy;
}
currentWidth = Math.max(currentChildHookPointx, currentWidth);
setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec),
resolveSize(currentHeight, heightMeasureSpec));
}
#Override
protected void onLayout(boolean b, int left, int top, int right, int bottom) {
//call layout on children
int childCount = this.getChildCount();
for(int i = 0; i < childCount; i++) {
View child = this.getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
}
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyFlowLayout.LayoutParams(getContext(), attrs);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new MyFlowLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
#Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new MyFlowLayout.LayoutParams(p);
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MyFlowLayout.LayoutParams;
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
int spacing = -1;
int x = 0;
int y = 0;
LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout);
spacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_layout_space, 0);
t.recycle();
}
LayoutParams(int width, int height) {
super(width, height);
spacing = 0;
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
}
Usage in a layout.xml file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:cl="http://schemas.android.com/apk/res-auto"
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"
tools:context="com.merryapps.customlayout.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<com.merryapps.customlayout.MyFlowLayout
android:id="#+id/flw1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000">
<Button
android:id="#+id/b1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello"
cl:layout_space="20dp"/>
<Button
android:id="#+id/b2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/world"/>
<Button
android:id="#+id/b4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/world"/>
<Button
android:id="#+id/b5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/world"/>
<Button
android:id="#+id/b6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/world"/>
</com.merryapps.customlayout.MyFlowLayout>
<Button
android:id="#+id/b3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/world"
android:textAllCaps="false"/>
</LinearLayout>
For show type of view one must go for flow layout :-
There are many libraries available on Git
Following is the example of,
blazsolar/FlowLayout
Add this line in app.gradle
compile "com.wefika:flowlayout:<version>"
Usage:-
<com.wefika.flowlayout.FlowLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start|top">
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lorem ipsum" />
</com.wefika.flowlayout.FlowLayout>
For detail implementation follow below link-
https://github.com/blazsolar/FlowLayout
You can try this links too:-
https://github.com/xiaofeng-han/AndroidLibs/tree/master/flowlayoutmanager (try this)
https://github.com/ApmeM/android-flowlayout
https://gist.github.com/hzqtc/7940858
Ironically enough I stumbled on a problem when I answered another question
The problem is that if I add a RelativeLayout as a child of my ICGridLayout the children of that RelativeLayout does not get the RelativeLayout.LayoutParams. This goes for all kinds of layouts that I add to my ICGridLayout. I've read through the source code for both LinearLayout, RelativeLayout, AbsoluteLayout and ViewGroup but have not found anything that gives me a hint of where I do something wrong. I also watched the Romain Guy's guide to created a FlowLayout in the hopes of getting an answer, alas that did not happen.
EDIT
Added my layout.xml file. It seems as if the children respond to above, below, toLeftOf, toRightOf and margins but not other relative layout rules.
As you can see I use simple XML layout.
Even if they children respond to the above rules, the eclipse (and android studio) auto complete does not recognise the xml attributes.
END EDIT
My attrs.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ICGridLayout_Layout">
<attr name="columns" format="integer"/>
<attr name="layout_left" format="integer"/>
<attr name="layout_top" format="integer"/>
<attr name="layout_right" format="integer"/>
<attr name="layout_bottom" format="integer"/>
<attr name="layout_col_span" format="integer"/>
<attr name="layout_row_span" format="integer"/>
<attr name="layout_spacing" format="dimension"/>
</declare-styleable>
</resources>
And my ICGridLayout.java file:
package com.risch.evertsson.iclib.layout;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RemoteViews.RemoteView;
import com.risch.evertsson.iclib.R;
/**
* Created by johanrisch on 6/13/13.
*/
#RemoteView
public class ICGridLayout extends ViewGroup {
private int mColumns = 4;
private float mSpacing;
public ICGridLayout(Context context) {
super(context);
}
public ICGridLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ICGridLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(
attrs,
R.styleable.ICGridLayout_Layout);
this.mColumns = a.getInt(R.styleable.ICGridLayout_Layout_columns, 3);
this.mSpacing = a.getDimension(R.styleable.ICGridLayout_Layout_layout_spacing, 0);
a.recycle();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int width = (int) (r - l);
int side = width / mColumns;
int children = getChildCount();
View child = null;
for (int i = 0; i < children; i++) {
child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int left = (int) (lp.left * side + mSpacing / 2);
int right = (int) (lp.right * side - mSpacing / 2);
int top = (int) (lp.top * side + mSpacing / 2);
int bottom = (int) (lp.bottom * side - mSpacing / 2);
child.layout(left, top, right, bottom);
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
}
private void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
} else {
throw new RuntimeException("widthMeasureSpec must be AT_MOST or " +
"EXACTLY not UNSPECIFIED when orientation == VERTICAL");
}
View child = null;
int row = 0;
int side = width / mColumns;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.bottom > row) {
row = lp.bottom;
}
int childHeight = (lp.bottom - lp.top)*side;
int childWidth = (lp.right-lp.left)*side;
int heightSpec = getChildMeasureSpec(heightMeasureSpec, 0, childHeight);
int widthSpec = getChildMeasureSpec(widthMeasureSpec, 0, childWidth);
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
child.measure(widthSpec, heightSpec);
}
height = row * side;
// TODO: Figure out a good way to use the heightMeasureSpec...
setMeasuredDimension(width, height);
}
#Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new ICGridLayout.LayoutParams(getContext(), attrs);
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof ICGridLayout.LayoutParams;
}
#Override
protected ViewGroup.LayoutParams
generateLayoutParams(ViewGroup.LayoutParams p) {
return new ICGridLayout.LayoutParams(p);
}
protected ViewGroup.LayoutParams
generateLayoutParams(ViewGroup.MarginLayoutParams p) {
return new ICGridLayout.LayoutParams(p);
}
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
int right = 1;
int bottom = 1;
int top = 0;
int left = 0;
int width = -1;
int height = -1;
public LayoutParams() {
super(MATCH_PARENT, MATCH_PARENT);
top = 0;
left = 1;
}
public LayoutParams(int width, int height) {
super(width, height);
top = 0;
left = 1;
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(
attrs,
R.styleable.ICGridLayout_Layout);
left = a.getInt(R.styleable.ICGridLayout_Layout_layout_left, 0);
top = a.getInt(R.styleable.ICGridLayout_Layout_layout_top, 0);
right = a.getInt(R.styleable.ICGridLayout_Layout_layout_right, left + 1);
bottom = a.getInt(R.styleable.ICGridLayout_Layout_layout_bottom, top + 1);
height = a.getInt(R.styleable.ICGridLayout_Layout_layout_row_span, -1);
width = a.getInt(R.styleable.ICGridLayout_Layout_layout_col_span, -1);
if (height != -1) {
bottom = top + height;
}
if (width != -1) {
right = left + width;
}
a.recycle();
}
public LayoutParams(ViewGroup.LayoutParams params) {
super(params);
}
public LayoutParams(ViewGroup.MarginLayoutParams params) {
super(params);
}
}
}
My layout.xml file:
<com.risch.evertsson.iclib.layout.ICGridLayout
android:id="#+id/ICGridLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:spacing="4dp"
app:columns="4" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="8"
app:layout_left="0"
app:layout_right="4"
app:layout_top="0" >
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="90dp"
android:layout_marginTop="109dp"
android:text="Button" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:background="#ffffff"
android:layout_marginLeft="0dp"
android:centerHorizontal="true"
android:layout_below="#+id/button"
android:orientation="vertical" >
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="90dp"
android:layout_marginTop="109dp"
android:text="Button" />
</LinearLayout>
</RelativeLayout>
</com.risch.evertsson.iclib.layout.ICGridLayout>
I've spent at least 5 hours browsing SO and google in order to find an answer and that's why I'm writing my own question.
Thanks in advance.
--Johan Risch
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