0

私は現在、そのコア機能で、リサイクラービューで複数のカウントダウンタイマーを処理するアプリを開発中です。recyclerviewのカウントダウンタイマーを処理する - android

ここで実装した方法は、作成されたすべてのViewHolderに対して単にカウントダウンタイマーを開始することです。私の問題は時々、タイマーが終了するたびに、他のすべてのタイマーは変な動作をしています。

たとえば、タイマーが起動するたびに、そのタイマーのViewHolderは、上記のクラスが参照しているCardView UIコンポーネントの背景を変更します。

実際には、1つのタイマーが稼動している場合、他のビューホルダーにアクセスして変更しています。

ここに私のアダプタコードがあります。私は可能な限り文書化しようとしました:

package bikurim.silverfix.com.bikurim.adapters; 

import android.animation.Animator; 
import android.animation.AnimatorListenerAdapter; 
import android.animation.AnimatorSet; 
import android.animation.ObjectAnimator; 
import android.content.Context; 
import android.content.res.ColorStateList; 
import android.os.CountDownTimer; 
import android.support.v7.widget.CardView; 
import android.support.v7.widget.RecyclerView; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 
import android.view.animation.Animation; 
import android.view.animation.AnimationUtils; 
import android.widget.Filter; 
import android.widget.Filterable; 
import android.widget.ImageButton; 
import android.widget.ImageView; 
import android.widget.RelativeLayout; 
import android.widget.TextView; 

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.Set; 
import java.util.concurrent.TimeUnit; 

import bikurim.silverfix.com.bikurim.Constants; 
import bikurim.silverfix.com.bikurim.models.Family; 
import bikurim.silverfix.com.bikurim.R; 
import bikurim.silverfix.com.bikurim.utils.CountDownManager; 
import bikurim.silverfix.com.bikurim.utils.TimerEventListener; 

public class FamiliesAdapter extends RecyclerView.Adapter<FamiliesAdapter.FamilyViewHolder> implements Filterable, TimerEventListener { 

    // last position holds the last position of the element that was added, for animation purposes 
    private static int lastPosition = -1; 
    private Context context; 
    private ArrayList<Family> families; 
    private ArrayList<Family> dataSet; 
    private FamilyFilter filter; 

    private CountDownManager countDownManager; 

    public FamiliesAdapter(Context context, ArrayList<Family> families) { 
     this.context = context; 
     this.dataSet = families; 
     this.families = dataSet; 

     countDownManager = new CountDownManager(Constants.Values.TIME_INTERVAL, this); 
     countDownManager.start(); 
    } 


