0

app lags when scroll the recyclerview tried on physical device one plus 9 pro same thing happen minor lags when scroll. If i remove countdown it work smooth. below is my recyclerview adapter.I also used custom countdown timer and runnable class same thing. in first try i have created countdowntimer in onbindviewholder but according to this answer onbindview create countdown timer every time when scoll so i moved in viewholder.

public class AdapterFixturesList extends RecyclerView.Adapter<AdapterFixturesList.MyViewHolder> {
    private List<BeanHomeFixtures> mListenerList;
    Context mContext;

    public AdapterFixturesList(List<BeanHomeFixtures> mListenerList, Context context) {
        mContext = context;
        this.mListenerList = mListenerList;
    }

public class MyViewHolder extends RecyclerView.ViewHolder {
        TextViewtv_TimeRemained;
        BeanHomeFixtures mModel;
        CountDownTimer countDownTimer;

public MyViewHolder(View view) {
            super(view);
            tv_TimeRemained = view.findViewById(R.id.tv_TimeRemained);
        }
   }

@Override
    public int getItemCount() {
        return mListenerList.size();
    }
@Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_fixtures_list, parent, false);

        return new MyViewHolder(itemView);
    }     

@Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        final int time = mListenerList.get(position).getTime();
        holder.tv_TimeRemained.setText(time + "");


            if (holder.countDownTimer != null) {
                holder.countDownTimer.cancel();
            }

            try {

                int FlashCount = time;
                long millisUntilFinished = FlashCount * 1000;

                holder.countDownTimer = new CountDownTimer(millisUntilFinished, 1000) {

                    public void onTick(long millisUntilFinished) {

                        long Days = TimeUnit.HOURS.toDays(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                        long Hours = TimeUnit.MILLISECONDS.toHours(millisUntilFinished) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(millisUntilFinished));
                        long Minutes = TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                        long Seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished));

                        String format = "%1$02d";
                        holder.tv_TimeRemained.setText(String.format(format, Days) + ":" + String.format(format, Hours) + ":" + String.format(format, Minutes) + ":" + String.format(format, Seconds));
                    }

                    public void onFinish() {
                        callMyMatchRecord(false);
                        holder.tv_TimeRemained.setText("Entry Over!");
                    }

                }.start();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

I tried Runnable method also but same result.I appreciate for help.

image profiler image

UPDATED

after trying all methods best result give me this.right now this method skipping frames every second i dont know is this right or not please check.

public class AdapterFixturesList extends RecyclerView.Adapter<AdapterFixturesList.MyViewHolder> {
    private List<BeanHomeFixtures> mListenerList;
    Context mContext;
    CountDownTimer timer;

    public AdapterFixturesList(final List<BeanHomeFixtures> mListenerList, Context context) {
        mContext = context;
        this.mListenerList = mListenerList;

        long maxTime = 0;
        for (BeanHomeFixtures item : mListenerList) {
            if (!item.getTime().equalsIgnoreCase("")) {
                maxTime = Long.parseLong(item.getTime());
            }
        }

        timer = new CountDownTimer(maxTime, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {
                    for (int i = 0, dataLength = mListenerList.size(); i < dataLength; i++) {
                        BeanHomeFixtures item = mListenerList.get(i);
                        if (!item.getTime().equalsIgnoreCase("")) {
                            item.setTime(String.valueOf(Long.parseLong(item.getTime())-1));
                        }
                        notifyItemChanged(i , "time");
                    }
                }
                @Override
                public void onFinish() {
                }
            }.start();

        }


    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tv_TimeRemained;

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

            tv_TimeRemained = view.findViewById(R.id.tv_TimeRemained);
           
        }

    }

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

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_fixtures_list, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {

        
        final String time = mListenerList.get(position).getTime();

            if(Long.parseLong(time) > 0){
                long FlashCount = Long.parseLong(time);
                long millisUntilFinished = FlashCount * 1000;
                long Days = TimeUnit.HOURS.toDays(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                long Hours = TimeUnit.MILLISECONDS.toHours(millisUntilFinished) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(millisUntilFinished));
                long Minutes = TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                long Seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished));

                String format = "%1$02d";
               holder.tv_TimeRemained.setText(String.format(format, Days) + ":" + String.format(format, Hours) + ":" + String.format(format, Minutes) + ":" + String.format(format, Seconds));
            }
            else{
                holder.tv_TimeRemained.setText("Entry Over!");
            }
        }

}

