android android-recyclerview countdowntimer

android - MĂșltiples temporizadores de cuenta regresiva en RecyclerView parpadeando cuando se desplaza



android-recyclerview countdowntimer (3)

Como Andrei Lupsa dijo que debe tener la referencia CountDownTimer en su ViewHolder, si no desea restablecer el temporizador al desplazarse (onBindViewHolder), debe verificar si la referencia CountDownTimer es nula o no en onBindViewHolder:

public void onBindViewHolder(final FeedViewHolder holder, final int position) { ... if (holder.timer == null) { holder.timer = new CountDownTimer(expiryTime, 500) { ... }.start(); } } public static class FeedViewHolder extends RecyclerView.ViewHolder { ... CountDownTimer timer; public FeedViewHolder(View itemView) { .... } }

He implementado un temporizador de cuenta regresiva para cada elemento de RecyclerView que se encuentra en una actividad de fragmento. El temporizador de cuenta regresiva muestra el tiempo restante para el vencimiento. El temporizador de cuenta regresiva funciona bien, pero cuando se desplaza hacia arriba comienza a parpadear. Busqué mucho pero no obtuve la buena referencia. ¿Alguien puede ayudarme?

Este es mi adaptador RecyclerView

public class MyOfferAdapter extends RecyclerView.Adapter<MyOfferAdapter.FeedViewHolder>{ private final Context mContext; private final LayoutInflater mLayoutInflater; private ArrayList<Transactions> mItems = new ArrayList<>(); private ImageLoader mImageLoader; private String imageURL; private View mView; private String mUserEmail; public MyOfferAdapter(Context context) { mContext = context; mLayoutInflater = LayoutInflater.from(context); VolleySingleton mVolley = VolleySingleton.getInstance(mContext); mImageLoader = mVolley.getImageLoader(); } public void addItems(ArrayList<Transactions> items,String userEmail) { int count = mItems.size(); mItems.addAll(items); mUserEmail = userEmail; notifyItemRangeChanged(count, items.size()); } @Override public FeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { mView = mLayoutInflater.inflate(R.layout.my_feed_item_layout, parent, false); return new FeedViewHolder(mView); } @Override public void onBindViewHolder(final FeedViewHolder holder, final int position) { holder.desc.setText(mItems.get(position).getDescription());//replace by title holder.scratchDes.setText(mItems.get(position).getScratchDescription()); long timer = mItems.get(position).getTimerExpiryTimeStamp(); Date today = new Date(); final long currentTime = today.getTime(); long expiryTime = timer - currentTime; new CountDownTimer(expiryTime, 500) { public void onTick(long millisUntilFinished) { long seconds = millisUntilFinished / 1000; long minutes = seconds / 60; long hours = minutes / 60; long days = hours / 24; String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60; holder.timerValueTimeStamp.setText(time); } public void onFinish() { holder.timerValueTimeStamp.setText("Time up!"); } }.start(); } @Override public int getItemCount() { return mItems.size(); } public static class FeedViewHolder extends RecyclerView.ViewHolder { TextView desc; TextView scratchDes; TextView timerValueTimeStamp; ImageView feedImage; CardView mCv; public FeedViewHolder(View itemView) { super(itemView); mCv = (CardView) itemView.findViewById(R.id.cv_fil); desc = (TextView) itemView.findViewById(R.id.desc_tv_fil); feedImage = (ImageView) itemView.findViewById(R.id.feed_iv_fil); scratchDes = (TextView) itemView.findViewById(R.id.tv_scratch_description); timerValueTimeStamp = (TextView) itemView.findViewById(R.id.tv_timer_value_time_stamp); } }

Y este es mi archivo xml usado en el adaptador

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/cv_fil" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/card_margin" android:layout_gravity="center" app:cardUseCompatPadding="true" app:cardElevation="4dp" android:elevation="6dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/feed_iv_fil" android:layout_width="match_parent" android:layout_height="200dp" android:layout_alignParentTop="true" android:scaleType="fitXY" android:tint="@color/grey_tint_color" /> <TextView android:id="@+id/tv_scratch_description" style="@style/ListItemText" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="casul shoes" android:fontFamily="sans-serif-light" android:padding="10dp" /> <TextView android:id="@+id/tv_timer_value_time_stamp" style="@style/CardTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> <TextView android:id="@+id/desc_tv_fil" style="@style/VendorNameText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/feed_iv_fil" android:textColor="#3f3e3f" android:padding="10dp" /> </RelativeLayout> </android.support.v7.widget.CardView>

Y esta es la captura de pantalla de mi RecyclerView


Este problema es simple.

RecyclerView reutiliza los titulares, llamando a bind cada vez para actualizar los datos en ellos.

Como crea un CountDownTimer cada vez que se vinculan datos, terminará con varios temporizadores que actualizan el mismo ViewHolder .

Lo mejor aquí sería mover el CountDownTimer en FeedViewHolder como referencia, cancelarlo antes de vincular los datos (si se inició) y reprogramar la duración deseada.

public void onBindViewHolder(final FeedViewHolder holder, final int position) { ... if (holder.timer != null) { holder.timer.cancel(); } holder.timer = new CountDownTimer(expiryTime, 500) { ... }.start(); } public static class FeedViewHolder extends RecyclerView.ViewHolder { ... CountDownTimer timer; public FeedViewHolder(View itemView) { ... } }

De esta manera, cancelará cualquier instancia de temporizador actual para ese ViewHolder antes de iniciar otro temporizador.


Hay dos tipos de soluciones que puedo pensar y son sin usar la clase CountDownTimer

