27

i have a RecycleView with an adapter that show a list of servers and the user must select one server.

when i call notifyItemChanged(previousPosition) inside the onClick() method to make the old server unselected and the new server selected, that's make the RecycleView list jump to up exactly in the middle of list.

and this problem happen just when i click on one of the last 2 or 3 servers inside the RecycleView list

here is the code of my RecyclerView.Adapter :

public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerViewHolder> {

    private List<Server> listServers = new ArrayList<>();
    private int[] icons = new int[]{R.drawable.server1,R.drawable.server2,R.drawable.server3,R.drawable.server4,R.drawable.server5,R.drawable.server6,R.drawable.offline};
    private int selected = 0;
    private int previousSelected = 0;

    public ServerAdapter(List<Server> listServers){
        this.listServers = listServers;
    }

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

    @Override
    public void onBindViewHolder(final ServerViewHolder holder, final int position) {
        if(position == selected){
            holder.getBackground().setSelected(true);
        }else{
            holder.getBackground().setSelected(false);
        }
        holder.getBackground().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(position != selected){
                    previousSelected = selected;
                    selected = position;
                    holder.getBackground().setSelected(true);
                    notifyItemChanged(previousSelected);
                }
            }
        });
        holder.getImageServer().setImageResource(icons[position%6]);
        holder.getTextNameServer().setText(listServers.get(position).getName());
        holder.getTextConnected().setText(listServers.get(position).getUrl());
    }

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

    public class ServerViewHolder extends RecyclerView.ViewHolder{
        private ImageView imageServer;
        private TextView textNameServer;
        private TextView textConnected;
        private View background;
        public ServerViewHolder(View itemView) {
            super(itemView);
            imageServer = (ImageView)itemView.findViewById(R.id.imageServer);
            textNameServer = (TextView)itemView.findViewById(R.id.textNameServer);
            textConnected = (TextView)itemView.findViewById(R.id.textConnected);
            background = itemView;
        }

        public ImageView getImageServer() {
            return imageServer;
        }

        public TextView getTextConnected() {
            return textConnected;
        }

        public TextView getTextNameServer() {
            return textNameServer;
        }

        public View getBackground() {
            return background;
        }
    }
}

any solutions to solve this problem ? thanks.

The problem happened exactly when i specify the layout height and do not let it to wrap_content

<android.support.v7.widget.RecyclerView
    android:layout_width="wrap_content"
    android:layout_height="400dp"
    android:id="@+id/serverRecyclerView"
    android:layout_margin="10dp"
/>

or when i put it below something for expample like that :

<android.support.v7.widget.RecyclerView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/serverRecyclerView"
    android:layout_margin="10dp"
    android:layout_below="@+id/image"/>

my code exactly is :

<android.support.v7.widget.RecyclerView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/serverRecyclerView"
    android:layout_margin="10dp"
    android:layout_alignTop="@+id/imageBall"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:layout_toRightOf="@+id/camera"
    android:layout_toEndOf="@+id/camera"/>
Zakariaa Oulhafiane
  • 407
  • 1
  • 4
  • 11
  • why notifyItemChanged(previousSelected);??? – Oliver U. Apr 19 '16 at 17:57
  • because i want this previous view selected to be shown as unselected , if i didn't it all the servers will be shown as selected because when i will call notifyItemChanged to this old position it will call onBindViewHolder and it will check if this position is the same as the number stored in the integer selected here if(position == selected){ holder.getBackground().setSelected(true); }else{ holder.getBackground().setSelected(false); } – Zakariaa Oulhafiane Apr 19 '16 at 18:24
  • @OulhafianeZakariaa quick note -> backtick around code, method names improves readability (like ` this ` -> `this`) – Marcin Orlowski Apr 19 '16 at 20:03
  • Like this issue? https://code.google.com/p/android/issues/detail?id=203574 – Hsingchien Cheng Apr 25 '16 at 09:53
  • @Hiking yes is the same problem for me , thank u so much for letting me know that :) – Zakariaa Oulhafiane Apr 26 '16 at 04:01

11 Answers11

16

Looks like this is a bug: https://code.google.com/p/android/issues/detail?id=203574

The best workaround seems to be Bart's answer to set the RecyclerView's LinearLayoutManager's AutoMeasure property to false.

  LinearLayoutManager llm = new LinearLayoutManager(context);
  llm.setAutoMeasureEnabled(false);       
  recyclerView.setLayoutManager(llm);

The set FixedSize to true solution had way too many side-effects...

RecyclerView.setHasFixedSize(true)

Aace
  • 1,792
  • 16
  • 15
  • 8
    This does not work for me. Instead, all of my views within the RecyclerView disappear. – clocksmith Aug 26 '16 at 20:34
  • Solved my problem. It should be the accepted answer! – Ian Medeiros Nov 14 '16 at 19:55
  • the 13th comment describes that the problem stopped for them after they changed the layout that the recyclerview was embedded in from a linearlayout to a relativelayout. The same thing worked for me, so I would assume the solution is to change the containing layout to a relative layout – TormundThunderfist Aug 22 '18 at 10:37
  • 1
    now setAutoMeasureEnabled(false) method is deprecated – Sirojiddin Komolov Mar 26 '19 at 19:17
  • 1
    It is deprecated but looking at the deprecation notes it mentions to just override that method. Something like this will be the same:- `class JumpFixLinearLayoutManager @JvmOverloads constructor( context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) { override fun isAutoMeasureEnabled(): Boolean { return false } }` – Phill Wiggins Jun 03 '19 at 10:26
  • @PhillWiggins your solution is working for me. Thanks. – Parth Patel Oct 13 '21 at 07:22
10

I don't know why, but I used:

RecyclerView.setHasFixedSize(true)

This worked for me. I hope it can help.

Will
  • 24,082
  • 14
  • 97
  • 108
zzq
  • 171
  • 8
6

 android:descendantFocusability="blocksDescendants"

android:descendantFocusability="blocksDescendants"

this attr solve my bug

Zhe Guo
  • 77
  • 1
  • 2
  • 1
    use this attr in parent view of rv – Zhe Guo Oct 26 '18 at 14:49
  • 2
    horizontal recyclerView inside vertical recyclerView generally causes that problem and this is best approach to get rid of problem. Don't put this attr into horizontal recyclerViews, it needs to be in only Parent(vertical RecyclerView) – OMArikan Nov 26 '19 at 00:50
4
RecyclerView.ItemAnimator animator = myRecyclerListView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
    ((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
Ajay George
  • 302
  • 2
  • 8
  • Alternatively, you can also set various animation durations, to avoid casting. For example `recyclerView.itemAnimator?.changeDuration = 0`. – Peter Apr 07 '22 at 10:10
3

My RecyclerView was inside ConstraintLayout, and I also had such problem and calling setAutoMeasureEnabled(false) of RecyclerView's LayoutManager did not fix the issue for me, furthermore this method is deprecated in 28.0.0 version. What I did is that, I wrapped my RecyclerView with RelativeLayout and now it works like a charm. As mentioned in bugtracker, this "issue" is intented behaviour in LinearLayout and is not going to be fixed. So if it is possible, just wrap your RecyclerView something like this:

<RelativeLayout
    android:id="@+id/container_messages_list"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:background="@drawable/chat_back_pattern"
    app:layout_constraintBottom_toTopOf="@+id/bottom_view"
    app:layout_constraintTop_toBottomOf="@+id/toolbar">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/messages_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
</RelativeLayout>
Sirojiddin Komolov
  • 771
  • 10
  • 17
2

RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).

If your use of RecyclerView falls into this category, set this to true. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.

If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.

setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width because each element inserted by the adapter could change the size of the RecyclerView depending on the items inserted/deleted, so, the size of the RecyclerView will be different each time we add/delete items.

recyclerView.setHasFixedSize(true);
  • true if adapter changes cannot affect the size of the RecyclerView.

References
Android Developers Reference - RecyclerView
Understanding RecyclerView setHasFixedSize - Gastón Saillén

ucMedia
  • 4,105
  • 4
  • 38
  • 46
1

for anyone who stumbles upon this issue, try using

yourRecyclerView.notifyItemChanged(int position, Object payload);

This one did the trick for me.

Using

setAutoMeasureEnabled(false);

also worked but in some edge cases recycler view was acting weird. Good luck!

Dhunju_likes_to_Learn
  • 1,201
  • 1
  • 16
  • 25
0

I came across the similar problem, just take care of the xml layout file.
Do not use the layout_below , layout_above or others similar properties in RecyclerView or RecyclerView's parent view.
You can use LinearLayout weight , layout_marginBottom or sth to achieve
layout_below or other.

ThomsonStan
  • 359
  • 3
  • 6
0

The late answer better than nothing, if you're using NestedScrollView as the parent view of RecyclerView you should delete it.

Raed
  • 844
  • 11
  • 10
0

I had a similar problem and I tryed all solutions listed above, but noone worked. I was already padding the "Payloads" to "notifyItemChanged(position, payloads)" because I just needed to "upload" a checkbox value so I was passing the value inside "Payloads" without recalling the update of the entire viewholder. This solution worked for all view holders in my recycler view except for the last one (and probably for all "recycled" ones, I mean those who recall the "onBindViewHolder" by "recycling" an existing view). I think using "notifyItemChanged" will works if you have only the recyclerview and I also think that this problem of "auto-scrolling" is raised by nested scroll views & recycler views.

I was in the case exposed by "raed", so "ScroolView -> RecyclerView -> "n" x RecyclerView". I have a scroolview wich contains a recyclerview whose viewholders can contains a recycler views.

Delete the parent ScrollView is a really weird solution and I couldn't use it, so I setted the "onStopNestedScroll" inside the "ScrollView" and not inside the RecyclerView.

Personally I used it programmatically before the code part which calls the "notifyItemChanged" method by doing:

msvContainer.onStopNestedScroll(mRecyclerView);

Where "msvContainer" is my ScrollView which contains the RecyclerView, and "mRecyclerView" is my RecyclerView contained by the ScrollView. This way worked 99% because the first time I call "notifyItemChanged" the view scroll up only for the ScrollView, so it hides a button inside my ScrollView which is below my RecyclerView but it doesn't scroll the RecyclerView items. After the first call "notifyItemChanged" works properly.

I found that calling:

msvContainer.stopNestedScroll();

works too. But i suggest to use the first method with the target view if you have multiple nested scroll views.

Anyway you should call "startNestedScroll" after you ran out of the critical part of re-updating your view holder because the targeted view, so in my case the RecyclerView, won't scroll until you call this method so it won't recycler his view holders too.

(In my case that I have multiple Recycler View inside a parent Recycler View inside a parent Scroll View if I was in need to call "notifyItemChanged" inside the most inner Recycler View i would use the "stopNestedScroll" method for every parent view and then re-activated the scroll after the scroll-critical part)

Hope this is helpful, have a nice coding! Bye

Z3R0
  • 1,011
  • 10
  • 19
0

In my case, all I did was to set the height of the recyclerview to "match_parent". Then in your MainActivity, do; recyclerView.smoothScrollToPosition(yourAdapter.getItemCount()-1);

Thats all...