0

I have a RecyclerView, that recieves list of data - headers, each of them has a bunch of subheaders and texts. So I don't know how many subheaders I'll have for item, and use this code to dynamically inflate the content:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ((ViewHolder) holder).header.setText(data.get(position).getTitle());

    OrganizationInfoEntry entry = data.get(position);
    Map<String, String> entryContent = entry.getContent();

    for (Map.Entry<String, String> mapEntry : entryContent.entrySet()) {

        String subheaderText = mapEntry.getKey();
        String answerText = mapEntry.getValue();

        View v = LayoutInflater.from(context).inflate(R.layout.item_card_content, null);

        TextView subheader = (TextView) v.findViewById(R.id.subheader);
        subheader.setText(subheaderText);

        TextView answer = (TextView) v.findViewById(R.id.answer);
        answer.setVisibility(GONE);
        answer.setText(answerText);

        subheader.setOnClickListener(v1 -> toggleVisibility(answer));

        ((ViewHolder) holder).content.addView(v);
    }
}

From first sight everything looks good, but after scrolling data of first and last items begins to doubling, and finally all other items join the party, turning my recycle to absolute mess.

Pls, tell how can I fix that.

P.S. holder.setIsRecycable(false) isn't a solution. This affects the whole idea to use Recycler badly and hides shown text after scrolling.

P.P.S This is how it looks on device.

  1. Everything ok.

enter image description here

  1. We show content texts, thereby first item becomes larger then screen.

enter image description here

  1. Scroll down - the mess is started.

enter image description here

Dmitry Smolyaninov
  • 2,159
  • 1
  • 18
  • 32

1 Answers1

1

The problem here is that you create different list items for the recycler view, which is ok at first but when you scroll, the recyclerView trys to recycle an old view with n amount of subHeaders and insert new data that may contain m!=n amount of subHeaders. there are several solutions, if you know of a constant and low number of different views (lets say you know you can have between 3 and 5 subHeaders ---> 3 different view typs) you can create a recycler view with different view types ceckout this stackOverflow answer. another solution is to create a costum view group and measure it acourding to the number of subHeaders (if you do this you do not need a list item xml file).

public class ItemView extends ViewGroup {

private static Paint mPaint;

private final ImageView mAvatar;

protected final TextView mText;

private final TextView mAuthor;

public ItemView(Context context) {
    super(context);

    setWillNotDraw(false);

    mAvatar = new ImageView(context);

    int size = getResources().getDimensionPixelSize(R.dimen.avatar_size);
    mAvatar.setLayoutParams(new LayoutParams(size, size));
    addView(mAvatar);

    Point p = new Point();
    Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    display.getSize(p);

    mText = new TextView(context);
    addView(mText);

    mAuthor = new TextView(context);
    mAuthor.setSingleLine();
    addView(mAuthor);
}

public void setData(Status item) {
    AppContext.getImageHandler().load(item.getUser().getProfileImageURL(), mAvatar);
    mAuthor.setText(item.getUser().getName());
    mText.setText(item.getText());
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = measureAllChildren(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(getMeasuredWidth(), height);
}

protected int measureAllChildren(int widthMeasureSpec, int heightMeasureSpec) {
    int m = getResources().getDimensionPixelSize(R.dimen.margin);
    measureChild(mAvatar, widthMeasureSpec, heightMeasureSpec);
    measureChild(mAuthor, widthMeasureSpec, heightMeasureSpec);
    measureChild(mText, MeasureSpec.makeMeasureSpec(getMeasuredWidth() - 2 * m, MeasureSpec.EXACTLY), heightMeasureSpec);
    mAvatar.layout(m, m, m + mAvatar.getMeasuredWidth(), m + mAvatar.getMeasuredHeight());
    mAuthor.layout(mAvatar.getRight() + m, mAvatar.getTop(), mAvatar.getRight() + m + mAuthor.getMeasuredWidth(), m + mAuthor.getMeasuredHeight());
    mText.layout(m, m + mAvatar.getBottom(), getMeasuredWidth(), m + mAvatar.getBottom() + mText.getMeasuredHeight());
    return calculateFinalHeight(m);
}

protected int calculateFinalHeight(int margin) {
    return mText.getBottom() + margin;
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (mPaint == null) {
        mPaint = new Paint();
        mPaint.setColor(Color.rgb(160, 200, 220));
        mPaint.setStrokeWidth(getResources().getDisplayMetrics().density);
    }

    canvas.drawLine(0, getHeight(), getWidth(), getHeight(), mPaint);
}

}

you can use the measureAllChildren method to measure all of the subHeaders and then use calculateFinalHeight() to measur the final hight accourding to the last subheader.

if you use a costum view group than your methods inside the adapter can look like this:

// Create new views (invoked by the layout manager)
@Override
public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Context context = parent.getContext();
    ItemView itemView = new ItemView(context);
    return new ViewHolder(itemView);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    ((ItemView) holder.itemView).setData(mItems.get(position));
}

final class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private final Context context;

    public ViewHolder(ItemView v) {
        super(v);
        context = v.getContext();
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        dispatchIntent();
    }
}

good luck :)

Community
  • 1
  • 1
nir
  • 302
  • 3
  • 12