please excuse the crude photoshop job. but this image should show pretty succinctly what I'm trying to do:
buttons http://www.efalk.org/buttons3.png
In a nutshell, I want the buttons evenly spaced along the height of my screen, and I'd like them to be all the same size. Is there a reasonable way to do this?
You can try something like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="#+id/FrameLayout01" android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_weight="1">
<Button android:text="#+id/Button01" android:id="#+id/Button01"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top|left">
</Button>
<Button android:text="#+id/Button02" android:id="#+id/Button02"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top|right"></Button>
<Button android:text="#+id/Button03" android:id="#+id/Button03"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"></Button>
<Button android:text="#+id/Button04" android:id="#+id/Button04"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"></Button>
<Button android:text="#+id/Button05" android:id="#+id/Button05"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|left"></Button>
<Button android:text="#+id/Button06" android:id="#+id/Button06"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|right"></Button>
</FrameLayout>
And if you need more than three rows, you can define a LinearLayout and fill it with FrameLayout where every frame contains two buttons.
thanks for the ideas; they helped me focus on the problem.
Sometimes, however, you just have to bite the bullet and do it in code. Here's a quick-and-dirty implementation as a custom ViewGroup. Might have been easier to just do this in onCreate(), after setting the layout, but this worked for me.
/**
* $Id$
*
* SideLayout.java - Quick-and-dirty button layout
*
* Author: Edward A. Falk
* efalk#users.sourceforge.net
*
* Date: Aug 2010
*
* Overview: this container widget takes its children and lays
* them out in two columns, on the left and right edges of the
* parent. Children are evenly spaced and forced to be all the
* same size.
*/
import java.lang.Math;
import android.view.View;
import android.view.ViewGroup;
import android.content.Context;
import android.util.AttributeSet;
public class SideLayout extends ViewGroup {
private int max_wid, max_hgt;
public SideLayout(Context ctx) {
super(ctx);
}
public SideLayout(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
public SideLayout(Context ctx, AttributeSet attrs, int defStyle) {
super(ctx, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
final int num_children = getChildCount();
// Since this is a special-purpose widget, this is brute simple. All
// of our children will have sizes of wrap_content, so we query them
// all, then make a second pass assigning their final sizes.
max_wid = 0;
max_hgt = 0;
for( int i = 0; i < num_children; ++i )
{
final View child = getChildAt(i);
if( child != null && child.getVisibility() != View.GONE )
{
measureChild(child, widthMeasureSpec, heightMeasureSpec);
max_wid = Math.max(max_wid, child.getMeasuredWidth());
max_hgt = Math.max(max_hgt, child.getMeasuredHeight());
}
}
// Make a second pass, tell children the actual size they got.
for( int i = 0; i < num_children; ++i )
{
final View child = getChildAt(i);
if( child != null && child.getVisibility() != View.GONE )
{
child.measure(
MeasureSpec.makeMeasureSpec(max_wid, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(max_hgt, MeasureSpec.EXACTLY));
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
final int num_children = getChildCount();
int x,y;
if( num_children <= 0 )
return;
// Split the children into two groups, left and right. Each
// column is evenly-spaced.
int wid = r - l;
int hgt = b - t;
int nrow = (num_children + 1) / 2;
int margin = (hgt - max_hgt * nrow) / (nrow + 1);
int i = 0;
for( int col = 0; col < 2; ++col ) {
x = col == 0 ? 0 : wid - max_wid;
y = margin;
for( int row = 0; row < nrow; ++row, ++i ) {
if( i < num_children ) {
final View child = getChildAt(i);
if( child != null && child.getVisibility() != View.GONE )
child.layout(x, y, x+max_wid, y+max_hgt);
}
y += margin + max_hgt;
}
}
}
}
Related
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
In one of my activities, I'd like to have something like a 'menubar', positioned at the bottom of the screen in portrait mode and at the right of the screen in landscape mode.
The menubar is permanently visible and will house imagebuttons, which have the same height but may have different widths. According to the number of 'buttons' in the 'menubar' a proper padding is chosen, so that the available height or witdh is used properly.
Basically it should look like this:
I already considered using a 'split action bar', as suggested here. But in this case I only have the desired effect in portrait mode.
At the moment I consider to implement a custom gridview layout to get this effect, or to modify the dashboard pattern found here.
Maybe some of you already implemented something like a 'menubar like layout' and can point me in the right direction.
EDIT
I slightly modified the dashboard pattern code mentioned above, it's just a first proof of concept, but seems to do what I intended:
public class Nx1Layout extends ViewGroup {
private static final String TAG = "Nx1Layout";
private int mMaxChildWidth = 0;
private int mMaxChildHeight = 0;
private int mScreenOrientation = Configuration.ORIENTATION_PORTRAIT;
public Nx1Layout(Context context) {
super(context, null);
}
public Nx1Layout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public Nx1Layout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScreenOrientation(int screenOrientation) {
mScreenOrientation = screenOrientation;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxChildWidth = 0;
mMaxChildHeight = 0;
// Measure once to find the maximum child size.
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight());
}
// Measure again for each child to be exactly the same size.
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec),resolveSize(mMaxChildHeight, heightMeasureSpec));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
final int count = getChildCount();
// Calculate the number of visible children.
int visibleCount = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
++visibleCount;
}
if (visibleCount == 0) {
return;
}
// Horizontal and vertical space between items
int hSpace = 0;
int vSpace = 0;
int cols, rows;
if(mScreenOrientation == Configuration.ORIENTATION_PORTRAIT) {
cols = visibleCount;
rows = 1;
} else {
cols = 1;
rows = visibleCount;
}
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
// Lay out children based on calculated best-fit number of rows and cols.
// If we chose a layout that has negative horizontal or vertical space, force it to zero.
hSpace = Math.max(0, hSpace);
vSpace = Math.max(0, vSpace);
// Re-use width/height variables to be child width/height.
width = (width - hSpace * (cols + 1)) / cols;
height = (height - vSpace * (rows + 1)) / rows;
int left, top;
int col, row;
int visibleIndex = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
row = visibleIndex / cols;
col = visibleIndex % cols;
left = hSpace * (col + 1) + width * col;
top = vSpace * (row + 1) + height * row;
child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width), (vSpace == 0 && row == rows - 1) ? b : (top + height));
++visibleIndex;
}
}
}
Here's some activity code to test it:
public class TestActivity extends SherlockFragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
// setup ui
super.onCreate(savedInstanceState);
setContentView(R.layout.ui_test);
Nx1Layout layout = (Nx1Layout)findViewById(R.id.layout1);
layout.setScreenOrientation(getScreenOrientation());
}
public int getScreenOrientation()
{
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = Configuration.ORIENTATION_UNDEFINED;
if(getOrient.getWidth() == getOrient.getHeight()){
orientation = Configuration.ORIENTATION_SQUARE;
} else{
if(getOrient.getWidth() < getOrient.getHeight()){
orientation = Configuration.ORIENTATION_PORTRAIT;
}else {
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
return orientation;
}}
here's the layout file for portrait mode:
<?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" >
<com.yourpackagename.Nx1Layout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="70dp"
android:id="#+id/layout1">
<ImageView
android:id="#+id/image1"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw" />
<ImageView
android:id="#+id/image2"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw" />
<ImageView
android:id="#+id/image3"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw" />
</com.yourpackagename.Nx1Layout>
</LinearLayout>
and here the layout file for landscape mode:
<?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" >
<com.yourpackagename.Nx1Layout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="70dp"
android:layout_height="match_parent"
android:id="#+id/layout1">
<ImageView
android:id="#+id/image1"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw" />
<ImageView
android:id="#+id/image2"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw_narrow" />
<ImageView
android:id="#+id/image3"
android:layout_width="70dp"
android:layout_height="70dp"
android:contentDescription="some description"
android:scaleType="centerInside"
android:padding="2dp"
android:src="#drawable/test_draw" />
</com.yourpackagename.Nx1Layout>
</LinearLayout>
last but not least some drawable I used:
Feel free to comment on this solution...
I'm having a little difficulties while trying to get a certain layout to work: I want to have list. List does not have to be scrollable, but should be shown completely. But the page itself should be able to scroll (with the lists in it), if the total content ist higher than the screen.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ff181818"
>
<Textview android:id="#+id/my_text" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<Textview android:id="#+id/headertext" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<ListView
android:id="#+id/my_list1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
/>
</LinearLayout>
</ScrollView>
it only uses a small part of the screen (about 2 lines per list), instead of filling the available height, and the lists themselves can be scrolled. How can I change the layout to always show the whole lists but have the screen be scrollalbe?
The solution I used is to replace ListView with LinearLayout. You can create all your items inside LinearLayout, they will all be displayed. So there's really no need to use ListView.
LinearLayout list = (LinearLayout)findViewById(R.id.list_recycled_parts);
for (int i=0; i<products.size(); i++) {
Product product = products.get(i);
View vi = inflater.inflate(R.layout.product_item, null);
list.addView(vi);
}
As #Alex noted in the accepted answer that LinearLayout is hardly a replacement. I had a problem where LinearLayout was not an option, that's when i came across this blog. I will put the code here for reference purposes. Hope it helps someone out there!
public class UIUtils {
/**
* Sets ListView height dynamically based on the height of the items.
*
* #param listView to be resized
* #return true if the listView is successfully resized, false otherwise
*/
public static boolean setListViewHeightBasedOnItems(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all items.
int totalItemsHeight = 0;
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight;
listView.setLayoutParams(params);
listView.requestLayout();
return true;
} else {
return false;
}
}
}
Usage:
//initializing the adapter
listView.setAdapter(adapter);
UIUtils.setListViewHeightBasedOnItems(listView);
//whenever the data changes
adapter.notifyDataSetChanged();
UIUtils.setListViewHeightBasedOnItems(listView);
You can make your own customlistview. (It can extends ListView/ExpandableListView/GridView) and override the onMeasure method with this. With this you'll never need to call a function or anything. Just use it in your xml.
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
I had a ListView in my layout and wanted to use a library which can't handle a ListView here because it wraps it into a ScrollView. The best solution for me is based on FedorĀ“s answer.
Since I already got an ArrayAdapter for the ListView I wanted to re-use it:
LinearLayout listViewReplacement = (LinearLayout) findViewById(R.id.listViewReplacement);
NamesRowItemAdapter adapter = new NamesRowItemAdapter(this, namesInList);
for (int i = 0; i < adapter.getCount(); i++) {
View view = adapter.getView(i, null, listViewReplacement);
listViewReplacement.addView(view);
}
For me this works fine because I just need to display dynamic data varying from 1 to 5 elements. I just had to add my own divider.
If someone still has the problem then you can make customList and add onMesure() method just like I implemented it:
public class ScrolleDisabledListView extends ListView {
private int mPosition;
public ScrolleDisabledListView(Context context) {
super(context);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Record the position the list the touch landed on
mPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
return super.dispatchTouchEvent(ev);
}
if (actionMasked == MotionEvent.ACTION_MOVE) {
// Ignore move events
return true;
}
if (actionMasked == MotionEvent.ACTION_UP) {
// Check if we are still within the same view
if (pointToPosition((int) ev.getX(), (int) ev.getY()) == mPosition) {
super.dispatchTouchEvent(ev);
} else {
// Clear pressed state, cancel the action
setPressed(false);
invalidate();
return true;
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
Check this out:
ListView ignoring wrap_content
Using android:layout_height and android:layout_weight solved it for me:
<ListView
android:layout_height="0dp"
android:layout_weight="1"
/>
I just did it using setting params of ListView
public static void setListViewHeightBasedOnChildren(ListView listView) {
//this comes from value from xml tag of each item
final int HEIGHT_LARGE=75;
final int HEIGHT_LARGE=50;
final int HEIGHT_LARGE=35;
ViewGroup.LayoutParams params = listView.getLayoutParams();
int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
switch(screenSize) {
case Configuration.SCREENLAYOUT_SIZE_LARGE:
params.height =(int) (HEIGHT_LARGE*size);
break;
case Configuration.SCREENLAYOUT_SIZE_NORMAL:
params.height =(int) (HEIGHT_NORMAL*size);
break;
case Configuration.SCREENLAYOUT_SIZE_SMALL:
params.height =(int) (HEIGHT_SMALL*size);
break;
}
listView.setLayoutParams(params);
}
I don't have a static header, but using HussoM's post as a clue, here is what I was able to get to work. In my scenario, the height of the items in the list was non-uniform, due to variable text sentences in each of the items, and I am using wrap_content for the height and match_parent for the width.
public class NonScrollableListView extends ListView {
public NonScrollableListView(Context context) {
super(context);
}
public NonScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Measure the height of all the items in the list and set that to be the height of this
* view, so it appears as full size and doesn't need to scroll.
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ListAdapter adapter = this.getAdapter();
if (adapter == null) {
// we don't have an adapter yet, so probably initializing.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int totalHeight = 0;
// compute the height of all the items
int itemCount = adapter.getCount();
for (int index=0; index<itemCount; index++) {
View item = adapter.getView(index, null, this);
// set the width so it can figure out the height
item.measure(widthMeasureSpec, 0);
totalHeight += item.getMeasuredHeight();
}
// add any dividers to the height
if (this.getDividerHeight() > 0) {
totalHeight += this.getDividerHeight() * Math.max(0, itemCount - 1);
}
// make it so
this.setMeasuredDimension(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY));
}
}
If all items has the same height
int totalItemsHeight = baseDictionaries.size() * item.getMeasuredHeight();
int totalDividersHeight = listView.getDividerHeight() * (baseDictionaries.size() - 1);
int totalPadding = listView.getPaddingBottom() + listView.getPaddingTop();
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) listTranslationWords.getLayoutParams();
lp.height = totalItemsHeight + totalDividersHeight + totalPadding;
listTranslationWords.setLayoutParams(lp);
Iam supprised no one see this.U cant have two scrolls on the same layout. 1st u have a scrollview and then u have a list, i bet u are killing some android good practices there.
If you want a simple solution to this problem without extending ListView class, this is a solution for you.
mListView.post(new Runnable() {
#Override
public void run() {
int height = 0;
for(int i = 0; i < mListView.getChildCount();i++)
height += mListView.getChildAt(i).getHeight();
ViewGroup.LayoutParams lParams = mListView.getLayoutParams();
lParams.height = height;
mListView.setLayoutParams(lParams);
}
});
In my case, I had ListView inside ScrollView and scrollview was shrinking listview by default. So I just add this in my ScrollView and it worked for me
android:fillViewport="true"
Set android:layout_height="fill_parent" in your LinearLayout