【问题标题】:Multiple count down timers in RecyclerView flickering when scrolledRecyclerView中的多个倒计时计时器在滚动时闪烁
【发布时间】:2016-08-11 08:16:16
【问题描述】:

我已经为片段活动中的每个 RecyclerView 项目实现了倒计时。倒计时计时器显示剩余的到期时间。倒数计时器工作正常,但向上滚动时开始闪烁。搜索了很多,但没有得到很好的参考。谁能帮帮我?

这是我的 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);

    }

}

这是我在适配器中使用的 xml 文件

<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>

这是我的 RecyclerView 的屏幕截图

【问题讨论】:

    标签: android android-recyclerview countdowntimer


    【解决方案1】:

    这个问题很简单。

    RecyclerView重复使用holder,每次调用bind更新其中的数据。

    由于每次绑定任何数据时都会创建一个CountDownTimer,因此您最终将有多个计时器更新同一个ViewHolder

    最好将CountDownTimer 中的CountDownTimer 作为参考移动,在绑定数据(如果已启动)之前取消它并重新安排到所需的持续时间。

    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) {
            ...
        }
    }
    

    这样您将在启动另一个计时器之前取消该ViewHolder 的任何当前计时器实例。

    【讨论】:

    • @Lupsaa 我注意到其他解决方案使用 Handler 和 Runnable 方法和对象来更新主线程之外的新线程。我想知道与您的解决方案相比,这些解决方案是否更适合 RecyclerView 列表中的大量项目。你能评论吗?请参阅 Gergely Kőrössy 的答案:stackoverflow.com/questions/32166375/… 和 H Raval 的答案:stackoverflow.com/questions/35860780/…
    • @AJW 由于持有人正在回收,列表中的大量项目无关紧要。您可以有一个包含 10.000 个项目的列表,如果一次只有 10 个可见,那么您可能会有大约 15 个被重用的持有者实例。在这里,我只是通过解释他遇到闪烁的原因来提供这个问题的答案。我确信有多种方法,但在我看来,使用 Thread / Handler 太过分了。此解决方案不会同时更新持有者,因为每个计时器都是在绑定视图时启动的。
    • @Lupsaa Ahh,所以由于 Viewholder 进行回收,因此在任何时候滚动期间只有 15 个持有者实例,所以这不应该压倒主线程吗?并且当持有者退出视图时,该视图的计时器将被取消,因此主线程只会处理相对较少数量的计时器?
    • @AJW 这是正确的。这就是为什么您必须取消视图持有者计时器的前一个实例(如果存在)并启动一个新实例。
    • @Lupsaa 非常好,感谢您回来查看回复,干杯。
    【解决方案2】:

    正如 Andrei Lupsa 所说,您应该在 ViewHolder 中保留 CountDownTimer 引用,如果您不想在滚动时重置计时器 (onBindViewHolder),您应该检查 CountDownTimer 引用是否在 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) {
    
              .... 
      }
    }
    

    【讨论】:

      【解决方案3】:

      我能想到两种类型的解决方案,它们没有使用CountDownTimer

      1. 使用postDelayed 方法制作处理程序并在其中调用notifyDataSetChanged()。在您的适配器中计算时间。就像下面的代码。

      在适配器类的构造函数中

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

      在你身上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. 如果您认为notifyDataSetChanged() 每毫秒都是错误的,那么这里是第二种选择。使用Runnable 实现创建类并使用setTag()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();}
                 }
         }
        

      并像这样在onBindViewHolder中使用

      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);
      }
      

      【讨论】:

        猜你喜欢
        • 2022-11-26
        • 1970-01-01
        • 2022-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-14
        • 1970-01-01
        相关资源
        最近更新 更多