How to have a GridView that adapts its height when items are added
I am trying to display a dynamically-growing list of strings with a checkbox in a GridView, which is itself in a TableLayout.
I can display these "checkboxed" strings fine in a row. My problem occurs when I let the user dynamically add new strings in the GridView.
I created a custom adapter that receives the list of strings. Say we have n strings. The adapter returns 'n + 1' for the items count; in getView, it returns:
- a View with a LinearLayout, itself having a CheckBox and an EditText for the first n items,
- a LinearLayout with a simple button, with a '+' caption for the 'n + 1'th item.
So far so good. When the '+' button is clicked, I add an empty string to the list of strings and call notifyDataSetChanged in the adapter.
The GridView redraws itself with one more item. BUT it keeps its original height and creates a vertical scrollbar. I'd like the GridView to expand its height (i.e. take up more space on screen and show all items).
I've tried to change the screen to a vertical LinearLayout instead of a TableLayout, but the results is the same.
Here is the layout of my screen:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<!-- other lines omitted -->
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wine_rack_explanation"
android:layout_gravity="center_vertical" />
<GridView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/gvRack"
android:columnWidth="90dp"
android:numColumns="auto_fit" />
</LinearLayout>
<!-- other lines omitted -->
</LinearLayout>
</ScrollView>
The checkbox + string item from the adapter is defined like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<CheckBox android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cbCoordinate"
android:checked="true"
/>
<EditText android:id="@+id/txtCoordinate"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
The '+' button item is defined like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button android:id="@+id/btnAddBottle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_bottle_caption" />
</LinearLayout>
I've tried to call invalidate or invalideViews on the GridView after an item is added. Also called invalidate on the TableLayout and TableRow (in the previous layout of the screen). No success.
Any idea why the GridView refuses to extend its hei开发者_StackOverflow中文版ght ?
(Note that I am completely open to using an other viewgroup than the GridView)
Use this code
public class MyGridView extends GridView {
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context) {
super(context);
}
public MyGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
I have done this way:
This code will set GridView
height as per number of Child.
Note: Where 5
is number of columns.
private void setDynamicHeight(GridView gridView) {
ListAdapter gridViewAdapter = gridView.getAdapter();
if (gridViewAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = gridViewAdapter.getCount();
int rows = 0;
View listItem = gridViewAdapter.getView(0, null, gridView);
listItem.measure(0, 0);
totalHeight = listItem.getMeasuredHeight();
float x = 1;
if( items > 5 ){
x = items/5;
rows = (int) (x + 1);
totalHeight *= rows;
}
ViewGroup.LayoutParams params = gridView.getLayoutParams();
params.height = totalHeight;
gridView.setLayoutParams(params);
}
Hope this will help you.
Developed on the idea shared by https://stackoverflow.com/users/4233197/hiren-patel, so Thank You :)
Pass you GridView and its number of columns through this function and you are done.
private void setGridViewHeightBasedOnChildren(GridView gridView, int noOfColumns) {
ListAdapter gridViewAdapter = gridView.getAdapter();
if (gridViewAdapter == null) {
// adapter is not set yet
return;
}
int totalHeight; //total height to set on grid view
int items = gridViewAdapter.getCount(); //no. of items in the grid
int rows; //no. of rows in grid
View listItem = gridViewAdapter.getView(0, null, gridView);
listItem.measure(0, 0);
totalHeight = listItem.getMeasuredHeight();
float x;
if( items > noOfColumns ){
x = items/noOfColumns;
//Check if exact no. of rows of rows are available, if not adding 1 extra row
if(items%noOfColumns != 0) {
rows = (int) (x + 1);
}else {
rows = (int) (x);
}
totalHeight *= rows;
//Adding any vertical space set on grid view
totalHeight += gridView.getVerticalSpacing() * rows;
}
//Setting height on grid view
ViewGroup.LayoutParams params = gridView.getLayoutParams();
params.height = totalHeight;
gridView.setLayoutParams(params);
}
When onMeasure() is called for GridView, if heightMode is MeasureSpec.UNSPECIFIED, then the gridview will be as tall as all of it's contained elements (plus some padding).
To ensure that this is the case, you can make a quick custom view extending GridView and including the following override:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
}
This is working for me:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background" >
<!-- MORE LAYOUTS IF YOU WANT -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:fillViewport="true" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- MORE LAYOUTS IF YOU WANT -->
<GridView
android:id="@+id/gridview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:stretchMode="columnWidth"
android:verticalSpacing="10dp" >
</GridView>
<!-- MORE LAYOUTS IF YOU WANT -->
</RelativeLayout>
</ScrollView>
</RelativeLayout>
Graham's solution did not work for me. However this one (with a bit more hacking) did: https://stackoverflow.com/a/8483078/479478
This is working for me
public class MyGridView extends GridView {
public MyGridView(Context context) {
super(context);
}
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// set childs height to same value as child that has highest value
for(int i=0; i<getChildCount(); i++) {
MyLinearLayout child = (MyLinearLayout) getChildAt(i);
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
params.height = MyLinearLayout.maxHeight;
child.setLayoutParams(params);
}
// calculate total height and apply to the grid
double numRow = Math.ceil((double) getCount() / (double) getNumColumns());
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) this.getLayoutParams();
params.height = (int) numRow * MyLinearLayout.maxHeight;
this.setLayoutParams(params);
// invalidate after change grid's height
this.invalidate();
}
}
MyLinearLayout class
public class MyLinearLayout extends LinearLayout {
public static int maxHeight = 0;
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (maxHeight<this.getMeasuredHeight()) maxHeight=this.getMeasuredHeight();
}
}
The GridView contains some childs of MyLinearLayout, and this is the layout
<?xml version="1.0" encoding="utf-8"?>
<com.mysystem.view.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ddd"
android:padding="0.5dp"
android:layout_gravity="top"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:padding="20dp"
android:orientation="vertical">
<ImageView
android:id="@+id/img_menu"
android:layout_width="30dp"
android:layout_gravity="center_horizontal"
android:layout_height="30dp"
android:layout_marginTop="5dp"/>
<TextView
android:id="@+id/tv_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textAlignment="center"
android:textColor="#000"
android:text="Title"
android:textSize="10sp"/>
</LinearLayout>
</com.mysystem.view.MyLinearLayout>
精彩评论