web-dev-qa-db-ja.com

スクロールするとRecyclerViewの複数のカウントダウンタイマーがちらつく

フラグメントアクティビティにある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のスクリーンショットです enter image description here

14
Hasan shaikh

この問題は簡単です。

RecyclerViewはホルダーを再利用し、毎回bindを呼び出してホルダー内のデータを更新します。

データがバインドされるたびにCountDownTimerを作成するため、複数のタイマーが同じViewHolderを更新することになります。

ここでの最善の方法は、CountDownTimer内のFeedViewHolderを参照として移動し、データをバインドする前にキャンセルして(開始されている場合)、目的の期間に再スケジュールすることです。

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の現在のタイマーインスタンスをキャンセルします。

28
Andrei Lupsa

Andrei LupsaがViewHolderでCountDownTimer参照を保持する必要があると述べたように、スクロール時にタイマーをリセットしたくない場合(onBindViewHolder)、onBindViewHolderでCountDownTimer参照がnullかどうかを確認する必要があります。

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

          .... 
  }
}
5

私が考えることができる解決策には2つのタイプがあり、それらは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()が1ミリ秒ごとに間違っていると思われる場合は、2番目のオプションを使用してください。 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);
}
_
0