100

I have a RecyclerView. Each row has a play button, textview and Progressbar. when click on the play button have to play audio from my sdcard and have to progress Progressbar The problem is when i scroll down the recyclerview change the Progressbar in next row.means I can fit 5 items on the screen at once. When I scroll to the 6th, 6th row seekbar changes suddenly.

public class ListAdapter extends RecyclerView.Adapter {

    private List<Historyitem> stethitems;
    public Context mContext;
    public Activity activity;
    public Handler mHandler;

    static MediaPlayer mPlayer;
    static Timer mTimer;

    public ListAdapter(Activity activity,Context mContext,List<Historyitem> stethitems) {
        this.stethitems = stethitems;
        this.mContext = mContext;
        this.activity = activity;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {

        View rootView = LayoutInflater.
                from(mContext).inflate(R.layout.stethoscopeadapteritem, null, false);
        RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        rootView.setLayoutParams(lp);
        mHandler = new Handler();
        return new MyViewHolder(rootView);
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {

        final Historyitem dataItem = stethitems.get(position);
        final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
        myViewHolder.progressplay.setProgress(0);
        myViewHolder.stethdatetime.setText(dataItem.getReported_Time());
        myViewHolder.stethhosname.setText(dataItem.getdiv());

        if(dataItem.getPatient_Attribute().replaceAll(" ","").equals("")){
            myViewHolder.stethdoctorname.setText(dataItem.getunit());
        } else {
            myViewHolder.stethdoctorname.setText(dataItem.getPatient_Attribute());
        }

        myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                   FileDownload(dataItem.getmsg(),
                            myViewHolder.progressplay);

            }
        });
    }

    @Override
    public int getItemCount() {

        return stethitems.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {

        final CustomTextRegular stethdatetime;
        final CustomTextView stethhosname;
        final CustomTextBold stethdoctorname;
        final ImageButton stethstreamplay;
        final NumberProgressBar progressplay;

        public MyViewHolder(View itemView) {
            super(itemView);

            stethdatetime = (CustomTextRegular)
                    itemView.findViewById(R.id.stethdatetime);
            stethhosname = (CustomTextView)
                    itemView.findViewById(R.id.stethhosname);
            stethdoctorname = (CustomTextBold)
                    itemView.findViewById(R.id.stethdoctorname);
            stethstreamplay = (ImageButton)
                    itemView.findViewById(R.id.stethstreamplay);
            progressplay= (NumberProgressBar)
                    itemView.findViewById(R.id.progressplay);


        }
    }
    public void  FileDownload(final String downloadpath,

                                     final NumberProgressBar progressplay) {

        new AsyncTask<NumberProgressBar, Integer, NumberProgressBar>() {
            NumberProgressBar progress;
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                try {
                    if(mPlayer!=null){
                        mPlayer.stop();
                    }
                }catch (Exception e){
                }
                try {
                    if(mTimer != null){
                        mTimer.purge();
                        mTimer.cancel();
                    }
                }catch (Exception e){
                }
            }

            @Override
            protected NumberProgressBar doInBackground(NumberProgressBar... params) {
                int count;
                progress = progressplay;
                try {

                    final List<NameValuePair> list = new ArrayList<NameValuePair>();
                    list.add(new BasicNameValuePair("pid",id));

                    URL url = new URL(Config.requestfiledownload + "?path=" +
                            downloadpath);
                    URLConnection connection = url.openConnection();
                    connection.connect();

                    int lenghtOfFile = connection.getContentLength();
                    // download the file
                    InputStream input = new BufferedInputStream(url.openStream());
                    OutputStream output = new FileOutputStream(Environment.getExternalStorageDirectory() +
                            "record.wav");
                    byte data[] = new byte[1024];
                    long total = 0;
                    while ((count = input.read(data)) != -1) {
                        total += count;
                        // publishing the progress....
                        publishProgress((int) (total * 100 / lenghtOfFile));
                        output.write(data, 0, count);
                    }
                    output.flush();
                    output.close();
                    input.close();
                } catch (Exception e) {

                }
                return progress;
            }
            @Override
            protected void onPostExecute(final NumberProgressBar numberProgressBar) {
                super.onPostExecute(numberProgressBar);

                        try {
                            StartMediaPlayer(numberProgressBar);
                        } catch (Exception e){
                            e.printStackTrace();
                        }

            }
        }.execute();
    }

    public void StartMediaPlayer(final NumberProgressBar progressbar){
        Uri playuri = Uri.parse("file:///sdcard/record.wav");
        mPlayer = new MediaPlayer();
        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mPlayer.reset();
        try {
            mPlayer.setDataSource(mContext, playuri);
        } catch (IllegalArgumentException e) {

        } catch (SecurityException e) {

        } catch (IllegalStateException e) {

        } catch (Exception e) {

        }
        try {
            mPlayer.prepare();
        } catch (Exception e) {

        }

        mPlayer.start();
        progressbar.setMax(mPlayer.getDuration());
        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if(mPlayer!=null) {
                    mPlayer.release();
                    progressbar.setProgress(0);
                }
                if(mTimer != null){
                    mTimer.purge();
                    mTimer.cancel();
                }
            }
        });
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        progressbar.setProgress(mPlayer.getCurrentPosition());
                    }
                });
            }
        },0,500);
    }}
