简体   繁体   中英

RecyclerView items background drawable doesn't adjust dimensions to content size

So I got a RecyclerView that displays a list of items. Each item consists of an ImageView and two TextViews. One of the TextViews displays the name of the item and the length of this name is different with each item where some items may have only one line for this TextView and others can have two or tree.

Most of the items are displayed normally, but sometimes some items do not get properly resized when they are displayed until I scroll the RecyclerView, at which point all wrongly displayed items are properly resized. See the attached screenshots: 左边错误显示的项目,上下滚动后正确显示的项目

On the above screenshots one can see how the height of the items (or maybe just of its background) falls short of the content height. But I am experiencing similar problems with the item width, where some items are shorter that the RecyclerView width, but always on the right side of the item.

The background of these items is a drawable shape defined in a xml resource and set in the onBindViewHolder method of the RecyclerAdapter through: viewHolder.itemView.setBackground(Drawable drawable);

My question is, has anyone ever met a similar problem when displaying a list of items that differ in size?[look at UPDATE]

I have searched quite some here on Stackoverflow and haven't found anything similar.

I have come to a conclusion that if the problem is in the adapter when it recycles unused items, and doesn't remeasure new content when using the old viewholders, there must be a way to force it to do so in onBindViewHolder(). But I can't seem to find a way to do this.

There is also a small thought that it might just be the background that isn't resizing, since the item content does get displayed, but the background doesn't stretch.

I have tried (with no effect):

  1. To set layoutParams of ViewHolder.itemView, where I would set the height to WRAP_CONTENT
  2. Called ViewHolder.itemView.requestLayout(); (as suggested in some stackoverflow question)
  3. Called ViewHolder.itemView.invalidate();

I did find an answer that somewhat confirms my assumptions, that I'd need to supply the dimensions of the content to the items on binding, even though this should probably be done automatically by the RecyclerView.Adapter: https://stackoverflow.com/a/11091945/4089261

So how should I go about supplying the dimensions of each item in onBindViewHolder, without compromising the performance of the RecyclerView? [look at UPDATE]

Here is the xml of the background:

<?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>

Here is the source of my adapter:

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

Here is the xml of the layout that gets inflated:

<?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>

This is how I setup my RecyclerView:

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

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

UPDATE: I have solved the problem by changing the way I actively set the background of each item from:

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

to

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

So now my question is what is the difference between the two and why does the second work and the first produces the problems described at the beginning?

I had a similar problem, where the background was not only not resizing, but also when I changed the color with setColor(), it sponaneously changed back.

I was getting the background from resources once:

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

then mutating for each item:

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

However, once it had been mutated for the first item, subsequent calls to mutate just returned the original. The mMutated flag in the original was being set to true, so subsequent mutations did nothing. Strange behaviour.

In fixed it be doing the getDrawable() for each item, then mutating that. This works fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM