RecyclerView的项背景可绘制对象不会调整尺寸以适应内容大小

6
所以我有一个RecyclerView,显示一系列项目。每个项目都包括一个ImageView和两个TextViews。其中一个TextView显示项目的名称,该名称的长度与每个项目不同,某些项目可能仅为此TextView提供一行,而其他项目则可以提供两行或三行。
大多数项目正常显示,但有时在显示它们时,某些项目无法正确调整大小,直到我滚动RecyclerView,此时所有错误显示的项目都会正确调整大小。请参见附加的屏幕截图:左侧错误显示的项目,右侧上下滚动后正确显示的项目 在上面的屏幕截图中,可以看到项目的高度(或者可能只是其背景)不足内容高度。但我也遇到了类似的问题,即项目宽度不足RecyclerView宽度,但始终在项目的右侧。
这些项目的背景是在xml资源中定义的可绘制形状,并通过RecyclerAdapter的onBindViewHolder方法设置:viewHolder.itemView.setBackground(Drawable drawable);

我的问题是,是否有人遇到过显示大小不同的项目列表时类似的问题?[请参阅更新]

我在Stackoverflow上搜索了很多,没有找到类似的东西。

我得出结论,如果问题出现在适配器中,当它回收未使用的项目并且在使用旧的视图持有者时不重新测量新内容,则必须有一种方法在onBindViewHolder()中强制执行此操作。 但我似乎找不到这样做的方法。

还有一个小想法,可能只是背景没有调整大小,因为项目内容确实被显示出来,但背景没有拉伸。

我尝试过(没有效果):

  1. 设置ViewHolder.itemView的layoutParams,其中我将高度设置为WRAP_CONTENT
  2. 调用ViewHolder.itemView.requestLayout();(如某些stackoverflow问题所建议的)
  3. 调用ViewHolder.itemView.invalidate();
我找到了一个回答,有些证实了我的假设,即我需要在绑定时向项目提供内容的尺寸,即使这应该由RecyclerView.Adapter自动完成:https://stackoverflow.com/a/11091945/4089261 那么,在不影响RecyclerView性能的情况下,我应该如何在onBindViewHolder中提供每个项的尺寸呢?[请看更新]
这是背景的xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/colorListElementBackground"/>
    <stroke android:color="@color/colorListElementOutline" android:width="1dp"/>
    <corners android:radius="3dp"/>
</shape>

这是我的适配器的源代码:

public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder>{

    private Context mContext;
    private ArrayList<Item> mDataset;
    private int activeItemId = -1;
    private String adapterTAG = "";

    private RecyclerAdapterItemClickListener onItemClickListener = null;
    private OnListItemChildClickListener onItemChildClickListener = null;

    private Drawable background, backgroundActive, expandDrawable, foldDrawable;

    public ListAdapter(Context context, ArrayList<Item> dataset, String tag){
        mContext = context;
        mDataset = dataset;
        adapterTAG = tag;

        background = ResourcesCompat.getDrawable(mContext.getResources(),
                R.drawable.list_row_bg, null);
        backgroundActive = ResourcesCompat.getDrawable(mContext.getResources(),
                R.drawable.list_row_bg_selected, null);
        expandDrawable = ResourcesCompat.getDrawable(mContext.getResources(),
                R.drawable.ic_expand_arrow512,null);
        foldDrawable = ResourcesCompat.getDrawable(mContext.getResources(),
                        R.drawable.ic_collapse_arrow512,null);
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        FontView title_text, timestamp_text;
        ImageView child_expansion_icon;
        ViewHolder(View v) {
            super(v);
            title_text = (FontView) v.findViewById(R.id.item_title);
            timestamp_text = (FontView) v.findViewById(R.id.item_timestamp);
            child_expansion_icon = (ImageView)v.findViewById(R.id.child_expansion_icon);
        }
    }

    public void setActiveItemId(int itemId){
        activeItemId = itemId;
        this.notifyDataSetChanged();
    }

    @Override
    public ListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row_item,
                parent,false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(final ListAdapter.ViewHolder vh, final int position) {
        Item itme = mDataset.get(position);
        vh.title_text.setText(item.getTitle());
        vh.timestamp_text.setText(item.getTimestampString());

        if(activeItemId == item.getId()) {
            vh.itemView.setBackground(backgroundActive);
        }else{
            vh.itemView.setBackground(background);
        }

        if(!item.hasChildren()){
            vh.child_expansion_icon.setVisibility(View.INVISIBLE);
        }else{
            vh.child_expansion_icon.setVisibility(View.VISIBLE);
            if(!item.isExpanded()){
                vh.child_expansion_icon.setImageDrawable(expandDrawable);
            }else {
                vh.child_expansion_icon.setImageDrawable(foldDrawable);
            }
            vh.child_expansion_icon.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(onItemChildClickListener!=null){
                        onItemChildClickListener.onListItemChildClick(vh.child_expansion_icon,position);
                    }
                }
            });
        }

        vh.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(onItemClickListener!=null) {
                    onItemClickListener.onListItemClick(adapterTAG,vh.getAdapterPosition());
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDataset.size();
    }

    @Override
    public long getItemId(int position) {
        return mDataset.get(position).getId();
    }

    public void setOnItemClickListener(RecyclerAdapterItemClickListener listener){
        this.onItemClickListener = listener;
    }

    public void setOnItemChildClickListener(OnListItemChildClickListener listener){
        onItemChildClickListener = listener;
    }
}

