Как обращаться с GridView с ячейкой разной высоты?

ПРИМЕЧАНИЕ. Хорошо, я признаю, что название немного расплывчато, но английский язык не является моим основным языком, и я не уверен, как описать проблему в одном отклике.

Фон

Я пытаюсь создать приложение, отображающее всю информацию о приложениях в gridView.

У gridView есть numColumns, установленный на auto_fit, так что его количество столбцов будет прекрасно настроено для всех типов экранов.

Проблема

Поскольку информация о каждом приложении иногда может быть длинной, высота ячейки может отличаться от той, которая находится рядом с ней.

Я хочу, чтобы ячейки сетки имели одинаковую ширину (что отлично работает во всех случаях), а для каждой строки высота должна определяться максимальной высотой ячеек строки.

Это вызывает странные вещи:

  • Редко: некоторые ячейки становятся пустыми. иногда, когда прокручивается и возвращается туда, ячейки заполняются...
  • Весьма распространено: некоторые ячейки используют другие ячейки.
  • Довольно часто: некоторые ячейки не занимают места, которое они могут принимать, оставляя пустое пространство, на которое нельзя щелкнуть.
  • Очень редко: при воспроизведении с прокруткой, если вы прокрутите весь путь до вершины, вы получите всю сетку пустым и не прокручиваемым...

Вот скриншот, показывающий проблемы С# 2 и # 3 (они обычно не отображаются вместе):

enter image description here

Код, который я использовал

Это xml ячейки сетки:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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>

Что я пробовал

Я попробовал несколько возможных решений:

  • используя HorizontalListView, похоже, это разрешает, но затем он не позволяет щелкнуть/долго нажимать на элементы.

  • Я нашел красивую библиотеку для создания текстовых эффектов с эффектом "marquee" ( здесь), но его лицензия не настолько разрешительна.

  • Настройка высоты ячейки как фиксированной или установка текстовых просмотров с фиксированным количеством строк также работает, но затем она сокращает (часть) содержимое текстовых элементов без возможности их отображения.

  • Я также пытался использовать linearLayouts вместо RelativeLayout, но это не помогло.

  • Возможное решение - получить максимальную высоту, используемую всеми представлениями gridView (используя getView для всех элементов), подобно этот код, но предполагается, что вы знаете количество столбцов и хотите, чтобы все ячейки имели одинаковую высоту.

Вопрос

Как мне это сделать?

Должен ли я использовать что-то вроде StaggeredGrid?

Есть ли способ поместить текстовые элементы в фиксированный размер, но позволить им показывать их содержимое с помощью прокрутки (вручную или автоматически)?

Ответ 1

Хорошо, я нашел для этого приятное решение, которое заставит строки зависеть от их детей, но для этого мне пришлось сделать некоторые компромиссы:

  • это адаптер для listView, который имеет горизонтальные линейные макеты для своих строк.

  • тот, кто использует этот код, должен установить селектор ячеек и выбрать, что делать при нажатии или длительном нажатии.

  • тот, кто использует этот код, должен быть готов подготовить пустую ячейку для дополнительных последних ячеек списка.

  • используемый listView должен иметь свой список ListSelector для прозрачности, чтобы запретить нажимать на строки.

Я надеюсь, что кто-то может сделать лучшее решение, которое не потребует дополнительных просмотров.

Использование образца:

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

Здесь код:

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 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;
    }
  }

Ответ 2

Альтернативой вышеупомянутому решению является использование RecyclerView и GridLayoutManager:

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

Ответ 3

Вот рабочее решение: http://obduro.nl/blog/the-solution-of-android-gridview-overlap/

Решение объясняет, что эта ссылка предназначена для расчета высоты каждой строки в зависимости от высоты всех ее элементов (с максимальной высотой).

Он делает это, перехватывая событие onScrollChanged и обновляя высоты каждый раз, когда изменяются значения FirstVisiblePosition.

После тестирования это прекрасно работает, за исключением одного улова: после увеличения размера строки он не будет уменьшаться.

Фактический код можно найти здесь: https://github.com/JJdeGroot/AutoGridView