i know time is not going to change but if i remove notifyitemchange its work fine that means now timer not causing the lag. if i use handler and runnable it skipping 5 to 7 frame per second depend on data even in partial layout method. so i don't know what is the problem.

ryan kun
  • 1
  • 1
  • 2
  • First, make sure to override `getItemId(...)`, `getItemCount(...)` and `getItemType(...)` properly – cmak Oct 09 '22 at 12:07
  • Is `updateTimeRemaining` called from anywhere else besides what is shown in the post? – Computable Oct 09 '22 at 12:55
  • @Gardener No it's only called in myviewholder. – ryan kun Oct 10 '22 at 11:34
  • @cmak yes overrided properly . – ryan kun Oct 10 '22 at 11:39
  • Check how many timers you have going at once. You may have more running than you think. I don't see where a timer is cancelled if its view holder is recycled. – Cheticamp Oct 10 '22 at 13:12
  • @Cheticamp i think that can be problem how can i cancle it. – ryan kun Oct 10 '22 at 13:51
  • @Cheticamp you are right it's creating multiple timer and not canceling it. How about just create single timer and use it to change textview. is it possible? – ryan kun Oct 10 '22 at 14:25
  • You will need to explain a little more about what you are trying to do. Is there just one time that needs to be displayed? When does the timer start/restart? – Cheticamp Oct 10 '22 at 19:20
  • @ryankun please, show how you do it – cmak Oct 11 '22 at 00:02
  • @Cheticamp No , it's multiple time in recyclerview but i am trying to do like this https://stackoverflow.com/a/66405963/18420435 single countdowntimer. – ryan kun Oct 11 '22 at 08:08
  • @cmak I already updated full recyclerview adapter in question. – ryan kun Oct 11 '22 at 08:14
  • So, display the same timer for each item shown in the _RecyclerView. In short, let's say you have 10 items showing. You want to show the same value for the timer in each of the 10 items. Is that correct? – Cheticamp Oct 11 '22 at 13:20
  • @Cheticamp NO not same value diffrent values but single countdowntimer with in ontick notifyItemchange this only method is left which i have not tried. I am adding image in question. – ryan kun Oct 11 '22 at 14:34

2 Answers2

0

So, it looks like you want to update a set of text views to display the time remaining until a number of events start. All the event start times will differ. You could set up a separate timer for each event, but you can also set up a timer that goes off every second to make the text view updates.

Take a look at this question/answer for ways to set up a timer.

Once you have the timer going, you will be able to grab the current time and calculate the time remaining until each event. You will want to save the start time associated with each event in a backing array for the RecyclerView but you could also store the start time in the view holder for each displayed event. Either way, you will need access to the event start time in the view holder binding code.

Now you will have the current time (from the timer code) and the event start time (from the backing array or just stored in the view holder), so you can compute how much time remains until the start of each event. This code can be in the adapter.

Now the question comes down to how to actually perform the update to the text views that are displayed that hold the time remaining? This code can be also be in the adapter. To find which events are currently displayed, use LinearLayoutManager#findFirstVisibleItemPosition() and for the last visible position use LinearLayoutManager#findLastVisibleItemPosition(). These methods will give you the position range of the displayed RecyclerVIew items. You can make changes to what is displayed with RecyclerView.Adapter#notifyItemChanged(int) which will cause the view holder to be rebound. Since you will just be updating a single fields once an item is displayed, you may want to use RecyclerView.Adapter#onBindViewHolder(VH,int,java.util.List%3Cjava.lang.Object%3E)) for a partial rebind.

So, in short, you will capture the start time for each event. Once you have items displayed, you will start a timer that will go off periodically. On each tick, your code will identify all shown items and call notifyItemChanged() to make the changes. The view binder code will calculate the time left until the start of each event and update the appropriate text view.

Update:

Even with a single timer, you may see lagging scrolling because of the overhead of rebinding all the view holders with new data.

Here is a sample RecyclerView adapter that addresses that issue. The main features are that it

  1. Uses a runnable and a handler to do the timing of the updates.
  2. Does a partial binding on time updates instead of a complete rebind of the entire view holder. See mTimerRunnable and public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads).

This may be sufficient to stop the lag in the scrolling. If not, there is a switch mUpdateTime that can be set to false to inhibit timer updates while scrolling. You would use a RecyclerView.OnScrollListener to control this setting. In this callback, you might just inhibit updates while scrolling or inhibit updates when scrolling past a certain speed which you would have to calculate.

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private RecyclerView mRecyclerView;
    private final List<Long> mItems;
    public boolean mUpdateTime;

    Handler mTimerHandler = new Handler();
    Runnable mTimerRunnable = new Runnable() {
        @Override
        public void run() {
            if (mUpdateTime) {
                if (mRecyclerView == null) {
                    return;
                }
                LinearLayoutManager lm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
                if (lm == null) {
                    return;
                }

                int firstPosition = lm.findFirstVisibleItemPosition();
                int lastPosition = lm.findLastVisibleItemPosition();
                Log.d("Applog", String.format("Range= %d - %d", firstPosition, lastPosition));
                notifyItemRangeChanged(firstPosition, lastPosition - firstPosition + 1, System.currentTimeMillis());
            }
            mTimerHandler.postDelayed(this, 1000);
        }
    };

    RecyclerViewAdapter(List<Long> items) {
        mItems = items;
        mUpdateTime = true;
        // Start the timer.
        mTimerHandler.post(mTimerRunnable);
    }

    @Override
    public @NonNull
    RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_message, parent, false);
        return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ItemViewHolder vh = (ItemViewHolder) holder;
        String itemText = getTimeLeft(System.currentTimeMillis(), mItems.get(position));

        vh.mEventName.setText("Event#" + position);
        vh.mTimeToEvent.setText(itemText);
        int bgColor = (position % 2 == 0) ? android.R.color.holo_blue_light : android.R.color.holo_green_light;
        holder.itemView.setBackgroundColor(holder.itemView.getContext().getResources().getColor(bgColor));
    }

    // List<Object> has the current time as its zeroth element.
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.size() == 0) {
            Log.d("Applog", "Full bind " + position);
            super.onBindViewHolder(holder, position, payloads);
        } else {
            Log.d("Applog", "Partial bind " + position);
            ItemViewHolder vh = (ItemViewHolder) holder;
            String itemText = getTimeLeft((Long) payloads.get(0), mItems.get(position));
            vh.mTimeToEvent.setText(itemText);
        }
    }

    @Override
    public int getItemCount() {
        return (mItems == null) ? 0 : mItems.size();
    }

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

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        mRecyclerView = null;
    }

    private String getTimeLeft(Long now, Long eventTime) {
        long millisLeft = eventTime - now;
        if (millisLeft < 0) {
            return "00:00:00";
        }
        return String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(millisLeft), TimeUnit.MILLISECONDS.toMinutes(millisLeft) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisLeft)),
                TimeUnit.MILLISECONDS.toSeconds(millisLeft) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisLeft)));
    }

    static class ItemViewHolder extends RecyclerView.ViewHolder {
        private final TextView mEventName;
        private final TextView mTimeToEvent;

        ItemViewHolder(View item) {
            super(item);
            mEventName = item.findViewById(R.id.eventName);
            mTimeToEvent = item.findViewById(R.id.timeRemaining);
        }
    }

    private final static int TYPE_ITEM = 1;
}