    @Override 
    public FamilyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
     // Inflating the view from a given layout resource file 
     View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.family_list_item, parent, false); 
     final FamilyViewHolder pvh = new FamilyViewHolder(v); 

     return pvh; 
    } 

    @Override 
    public void onBindViewHolder(final FamilyViewHolder holder, int position) { 
     // Binds the Family object to the holder view 
     Family family = families.get(position); 
     holder.bindData(family); 

     countDownManager.addTimer(holder); 
     // Sets animation on the given view, in case it wasn't displayed before 
     setSlideAnimation(holder.cardView, position, false); 
    } 

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

    /* Gets the current the filter object */ 
    @Override 
    public Filter getFilter() { 
     if (filter == null) 
      filter = new FamilyFilter(); 
     return filter; 
    } 

    /* The following 2 methods are an implementations of the ViewHolderListener. 
    * Inside every view holder lies an instance of ViewHolderListener, for communication between the two*/ 

    public void startAnimationOnItem(FamilyViewHolder holder, boolean isEndAnimation) { 
     setSlideAnimation(holder.cardView, holder.getAdapterPosition(), isEndAnimation); 
    } 

    /* Changes the UI of a holder to a time's up view with a flickering ImageButton and a TextView*/ 

    @Override 
    public void onFinish(FamilyViewHolder holder) { 
     // Switches between the clock icon to the alarm icon 
     holder.clock.setVisibility(View.GONE); 
     holder.remove.setVisibility(View.VISIBLE); 

     RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); 
     params.addRule(RelativeLayout.RIGHT_OF, holder.remove.getId()); 
     params.addRule(RelativeLayout.CENTER_VERTICAL); 
     holder.timeLeft.setLayoutParams(params); 
     holder.timeLeft.setText(context.getString(R.string.time_up_message)); 

     setFadeAnimation(holder.remove); 
     holder.cardView.setBackgroundResource(R.color.cardview_light_background); 
     startAnimationOnItem(holder, true); 

     holder.family.timeLeft = 0; 
    } 

    @Override 
    public void onLessThanMinute(FamilyViewHolder holder) { 
     holder.cardView.setBackgroundResource(R.color.time_up_bg); 
     holder.isBackgroundChanged = true; 
    } 

    /* Starts a slide in animation for a given Card View */ 
    private void setSlideAnimation(View viewToAnimate, int position, boolean isEndAnimation) { 
     Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left); 
     if (!isEndAnimation) { 
      if (position > lastPosition) { 
       viewToAnimate.startAnimation(animation); 
       lastPosition = position; 
       return; 
      } 
     } 
     viewToAnimate.startAnimation(animation); 
    } 

    private void setFadeAnimation(View viewToAnimate) { 
     ObjectAnimator fadeOut = ObjectAnimator.ofFloat(viewToAnimate, "alpha", 1f, .1f); 
     fadeOut.setDuration(750); 
     ObjectAnimator fadeIn = ObjectAnimator.ofFloat(viewToAnimate, "alpha", .1f, 1f); 
     fadeIn.setDuration(750); 

     final AnimatorSet mAnimationSet = new AnimatorSet(); 

     mAnimationSet.play(fadeIn).after(fadeOut); 

     mAnimationSet.addListener(new AnimatorListenerAdapter() { 
      @Override 
      public void onAnimationEnd(Animator animation) { 
       super.onAnimationEnd(animation); 
       mAnimationSet.start(); 
      } 
     }); 
     mAnimationSet.start(); 
    } 

    public void removeData(int pos, FamilyViewHolder holder) { 
     // Sets the last position to the given deleted position for animation purposes 
     lastPosition = pos; 

     // Removes the family object from the data set 
     families.remove(pos); 
     notifyItemRemoved(pos); 
     notifyItemRangeChanged(pos, getItemCount()); 

     // Cancels the the timer and removes it from the entry set 
     countDownManager.cancelTimer(holder); 
    } 


    /* Cancels the timers and clears the entry set */ 
    public void cancelTimers() { 
     countDownManager.reset(); 
     countDownManager.stop(); 
    } 

    /* Clears the adapter's data and resets the last position to -1 */ 
    public void clearData() { 
     cancelTimers(); 
     filter = null; 
     lastPosition = -1; 
    } 

    /* The official view holder of the adapter. Holds references to the relevant views inside the layout, and */ 
    public static class FamilyViewHolder extends RecyclerView.ViewHolder { 
     public boolean isBackgroundChanged; 

     public CardView cardView; 
     public TextView personLname; 
     public TextView timeLeft; 
     public TextView visitors; 
     public ImageView clock; 
     public ImageButton remove; 

     public Family family; 

     private ColorStateList colorStateList; 

     public FamilyViewHolder(View itemView) { 
      super(itemView); 
      // isBackgroundChanged represents whether the holder is under 60 seconds or not 
      isBackgroundChanged = false; 

      // Getting the references for the UI components 
      cardView = (CardView) itemView.findViewById(R.id.cv); 
      personLname = (TextView) itemView.findViewById(R.id.person_Lname); 
      visitors = (TextView) itemView.findViewById(R.id.visitors); 
      timeLeft = (TextView) itemView.findViewById(R.id.person_timeLeft); 
      clock = (ImageView) itemView.findViewById(R.id.clock); 
      remove = (ImageButton) itemView.findViewById(R.id.time_up); 

      // Sets a reference to the old colors of the text view 
      colorStateList = timeLeft.getTextColors(); 
     } 

     public void bindData(Family item) { 
      family = item; 
      personLname.setText(family.lastName); 
      visitors.setText("מבקרים: " + family.visitorsNum); 
     } 

     public ColorStateList getOriginalColors() { 
      return colorStateList; 
     } 

    } 

    /* Filter class that queries the constraint on the data set every whenInMillis the user types a key */ 
    private class FamilyFilter extends Filter { 
     @Override 
     protected FilterResults performFiltering(CharSequence constraint) { 
      FilterResults results = new FilterResults(); 
      if (constraint.length() == 0 || constraint == null) { 
       results.values = dataSet; 
       results.count = dataSet.size(); 
      } else { 
       ArrayList<Family> queryResults = new ArrayList<Family>(); 
       for (Family f : dataSet) { 
        if (constraint.charAt(0) == f.lastName.toUpperCase().indexOf(0)) { 
         queryResults.add(f); 
        } else if (f.lastName.toUpperCase().contains(constraint.toString().toUpperCase())) { 
         queryResults.add(f); 
        } 
       } 
       results.values = queryResults; 
       results.count = queryResults.size(); 
      } 
      return results; 
     } 

     @Override 
     protected void publishResults(CharSequence constraint, FilterResults results) { 
      // Now we have to inform the adapter about the new list filtered 
      synchronized (families) { 
       families = (ArrayList<Family>) results.values; 
      } 
      notifyDataSetChanged(); 
     } 
    } 
} 

タイマーの終了時にビューホルダーがスクランブルされるのはなぜですか?

更新:これを少しずつ実装しようとしました。複数のカウントダウンを使用する代わりに、私は更新する必要があるホルダーの配列を保持するものだけを使用します。しかし、問題は同じままです。私のCountDownManagerクラスは次のとおりです:

私は何か間違っていますが、私は問題を特定できないようです。

答えて

1

カウントダウンタイマーからカウントダウンタイマーをキャンセルすることはできません。これは、キャンセルを呼び出すと次のイベントがすでにスケジュールされているため、そのイベントがトリガーされたときに次のイベントをトリガーするためです。あなたの問題はおそらく、タイマーがキャンセルされていないためです。

私はこれをやらなければならず、私のアプローチはすべてのビューに1つのカウントダウンタイマーを使用することでした。あなたはnotifyDataSetChangedを呼びたくないので、すべてをリフレッシュします。代わりに、時間が必要なすべての表示可能なビューに対するWeakReferenceの配列を保持するカスタムcount up timerを作成します。これらのすべてのビューを1度に更新することができます。ビューのタグに終了時刻または開始時刻を入れることができます。したがって、カウントアップでタグの終了時刻を取得して計算を実行します。

ライフサイクルの方法でタイマーを開始および停止することができます。お役に立てれば。

+0

お返事ありがとうございます、アリ!しかし、どのタイマーがお互いに違うのか、あなたが言うことをどのように実装できますか?私のためにこれを明確にしてください。 –

+0

彼らは皆、それを上げたり下ろしたりしていませんか?カウントアップまたはカウントダウンタイマーは、すべてのビューを循環させてテキストを更新するよう指示することのみを目的としています。 – Ali

+0

私が言ったようにCountDownManagerを実装しようとしました。私は自分のアダプタのコードを更新し、CountDownManagerの新しいコードを投稿に追加しました。それを見ていただけますか?私はまだ同じ問題を抱えている - ViewHoldersは他のViewHoldersからのデータでスクランブルを受ける –

関連する問題