SANAT
  • 8,489
  • 55
  • 66
Arya
  • 1,729
  • 3
  • 17
  • 35

16 Answers16

287

Please try this

  1. If you are using ListView - override the following methods.

     @Override
     public int getViewTypeCount() {    
         return getCount();
     }
    
     @Override
     public int getItemViewType(int position) {    
         return position;
     }
    
  2. If you are using RecyclerView - override only getItemViewType() method.

     @Override
     public int getItemViewType(int position) {    
         return position;
     }
    
Xenolion
  • 12,035
  • 7
  • 33
  • 48
Surendar D
  • 5,554
  • 4
  • 36
  • 38
  • 6
    Why `getItemViewType` is returning `position`?? Curious about this answer. – Jimit Patel Feb 14 '17 at 06:03
  • @JimitPatel exactly... that is the reason why this can't be a proper solution, I guess. Right now I got a RecyclerView which provides different layout types and therefore I use this function (aka "returning the desired view") – Martin Pfeffer Mar 30 '17 at 05:37
  • 1
    @MartinPfeffer I have used with different view types even in `ListView` but this answer is something I am unable to digest, thats why asked out of curiosity. And ya `RecyclerView` is 1000 times better than `ListView` – Jimit Patel Mar 30 '17 at 05:41
  • @JimitPatel found a nice fix: http://stackoverflow.com/a/43112630/2533290 – Martin Pfeffer Mar 30 '17 at 08:43
  • @MartinPfeffer thats too much expensive. I would prefer to call that function once changes are made in `TextWatcher` itself. – Jimit Patel Mar 30 '17 at 08:45
  • I only got < ~20 items :) (in my situation the issue was not directly in onScroll.. I want wo trigger a single update after a button gets clicked). In the most use cases where we provide a edittext there are not too much entries, I guess (somebody has to fill the stuff up..) – Martin Pfeffer Mar 30 '17 at 08:51
  • Not working.. added the same in my adapter .. still facing the issue of items changing their height when i scroll up down – Udit Kapahi Apr 13 '17 at 06:24
  • 22
    This approach might impact performance, especially when having a large list of items: A big advantage of RecyclerView is that it reuses ViewHolder instances and the corresponding view hierarchy. So when the user scrolls and new items are getting visible instead of creating new views previous ViewHolders which are not visible anymore are recycled and bound to newly visible items. But by returning position as type, RecyclerView will handle each ViewHolder as not reusable for a new item because it has a different type. So the "recycle feature" of RecyclerView is basically disabled by doing this – pakat Aug 23 '18 at 11:50
  • @pakat "So the "recycle feature" of RecyclerView is basically disabled" This is wrong, the recycle feature is enabled, the gains from ViewHolder pattern are the ones that are disabled. – Andre Romano Feb 14 '19 at 16:01
  • 1
    Work awesome bro best answer for that if you are using RecycyclerView - override only getItemViewType() method. solve – MustafaShaikh Dec 07 '19 at 13:05
  • How do I use it if I have multiple view types and I use return in order to know which layout to use? – Ben Aug 14 '20 at 15:46
69

Add setHasStableIds(true); in your adapter constructor and Override these two methods in adapter. It also worked if anyone using a RecyclerView inside a ViewPager which is also inside a NestedScrollView.

@Override
public long getItemId(int position) {
            return position;
}

@Override
public int getItemViewType(int position) {
       return position;
}
Gk Mohammad Emon
  • 6,084
  • 3
  • 42
  • 42
Nathan Teyou
  • 2,116
  • 16
  • 14
  • 1
    This is the best answer for me and my RecyclerView. Overriding only "getItemViewType" removes some randomness to my recyclerView (only when scrolling down, going upward on positions, but doesn't work going backward). Implementing also both "getItemId" override and "setHasStableIds(true)" does the job perfectly, both scrolling down and scrolling up. – Alessandro Iudicone Oct 24 '17 at 09:53
  • 12
    But the problem is present when i have getItemViewType return various viewTypes, not just regural position. Does anyone know hot to solve this? – RadoVidjen Jul 17 '18 at 09:40
37

As the name implies, the views in a RecyclerView are recycled as you scroll down. This means that you need to keep the state of each item in your backing model, which in this case would be a Historyitem, and restore it in your onBindViewHolder.

1) Create position, max, and whatever other variables you need to save the state of the ProgressBar in your model.

2) Set the state of your ProgressBar based on the data in your backing model; on click, pass the position of the item to your FileDownload/StartMediaPlayer methods.

public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
    final Historyitem dataItem = stethitems.get(position);
    final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
    myViewHolder.progressplay.setMax(dataItem.getMax());
    myViewHolder.progressplay.setProgress(dataItem.getPosition());
    ...
    myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
               FileDownload(dataItem.getmsg(), position);
        }
    });

3) Update the progress bar by updating the backing model and notifying that it was changed.

stethitems.get(position).setPosition(mPlayer.getCurrentPosition());
notifyItemChanged(position);
tachyonflux
  • 20,103
  • 7
  • 48
  • 67
14

I have faced the same problem while I was trying to implement a recyclerview that contains a edittex and a checkbox as a row elements. I solved the scrolling value changing problem just by adding the following two lines in the adapter class.

@Override
public long getItemId(int position) {
    return position;
}

@Override
public int getItemViewType(int position) {
    return position;
}

I hope it will be a possible solution. Thanks

Shadat
  • 150
  • 2
  • 4
11

recyclerview.setItemViewCacheSize(YourList.size());

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
8

If your recyclerview ViewHolder has more logic or has a different different view then you should try:

 **order_recyclerView.setItemViewCacheSize(x);**

where x is the size of the list. The above works for me, I hope it works for you too.

4

When we are changing RecyclerView items dynamically (i.e. when changing background color of a specific RecyclerView item), it could change appearance of the items in unexpected ways when scrolling due to the nature of how RecyclerView reuse its items.

However to avoid that it is possible to use android.support.v4.widget.NestedScrollView wrapped around the RecyclerView and letting the NestedScrollView handle the scrolling.

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

   </LinearLayout>
</android.support.v4.widget.NestedScrollView>

And then in the code you can disable nested scrolling for the RecyclerView to smooth out scrolling by letting only the NestedScrollView to handle scrolling.

ViewCompat.setNestedScrollingEnabled(recyclerView, false);
haZya
  • 285
  • 4
  • 14
3

Just put you recylerView in a NestedScroll View in your xml and add the property nestedScrollingEnabled = false.

And on your adapter onBindViewHolder add this line

final MyViewHolder viewHolder = (MyViewHolder)holder;

Use this viewHolder object with your views to setText or do any kind of Click events.

e.g viewHolder.txtSubject.setText("Example");

  • 2
    Put recylerView in a NestedScroll View in your XML and add the property nestedScrollingEnabled = false. thanks this helps me :) – Sachin Solanki Dec 24 '19 at 06:23
2

Override the method getItemViewType in adapter. in kotlin use

override fun getItemViewType(position: Int): Int {
    return position
}
Javlon Tulkinov
  • 560
  • 1
  • 5
  • 21
0

I had the same problem while handle a lot of data , it works with 5 because it renders the five elements that are visible on the screen but that gives prob with more elements. The thing is ..

Sometimes RecyclerView and listView just skips Populating Data. In case of RecyclerView binding function is skipped while scrolling but when you try and debug the recyclerView adapter it will work fine as it will call onBind every time , you can also see the official google developer's view The World of listView. Around 20 min -30 min they will explain that you can never assume the getView by position will be called every time.

so, I will suggest to use

RecyclerView DataBinder created by satorufujiwara.

or

RecyclerView MultipleViewTypes Binder created by yqritc.

These are other Binders available if you find those easy to work around .