  1. Haga Handle con el método postDelayed y llame a notifyDataSetChanged() en eso. En su adaptador haga el cálculo del tiempo. como debajo del código.

en Constructor de clase de adaptador

final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { notifyDataSetChanged(); handler.postDelayed(this, 1000); } }, 1000);

y en ti el método onBindViewHolder

public void onBindViewHolder(final FeedViewHolder holder, final int position) { updateTimeRemaining(endTime, holder.yourTextView); } private void updateTimeRemaining(long endTime, TextView yourTextView) { long timeDiff = endTime - System.currentTimeMillis(); if (timeDiff > 0) { int seconds = (int) (timeDiff / 1000) % 60; int minutes = (int) ((timeDiff / (1000 * 60)) % 60); int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24); yourTextView.setText(MessageFormat.format("{0}:{1}:{2}", hours, minutes, seconds)); } else { yourTextView.setText("Expired!!"); } }

  1. si crees que notifyDataSetChanged() cada milisegundo es incorrecto, aquí hay una segunda opción. Cree una clase con implementación Runnable y Runnable en el adaptador con el setTag() y getTag()

    public class DownTimer implements Runnable { private TextView yourTextView; private long endTime; private DateFormat formatter = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); private Handler handler = new Handler(); public DownTimer(long endTime, TextView textView) { this.endTime = endTime; yourTextView = textView; formatter.setTimeZone(TimeZone.getTimeZone("UTC")); } public void setEndTime(long endTime) { this.endTime = endTime; } public void start() { if (handler != null) handler.postDelayed(this, 0); } public void cancel() { if (handler != null) handler.removeCallbacks(this); } @Override public void run() { if (handler == null) return; if (yourTextView == null && endTime == 0) return; long timeDiff = endTime - System.currentTimeMillis(); try { Date date = new Date(timeDiff); yourTextView.setText(formatter.format(date)); }catch (Exception e){e.printStackTrace();} } }

y usar en onBindViewHolder como este

if (holder.yourTextView.getTag() != null) { DownTimer downTimer = (DownTimer) holder.yourTextView.getTag(); downTimer.cancel(); downTimer.setEndTime(endTime); downTimer.start(); } else { DownTimer downTimer = new DownTimer(endTime, holder.yourTextView); downTimer.start(); holder.yourTextView.setTag(downTimer); }