这是被填充的布局的 XML:

<?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="wrap_content"
    android:orientation="horizontal"
    android:background="@drawable/list_row_bg"
    style="@style/list_row_item"
    android:padding="10dp"
    >

    <ImageView
        android:id="@+id/child_expansion_icon"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:visibility="visible"
        />

    <LinearLayout
        android:id="@+id/item_details"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_weight="1"
        >
        <FontView
            android:id="@+id/item_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginStart="10dp"
            android:ellipsize="end"
            android:minLines="2"
            style="@style/list_row_item_title"
            android:text="Item 1"/>

        <FontView
            android:id="@+id/item_timestamp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="26dp"
            android:layout_marginStart="26dp"
            style="@style/list_row_item_timestamp"
            android:text="0:00:00"/>
    </LinearLayout>
</LinearLayout>

这是我设置RecyclerView的方法:

playlistAdapter = new ListAdapter(this, mShownItems,"PlaylistAdapter");
playlistAdapter.setOnItemClickListener(this);
playlistAdapter.setOnItemChildClickListener(this);
playlistView.setAdapter(playlistAdapter);
playlistView.addItemDecoration(new ItemListDecorator());

我的ItemListDecorator:

public class ItemListDecorator extends RecyclerView.ItemDecoration {

    public ItemListDecorator(){
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        ListAdapter adapter = (ListAdapter)parent.getAdapter();
        int pos = parent.getChildAdapterPosition(view);
        if(adapter.getItemCount()>0) {
            Item item = adapter.getItem(pos);
            if(item!=null) {
                //set displayed left margin depending on item level
                outRect.left = AppUtils.dpToPx(view, (item.getLevel() - 1) * 10);
                outRect.right = 0;
            }
        }
    }
}

更新: 我通过改变每个项目背景设置的方式解决了这个问题,原文如下:

Drawable background;
public ListAdapter(...){
    background = ResourcesCompat.getDrawable(mContext.getResources(),
                    R.drawable.list_row_bg, null);
}
@Override
    public void onBindViewHolder(final ListAdapter.ViewHolder vh, final int position) {
    vh.itemView.setBackground(background);
}

vh.itemView.setBackgroundResource(R.drawable.list_row_bg);

现在我的问题是这两者之间的区别是什么,为什么第二个有效而第一个会产生开头描述的问题?

1
我可以观察到这种行为。在阅读了您的更新后,我将我的背景加载方式从使用ResourceCompat获取Drawable并手动设置背景改为使用资源ID加载。之后一切都按预期工作了。感谢您指引我正确的方向。 - Basti Destruction
1
Drawable具有绘图属性,可能已被缓存。请参见http://loseyourmarbles.co/2013/09/android-drawable-instances-dont-share/。 - lannyf
@lannyf 这看起来是我最初问题的好候选人。链接的帖子也值得一读。谢谢。 - ak93
1个回答

0

我曾经遇到过类似的问题,背景不仅没有自适应大小,而且当我使用setColor()更改颜色时,它会自动改回原来的颜色。

我曾经从资源中获取背景:

Drawable lineBoxBg = AppCompatResources.getDrawable(this, R.drawable.line_box_bg);

然后对每个项目进行变异:

GradientDrawable bg = (GradientDrawable) lineBoxBg.mutate();

然而,一旦第一个项目被突变,对于后续的突变调用只会返回原始值。原始值中的mMutated标志被设置为true,因此后续的突变操作无效。这是一种奇怪的行为。

通过对每个项目执行getDrawable(),然后进行突变来解决该问题。这样可以正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接