1

I am trying to get into Android app development with a small walk-duration per route app.
But a little glitch is stopping me from continuing.
I tried to google it many times with no result, here's the thing:

My RecyclerView with onClick-expand cards shows strange behavior of reloading the whole list as soon as I add too many items in it. Also it moves the whole list down somehow for a second. As a reference here's my github project: https://github.com/TheInsayn/velocity

Here's how it should behave:
Imgur

but after I've added a 6th items it behaves like this:

Imgur

Here's the code I've used for the adapter (especially onBindViewHolder with isExpanded):
I even used Google's reference-suggestion from I/O 2016. (I don't really want to use a 3rd party lib)

class RecyclerAdapterWalks extends RecyclerView.Adapter<RecyclerAdapterWalks.WalkCardHolder> {
private List<Walk> mWalkList;
private RecyclerView mRecyclerView;
private int mExpandedPosition = -1;

class WalkCardHolder extends RecyclerView.ViewHolder {
    TextView mWalkRoute;
    TextView mWalkDuration;
    TextView mWalkDate;
    TextView mWalkWeekday;
    RelativeLayout mExpansion;
    TextView mAverageTime;

    WalkCardHolder(View view) {
        super(view);
        mWalkRoute = view.findViewById(R.id.txt_walk_route);
        mWalkDuration = view.findViewById(R.id.txt_walk_duration);
        mWalkDate = view.findViewById(R.id.txt_walk_date);
        mWalkWeekday = view.findViewById(R.id.txt_walk_weekday);
        mExpansion = view.findViewById(R.id.walk_card_expansion);
        mAverageTime = view.findViewById(R.id.txt_walk_average);
    }
}

RecyclerAdapterWalks(List<Walk> routes, RecyclerView rv) {
    mWalkList = routes;
    mRecyclerView = rv;
    setHasStableIds(true);
}

@Override
public WalkCardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_walk, parent, false);
    return new WalkCardHolder(view);
}

@Override
public void onBindViewHolder(WalkCardHolder holder, int position) {
    Walk walk = mWalkList.get(position);
    holder.mWalkRoute.setText(walk.getRoute().getName());
    holder.mWalkDuration.setText(DateFormat.format("mm:ss", new Date(walk.getDuration())));
    holder.mWalkDate.setText(DateFormat.format("dd.MM.yyyy", walk.getDate()));
    holder.mWalkWeekday.setText(DateFormat.format("EEEE", walk.getDate()));
    //handle expansion in list
    final boolean isExpanded = position == mExpandedPosition;
    holder.mExpansion.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
    holder.itemView.setActivated(isExpanded);
    holder.itemView.setOnClickListener(v -> {
        mExpandedPosition = isExpanded ? -1 : position;
        TransitionManager.beginDelayedTransition(mRecyclerView);
        notifyDataSetChanged();
    });
    if (isExpanded) {
        Date avg = new Date(walk.getRoute().getAverageWalkTime(mRecyclerView.getContext()));
        CharSequence min = DateFormat.format("m", avg);
        CharSequence sec = DateFormat.format("s", avg);
        String boldText = walk.getRoute().getName();
        String timeStr = "average walk duration for " + boldText + ": ";
        if (!min.equals("0")) timeStr += min + "m and ";
        timeStr += sec + "s";
        int idx = timeStr.indexOf(boldText);
        SpannableString str = new SpannableString(timeStr);
        str.setSpan(new StyleSpan(Typeface.BOLD), idx, idx + boldText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        holder.mAverageTime.setText(str);
    }
}

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

I have CardView in a RecyclerView in a CoordinatorLayout in a FrameLayout in a RelativeLayout...

Does anybody have an idea what might cause this?
Sorry for the long post and thanks for any help. :)

Edit: using setHasStableIds(true) in the Adapter's constructor seems to help a bit. but now the Transition is far from smooth.
small list (worse):
Imgur
longer list (better):
Imgur

2 Answers2

1

It is reloading the whole list because of notifyDataSetChanged(); line. That refreshes the whole list. If you have only added a single element, I would advise using notifyItemInserted(int position) or if you are updating a single item then just use the function notifyItemChanged(int position)

Napster
  • 1,353
  • 1
  • 9
  • 11
  • so i should notify the item which is expanded and the one i want to expand? will the whole list animate and move? I'm not moving around or inserting items here, otherwise i would use `notifyItemInserted` Also: in google's reference solution there's also `notifyDataSetChanged` and it seems to work fine.. But i'll experiment with it. Thanks. – Mathias Wöß Jan 10 '18 at 20:23
  • Yeah you would use `notifyItemChanged(int position)`. But I think the better solution for you would be to move the `if(isExpanded)` function inside the `onClickListener`. That way every time you press the button the listener will be triggered and it will expand. No need to call any notify method then – Napster Jan 10 '18 at 21:59
  • after moving the `if(isExpanded)` to the `onClick` (without notify) nothing changes anymore onClick. notifying single items disrupts the animation smoothness. (exchanging the expanded item from first to last f.e. should kinda move **all** the other items in the list smoothly up while expanding the last and collapsing the first). – Mathias Wöß Jan 10 '18 at 22:28
  • Okay one more thing I can suggest is try using `notifyItemRangeChanged(int positionStart, int itemCount)`. If `i` is the item changed then use `notifyItemRangeChanged(i-1,i+1)`. Just make sure they dont end up going beyond the range of your data – Napster Jan 11 '18 at 03:35
  • i tried `notifyItemRangeChanged` with no better results.. i found this answer: https://stackoverflow.com/questions/17695594/notifydatasetchanged-makes-the-list-refresh-and-scroll-jumps-back-to-the-top but it also didn't seem to help... – Mathias Wöß Jan 11 '18 at 18:15
0

Based on the input from @napster, I have finally solved my issue with the help of an old topic.
I too much trusted Nick Butcher's example from the I/O.
Quote: "This is a horrible solution"
By using the technique from https://stackoverflow.com/a/48092441/9165497 everything has improved and I now consider the problem resolved.

now my code looks like that and everything is much smoother and without problem:

final boolean isExpanded = position == mExpandedPosition;
holder.mExpansion.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
if (isExpanded) mPreviousExpandedPosition = holder.getAdapterPosition();
holder.itemView.setOnClickListener(v -> {
    mExpandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
    notifyItemChanged(mPreviousExpandedPosition);
    notifyItemChanged(holder.getAdapterPosition());
});
Nkosi
  • 235,767
  • 35
  • 427
  • 472