This is the way to deal with MultipleView Types or if you are using large amount of data . These binders can help you just read the documentation carefully that will fix it, peace!!

Hemant Shori
  • 2,463
  • 1
  • 22
  • 20
0

This line changes progress to 0 on each bind

myViewHolder.progressplay.setProgress(0);

Save its state somewhere then load it in this same line.

Ali Karaca
  • 3,365
  • 1
  • 35
  • 41
0

Why don't you try like this,

    HashMap<String, Integer> progressHashMap = new HashMap<>();

    //...
    if(!progressHashMap.containsKey(downloadpath)){
        progressHashMap.put(downloadpath, mPlayer.getCurrentPosition());
    }

    progressbar.setProgress(progressHashMap.get(downloadpath));
0

try this

@Override public void smoothScrollToPosition(RecyclerView    recyclerView, RecyclerView.State state,
       int position) {    LinearSmoothScroller linearSmoothScroller =
           new LinearSmoothScroller(recyclerView.getContext()) {
               @Override
               public PointF computeScrollVectorForPosition(int targetPosition) {
                   return LinearLayoutManager.this
                           .computeScrollVectorForPosition(targetPosition);
               }
           };    linearSmoothScroller.setTargetPosition(position);    startSmoothScroll(linearSmoothScroller); }

see this also

Vaishali Sutariya
  • 5,093
  • 30
  • 32
0

I had the similar issue and searched alot for the right answer. Basically it is more of a design of recycler view that it updates the view on the scroll because it refreshes the view.

So all you need to do is at the bind time tell it not to refresh it.

This is how your onBindViewHolder should look like

@Override
@SuppressWarnings("unchecked")
public void onBindViewHolder(final BaseViewHolder holder, final int position) {
    holder.bind(mList.get(position));

    // This is the mighty fix of the issue i was having
    // where recycler view was updating the items on scroll.
    holder.setIsRecyclable(false);
}
0

Since we know that kotlin synthetic is now deprecating from kotlin 1.8 onwards. Discontinuing Kotlin synthetics for views And if you are using view binding for it's alternative, then this Viewbinding on RecyclerView may cause this problem. In the adapter class i was initializing my view on onCreateViewHolder and passing binding object to our viewHolder class

binding = parent.viewBinding(ItemBinding::inflate)
        return MyViewViewHolder(binding.root)

But later I found issue that items are moving here & there on scrolling, so I passed view using inflator.

val itemView: View = LayoutInflater.from(parent.context)
        .inflate(R.layout.list_item, parent, false)
    return MyViewViewHolder(itemView)

And on onBindViewHolder i accessed each object by findViewById<YourView>.

This solved my problem.

-2

This is the expected behaviour of recyclerView. Since the view is recycled your items may get into random views. To overcome this you have to specify which item is put into which kind of view by yourself. This information can be kept in a SparseBooleanArray. what you can do is create a SparseBooleanArray in your adapter like this

SparseBooleanArray selectedItems = new SparseBooleanArray();

whenever your view changes, do:

selectedItems.put(viewItemIndex,true);

Now in your onBindViewHolder do

 if(selectedItems.get(position, false)){
    //set progress bar of related to the view to desired position
    }
    else {
        //do the default
    }

This is the basic to solve your problem. You can adjust this logic to any kind of similar problem in recyclerView.

rockfight
  • 1,916
  • 2
  • 20
  • 31
  • How to set viewItemIndex of view – Arya Aug 26 '15 at 04:11
  • That is the item in your adapter list. You can get using [getAdapterPostion](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getAdapterPosition()) or `stethitems.get(position)` as in your code. – rockfight Aug 26 '15 at 04:19
  • Still I am Facing the same issue. when i scroll down the recyclerview change the Progressbar in next row. – Arya Aug 26 '15 at 04:38
  • It is not entering into the else part. – Arya Aug 26 '15 at 04:51
  • ah sorry, change `if(selectedItems.get(position, true))` to `if(selectedItems.get(position, false))`. I will edit the original answer. – rockfight Aug 26 '15 at 05:32
  • I have edit the answer Now it is entering only in else part, it is not entering into the if part – Arya Aug 26 '15 at 05:41
  • Is `selectedItems.put(viewItemIndex,true)` being called just after view is changed? If yes then 'if part' must be entered while scrolling back to the same position. You must use this code before `notifyItemChanged(position)`. I don't see `notifyItemChanged` in your code. – rockfight Aug 26 '15 at 05:59