Question

NOTE: OK, I admit the title is a bit vague, but English is not my main language, and I'm not sure how to describe the problem in one sentance.

Background

I'm trying to create an app that shows all apps information on a gridView.

The gridView has its numColumns set to auto_fit , so that its number of columns will be set nicely for all types of screens.

The problem

Since each app's information can sometimes be long, the cell height can be different from those that are next to it.

I want the grid cells to be of the same width (which works fine on all cases), and for each row, the height should be determined by the max height of the row's cells.

This causes weird things to occur:

  1. Rare: some cells are becoming empty . sometimes, when scrolling and returning there, the cells get filled...
  2. Quite common: some cells draw over other cells.
  3. Quite common: some cells don't take the space they can take , leaving empty space that cannot be clicked.
  4. Very rare: when playing with the scrolling, if you scroll all the way to the top, you get the entire grid to be empty and not scrollable ...

Here is a screenshot showing both #2 and #3 issues (they don't usually show together) :

enter image description here

Code I've used

This is the xml of the grid cell :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/list_divider_holo_dark" >

    <ImageView
        android:id="@+id/appIconImageView"
        android:layout_width="48dip"
        android:layout_height="48dip"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:adjustViewBounds="true"
        android:contentDescription="@string/app_icon"
        android:src="@drawable/ic_menu_help" />

    <TextView
        android:id="@+id/appLabelTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_toLeftOf="@+id/isSystemAppImageView"
        android:layout_toRightOf="@+id/appIconImageView"
        android:ellipsize="marquee"
        android:text="label"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textStyle="bold"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/appDescriptionTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/appLabelTextView"
        android:layout_below="@+id/appLabelTextView"
        android:layout_toLeftOf="@+id/isSystemAppImageView"
        android:ellipsize="marquee"
        android:text="description"
        tools:ignore="HardcodedText" />

    <ImageView
        android:id="@+id/isSystemAppImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/appDescriptionTextView"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:adjustViewBounds="true"
        android:contentDescription="@string/content_description_this_is_a_system_app"
        android:src="@drawable/stat_sys_warning" />

</RelativeLayout>

What I've tried

I've tried multiple possible solutions:

  1. using HorizontalListView seems to solve this, but then it doesn't allow clicking/long clicking on items.

  2. I've found a nice library for making the textViews have a "marquee" effect (here) , but its license isn't so permissive.

  3. Setting the cell height as fixed one or setting the textViews to have fixed lines count also work, but then it cuts down (part of) the content of the textViews without the ability to show them.

  4. I've also tried using linearLayouts instead of RelativeLayout, but it didn't help.

  5. a possible solution is to get the max height used by all views on the gridView (using getView on all items) , similar to this code, but it assumes you know the number of columns , and that you do want all cells to be of the same height.

The question

How should I handle this?

Should I really use something like StaggeredGrid ?

Is there a way to put the textViews to stay fixed in size yet allow them to show their content using scrolling (manually or automatically) ?

Était-ce utile?

La solution 2

OK, i've found a nice solution for this, which will make the rows depend on their children, but for this I had to make some compromises :

  1. it's an adapter for a listView , which has horizontal linear layouts for its rows.

  2. whoever uses this code should set the selector of the cells and choose what to do on clicking or long clicking.

  3. whoever uses this code should be ready to prepare an empty cell for the extra last cells of the listView.

  4. the listView that is used should have its listSelector set to transparent, to disallow clicking on rows.

I hope anyone could make a better solution that won't need extra views.

sample usage:

_listAdapter=new GriddedListViewAdapter();
_listAdapter.setNumberOfColumns(numberOfColumns);
-listAdapter.setItems(items);
_listView.setAdapter(_listAdapter);

Here's the code:

public abstract class GriddedListViewAdapter<ItemType> extends BaseAdapter
  {
  private int                       _numColumns =1;
  private final List<Row<ItemType>> _rows       =new ArrayList<Row<ItemType>>();
  protected final Context           _context    =App.global();
  private List<ItemType>            _items;

  public void setItems(final List<ItemType> items)
    {
    _items=items;
    prepareRows();
    notifyDataSetChanged();
    }

  private void prepareRows()
    {
    _rows.clear();
    final int itemsCount=_items.size();
    for(int i=0;i<itemsCount;i+=_numColumns)
      {
      final Row<ItemType> row=new Row<ItemType>();
      row.startIndex=i;
      row.items=new ArrayList<ItemType>(_numColumns);
      for(int j=0;j<_numColumns;++j)
        {
        ItemType item;
        if(i+j<itemsCount)
          item=_items.get(i+j);
        else item=null;
        row.items.add(item);
        }
      _rows.add(row);
      }
    }

  public void setNumberOfColumns(final int numColumns)
    {
    if(_numColumns==numColumns)
      return;
    _numColumns=numColumns;
    if(_items!=null)
      {
      prepareRows();
      notifyDataSetChanged();
      }
    }

  @Override
  public final int getCount()
    {
    return _rows.size();
    }

  @Override
  public final Row<ItemType> getItem(final int position)
    {
    return _rows.get(position);
    }

  @Override
  public final long getItemId(final int position)
    {
    return position;
    }

  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  @Override
  public final View getView(final int position,final View convertView,final ViewGroup parent)
    {
    final Row<ItemType> row=getItem(position);
    IcsLinearLayout rowLayout=(IcsLinearLayout)convertView;
    if(rowLayout==null)
      {
      rowLayout=new IcsLinearLayout(_context,null);
      rowLayout.setMeasureWithLargestChildEnabled(true);
      rowLayout.setShowDividers(IcsLinearLayout.SHOW_DIVIDER_MIDDLE);
      rowLayout.setDividerDrawable(_context.getResources().getDrawable(R.drawable.list_divider_holo_dark));
      rowLayout.setOrientation(LinearLayout.HORIZONTAL);
      rowLayout.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
      }
    final int childCount=rowLayout.getChildCount();
    for(int i=childCount;i>_numColumns;--i)
      rowLayout.removeViewAt(i-1);
    // reuse previous views of the row if possible
    for(int i=0;i<_numColumns;++i)
      {
      // reuse old views if possible
      final View cellConvertView=i<childCount ? rowLayout.getChildAt(i) : null;
      // fill cell with data
      final View cellView=getCellView(row.items.get(i),row.startIndex+i,cellConvertView,rowLayout);
      LinearLayout.LayoutParams layoutParams=(LinearLayout.LayoutParams)cellView.getLayoutParams();
      if(layoutParams==null)
        {
        layoutParams=new LinearLayout.LayoutParams(0,LayoutParams.MATCH_PARENT,1);
        layoutParams.gravity=Gravity.CENTER_VERTICAL;
        cellView.setLayoutParams(layoutParams);
        }
      else
        {
        final boolean needSetting=layoutParams.weight!=1||layoutParams.width!=0||layoutParams.height!=LayoutParams.MATCH_PARENT;
        if(needSetting)
          {
          layoutParams.width=0;
          layoutParams.height=LayoutParams.MATCH_PARENT;
          layoutParams.gravity=Gravity.CENTER_VERTICAL;
          layoutParams.weight=1;
          cellView.setLayoutParams(layoutParams);
          }
        }
      if(cellConvertView==null)
        rowLayout.addView(cellView);
      }
    return rowLayout;
    }

  @Override
  public final int getViewTypeCount()
    {
    return super.getViewTypeCount();
    }

  @Override
  public final int getItemViewType(final int position)
    {
    return super.getItemViewType(position);
    }

  /**
   * should handle getting a single cell view. <br/>
   * NOTE:read the parameters description carefully !
   * 
   * @param item
   * the item that is associated with the cell. if null, you should prepare an empty cell
   * @param rawPosition the position within the original list of items that is associated with the cell
   * @param convertView
   * a recycled cell. you must use it when it's not null, fill it with data, and return it
   * @param parent
   * the parent of the view. you should use it for inflating the view (but don't attach the view to the
   * parent)
   */
  public abstract View getCellView(ItemType item,int rawPosition,View convertView,ViewGroup parent);

  // ////////////////////////////////////
  // Row//
  // /////
  private static class Row<ItemType>
    {
    int                 startIndex;
    ArrayList<ItemType> items;
    }
  }

Autres conseils

An alternative to the above solution is to use RecyclerView and GridLayoutManager:

recyclerView.setHasFixedSize(false);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));

Here is a working solution: http://obduro.nl/blog/the-solution-of-android-gridview-overlap/

The solution has explained on that link is to calculate each row height based on the height of all its elements (using maximum height).

It does that by catching the onScrollChanged event and updating heights everytime the FirstVisiblePosition changes.

After testing this, it works beautifully, except for one catch: Once a row size has been increased it will not decrease.

Actual code can be found here: https://github.com/JJdeGroot/AutoGridView

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top