I think your solution is here somewhere.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • 1
    ok thank you very much i will try , but do you think it is going to lag. I mean my main problem is lag which i think becuase of creating mulitple timer at same time. maybe other things can be a problem. Beacause runnable mehtod also make lags. – ryan kun Oct 11 '22 at 16:09
  • yah i am right, multiple timer is not a problem it's also lag in single timer. – ryan kun Oct 12 '22 at 12:54
  • Try the partial layout code I posted. If that doesn't help, you may need to inhibit timer updates during scroll. – Cheticamp Oct 12 '22 at 13:36
  • If you aren't already, you may want to [measure performance](https://developer.android.com/topic/performance/measuring-performance) of your app to isolate the issue. – Cheticamp Oct 12 '22 at 15:22
  • minor lag when scroll slowly , no lag on fast scroll. single timer improved performance.i already measured it attaching screenshot – ryan kun Oct 12 '22 at 15:28
  • image attached in qusetion – ryan kun Oct 12 '22 at 19:15
  • sorry bro its not textview which causing lag its actually single timer sorry for misleading you. i will try partial layout code and inform you. "I/Choreographer: Skipped 1 frames! The application may be doing too much work on its main thread." this log came in every second. – ryan kun Oct 12 '22 at 19:49
  • ok i tried your code same thing skipping frames every second. – ryan kun Oct 12 '22 at 20:19
  • I think because of notifyitemrange change or notifyitemchange. – ryan kun Oct 12 '22 at 20:47
  • Those calls are causing the data to change (new times) and, probably, causing a new layout of all visible view holders and a few that are not visible according to the rules of _RecyclerView_. – Cheticamp Oct 13 '22 at 00:43
  • so, what should i do if i remove notifyitemchange line it works fine. – ryan kun Oct 13 '22 at 09:12
  • I took a closer look at your code and you are updating the time for all event in your backing array. You only need to change those events that are displayed. Take another look at my answer code on how to do that. That is one area for improvement. You took out `notifyItemChanged()` - I am wondering if displayed times still update when you are not scrolling? – Cheticamp Oct 13 '22 at 13:09
  • No, its not updating displayed time i just testing timer is the reason for frame skipping. No timer is not notifyitemchange is the reason. – ryan kun Oct 14 '22 at 06:53
-1

i took out notifyitemchange().

public class AdapterFixturesList extends RecyclerView.Adapter<AdapterFixturesList.MyViewHolder> {
    private List<BeanHomeFixtures> mListenerList;
    Context mContext;
    CountDownTimer timer;
    List<MyViewHolder> lstHolders;

public AdapterFixturesList(final List<BeanHomeFixtures> mListenerList, Context context) {
        mContext = context;
        this.mListenerList = mListenerList;
        lstHolders = new ArrayList<>();

        long maxTime = 0;
        for (BeanHomeFixtures item : mListenerList) {
            if (!item.getTime().equalsIgnoreCase("")) {
                maxTime = Long.parseLong(item.getTime());
            }
        }
        long FlashCount = Long.parseLong(String.valueOf(maxTime));
        long millisUntilFinished = FlashCount * 1000;

        timer = new CountDownTimer(millisUntilFinished, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {
                    synchronized (lstHolders){
                        for (MyViewHolder holder : lstHolders) {

                            long Days = TimeUnit.HOURS.toDays(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                            long Hours = TimeUnit.MILLISECONDS.toHours(millisUntilFinished) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(millisUntilFinished));
                            long Minutes = TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished));
                            long Seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished));

                            String format = "%1$02d";
                            holder.tv_TimeRemained.setText(String.format(format, Days) + ":" + String.format(format, Hours) + ":" + String.format(format, Minutes) + ":" + String.format(format, Seconds));
                        }
                    }
                }
                @Override
                public void onFinish() {
                }
            }.start();

        }
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) { 
        synchronized (lstHolders) {
            lstHolders.add(holder);
        }
    }
}

i know it shows same time in every item but for just testing purpose. it still lag on sroll. it's not frame skipping on every second like i have told on updated question.

ryan kun
  • 1
  • 1
  • 2
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 17 '22 at 15:19