【问题标题】:How to handle GridView with cell of different heights?如何处理具有不同高度的单元格的 GridView?
【发布时间】:2014-01-18 12:14:36
【问题描述】:

注意:好的,我承认标题有点含糊,但英语不是我的主要语言,我不知道如何用一句话来描述这个问题。

背景

我正在尝试创建一个在 gridView 上显示所有应用信息的应用。

gridView 将其 numColumns 设置为 auto_fit ,以便为所有类型的屏幕很好地设置其列数。

问题

由于每个应用的信息有时会很长,因此单元格高度可能与旁边的不同。

我希望网格单元格具有相同的宽度(在所有情况下都可以正常工作),并且对于每一行,高度应由该行单元格的最大高度确定。

这会导致奇怪的事情发生:

  1. 罕见:一些单元格正在变空。有时,当滚动并返回那里时,单元格会被填满......
  2. 很常见:某些单元格会覆盖其他单元格。
  3. 很常见:一些单元格没有占用它们可以占用的空间,留下无法点击的空白空间。
  4. 非常罕见:在玩滚动时,如果一直滚动到顶部,则整个网格都为空且不可滚动...

这是显示 #2 和 #3 问题的屏幕截图(它们通常不会一起显示):

我用过的代码

这是网格单元格的 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>

我尝试过的

我尝试了多种可能的解决方案:

  1. 使用 Horizo​​ntalListView 似乎可以解决这个问题,但它不允许单击/长按项目。

  2. 我找到了一个不错的库,可以让 textViews 具有“字幕”效果 (here),但它的许可证并不那么宽松。

  3. 将单元格高度设置为固定值或将 textViews 设置为固定行数也可以,但随后会减少(部分)textViews 的内容而无法显示它们。

  4. 我也尝试过使用 linearLayouts 而不是 RelativeLayout,但没有帮助。

  5. 一种可能的解决方案是获取 gridView 上所有视图使用的最大高度(在所有项目上使用 getView),类似于 this code,但它假设您知道列数,并且您知道希望所有单元格的高度相同。

问题

我应该如何处理?

我真的应该使用 StaggeredGrid 之类的东西吗?

有没有办法让 textViews 保持固定大小,但允许它们使用滚动(手动或自动)显示其内容?

【问题讨论】:

  • 你试过设置layout_height="wrap_content"的封闭相对布局吗?
  • @KubaSpatny 是的。可悲的是,它没有帮助。 :(
  • 好吧 StaggeredGrid 肯定会工作,但它可能是一个矫枉过正。您的意思是使用选框效果库,您是否尝试过自己实现它?
  • 我没有,因为我不确定我应该怎么做以及需要多长时间才能做到。我在想也许它应该从 Horizo​​ntalScrollView 扩展,但它不会是可触摸的。我还需要对其进行足够的定制以告诉它如何移动。
  • TextView 和android:ellipsize="marquee"android:maxLines="4" 标签(或android:maxLength="10"),但是listview 的问题是它只有在特定视图处于焦点时才会滚动。因此,您必须创建一个扩展 TextView 的类,如此处所示stackoverflow.com/questions/1827751/…

标签: android textview android-gridview marquee


【解决方案1】:

上述解决方案的替代方案是使用RecyclerViewGridLayoutManager

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

【讨论】:

    【解决方案2】:

    好的,我找到了一个很好的解决方案,这将使行依赖于他们的孩子,但为此我不得不做出一些妥协:

    1. 它是 listView 的适配器,它的行具有水平线性布局。

    2. 使用此代码的人应设置单元格的选择器并选择单击或长按时要执行的操作。

    3. 使用此代码的任何人都应该准备好为 listView 的最后一个单元格准备一个空单元格。

    4. 所使用的 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'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;
        }
      }
    

    【讨论】:

      【解决方案3】:

      这是一个可行的解决方案:http://obduro.nl/blog/the-solution-of-android-gridview-overlap/

      该链接上解释的解决方案是根据其所有元素的高度(使用最大高度)计算每一行的高度。

      它通过捕获 onScrollChanged 事件并在每次 FirstVisiblePosition 更改时更新高度来做到这一点。

      经过测试,它工作得很好,除了一个问题:一旦增加了行大小,它就不会减少。

      实际代码可以在这里找到:https://github.com/JJdeGroot/AutoGridView

      【讨论】:

      • 不错,不过也有RecyclerView的解决方案
      • 我一直看到 RecyclerView 像 ListView,而不是 GridView,但我可能错了?你可以在 RecyclerView 中有多个列吗? GridView 的问题在于它有错误。
      • RecyclerView 可以随心所欲地显示项目。唯一的问题是它的复杂性,因为它的模块化程度很高,而且它错过了我们习惯拥有的一些东西,比如快速滚动条、页眉和页脚。对于快速滚动,我制作了一个库(这里:github.com/AndroidDeveloperLB/…)。对于页脚和页眉,您需要将第一项和最后一项设置为不同的类型,遗憾的是,类似于 ListView。
      • 那么,使用 RecyclerView 发布答案可能有用吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-08
      • 1970-01-01
      • 2019-12-26
      • 1970-01-01
      • 2015-02-19
      相关资源
      最近更新 更多