什么会导致计时器在 RecyclerView 中闪烁?

What could cause timers to flash in RecyclerView?

我正在尝试为 post 上显示的用户生成的 post 创建一个计时器。我正在使用 Firestore 存储有关 posts.

的所有信息

我的代码确实显示了计时器,但是当多个 post 由多个用户创建时,计时器会在正确时间和另一个倒计时之间闪烁,据我所知这是不相关的。这是我拥有的:

这就是我创建每个 post 的方式。

post.setTimer(data.getStringExtra("timerDuration"));
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, setEndDate());
Date endtime = cal.getTime();
post.setTimerEndDate(endtime);
addPhotoInfoToDatabase();
finish();

我的片段适配器:

adapter = new FirestoreRecyclerAdapter<Post, PostViewHolder>(options) {
    @Override
    protected void onBindViewHolder(@NonNull PostViewHolder postViewHolder, int position, @NonNull Post post) {
        postViewHolder.setPost(post);
    }

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

如果您需要更多信息,请告诉我。感谢您的帮助。

编辑: 我的应用程序还包含一个 ViewPager,我注意到当我离开问题计时器并向后滑动时,问题已解决。

更新:

 void setPost(final Post post) {
        ImageView imageView1 = view.findViewById(R.id.firstImageCardView);
        ImageView imageView2 = view.findViewById(R.id.secondImageCardView);
        ImageView profilePic = view.findViewById(R.id.user_image);
        TextView username = view.findViewById(R.id.user_name);
        final TextView timer = view.findViewById(R.id.timer);
        final Button chooseRight = view.findViewById(R.id.choose_right);
        final Button chooseLeft = view.findViewById(R.id.choose_left);
        final Button follow = view.findViewById(R.id.follow);
        String displayName;
        final String userId = currentUser.getUid();
        final String postId = post.getUserId();

        setProfilePicture(post);

        final DocumentReference docRefUsers = db
                .collection("users")
                .document(currentUser.getUid());

        final DocumentReference docRefFollowing = db
                .collection("following")
                .document(currentUser.getUid())
                .collection("UserIsFollowing")
                .document(post.getUserId());

        final DocumentReference docRefPosts = db
                .collection("posts")
                .document(post.getUserId())
                .collection("userPosts")
                .document(post.getDate().toString());


        if (userId.equals(postId)) {
            displayName = "Me";
            chooseLeft.setEnabled(false);
            chooseRight.setEnabled(false);
            follow.setText("");
            follow.setEnabled(false);
        } else if (post.getUsername() == null) {
            displayName = "Anonymous";
        } else {
            displayName = post.getUsername();
        }


        if (post.getProfilePicture() != null) {
            Picasso.get().load(post.getProfilePicture())
                    .transform(new CircleTransformActivity())
                    .fit()
                    .centerCrop()
                    .into(profilePic);
            username.setText(displayName);
        } else {
            Picasso.get().load(R.drawable.blank_profile_pic)
                    .transform(new CircleTransformActivity())
                    .fit()
                    .into(profilePic);
        }

/***********************************************************/
        if(post.getTimerEndDate() != null) {
            Date date = java.util.Calendar.getInstance().getTime();

            long currentTime = date.getTime();
            long endTime = post.getTimerEndDate().getTime();
            long timeLeft = endTime - currentTime;

            new CountDownTimer(timeLeft, 1000) {

                @SuppressLint("DefaultLocale")
                public void onTick(long millisUntilFinished) {

                    timer.setText(String.format("%02d:%02d:%02d",
                            (int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
                            (int) ((millisUntilFinished / (1000 * 60)) % 60),
                            (int) (millisUntilFinished / 1000) % 60));
                    //here you can have your logic to set text to edittext
                }

                public void onFinish() {
                    timer.setText("done!");
                }

            }.start();
        }

        Picasso.get()
                .load(post.getImageUrl_1())
                .fit()
                .transform(new RoundedCornersTransformation(30, 30))
                .into(imageView1);


        Picasso.get()
                .load(post.getImageUrl_2())
                .fit()
                .transform(new RoundedCornersTransformation(30, 30))
                .into(imageView2);

        if (!postId.equals(userId)) {
            docRefFollowing.addSnapshotListener(new EventListener<DocumentSnapshot>() {
                @Override
                public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
                    if (documentSnapshot != null && documentSnapshot.exists()) {
                        follow.setText("Following");
                        follow.setEnabled(false);
                    }
                }
            });
        }

        follow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final Map<String, Object> following = new HashMap<>();
                following.put("Exists", true);

                docRefFollowing.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                        if (task.isSuccessful()) {
                            docRefFollowing.set(following);
                            docRefUsers.update("following", FieldValue.increment(1));
                        }
                    }
                });

                db.collection("users")
                        .document(postId)
                        .update("followers", FieldValue.increment(1));


                follow.setText("Following");
            }
        });


        if (!postId.equals(userId)) {
            docRefPosts.collection("voted")
                    .document(currentUser.getUid()).addSnapshotListener(new EventListener<DocumentSnapshot>() {
                @Override
                public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
                    if (documentSnapshot != null && documentSnapshot.exists()) {
                        whichVoted = documentSnapshot.get("votedFor").toString();
                    }
                    if (whichVoted != null) {
                        switch (whichVoted) {
                            case ("left"):
                                chooseLeft.setEnabled(false);
                                chooseRight.setEnabled(true);
                                break;
                            case ("right"):
                                chooseRight.setEnabled(false);
                                chooseLeft.setEnabled(true);
                                break;
                        }
                    } else {
                        chooseRight.setEnabled(true);
                        chooseLeft.setEnabled(true);
                    }
                }
            });
        }
        chooseLeft.setText(post.getLeftText());
        chooseRight.setText(post.getRightText());

        chooseLeft.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                chooseLeft.setEnabled(false);
                chooseRight.setEnabled(true);
                upLeft(docRefPosts, chooseLeft);
            }
        });

        chooseRight.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                chooseRight.setEnabled(false);
                chooseLeft.setEnabled(true);
                upRight(docRefPosts, chooseRight);
            }
        });

    }

每次当您在 RecyclerView 中的项目在滚动后被回收时创建一个新的计时器。每次滚动项目时,timeLeft 变量也会发生变化,这会初始化一个新的 Timer,我猜这就是闪烁的原因。

因此,我建议在您的适配器中使用一个数组。这是用来存放你所有的 CountDownTimer 的。在适配器的构造函数中,正确初始化它以具有正确的值。让我们采用如下示例实现。

像下面这样声明两个数组。

CountDownTimer[] timers = new CountDownTimer[data.size()];

现在在适配器的构造函数中执行如下操作。

// Let us assume the list of Data is dataItems which is being passed to your adapter.
// Your constructor might take other parameters
public MyAdapter(ArrayList<Data> dataItems) {

    for(int i = 0; i < dataItems.size(); i++) {
        long timeLeft = getTimeLeftValue(dataItems.get(i));
        timers[i] = new CountDownTimer(timeLeft, 1000) {
                @SuppressLint("DefaultLocale")
                public void onTick(long millisUntilFinished) {
                    timer.setText(String.format("%d:%d:%d",
                            (int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
                            (int) ((millisUntilFinished / (1000 * 60)) % 60),
                            (int) (millisUntilFinished / 1000) % 60));
                    //here you can have your logic to set text to edittext
                }

                public void onFinish() {
                    timer.setText("done!");
                }
            }.start();
    }
}

private long getTimeLeftValue(Data post) {
    Date date = java.util.Calendar.getInstance().getTime();
    long currentTime = date.getTime();
    long endTime = post.getTimerEndDate().getTime();
    timeLeft = endTime - currentTime;

    return timeLeft.
}

现在,在您的 onBindViewHolder 中,您需要按如下方式设置计时器。

timerInstance = timers.get(position);

我假设 RecyclerView 中的每个项目都有一个显示 CountDownTimer 的计时器实例。

您没有指定您的逻辑在适配器中的确切位置。

逻辑应该在onBindViewHolder(…)以内。您可以使用 viewHolder.findViewById(...)

访问小部件

此外,CountDownTimer 是它自己的线程,编辑 TextView 或任何小部件都可能导致异常。将 timer.setText(…) 调用包装在 runOnUiThread()

首先,您应该知道 RecyclerView 会在您每次滚动到屏幕上最后一个可见项目时重新使用其查看器。

Based on your implementation for every time a user scroll past the last visible item or post on the screen, because it is inside a RecyclerView the original first-item on the list and even other items being scrolled past are recycled and the next item becomes the new first-item but being populated with a different post-values as expected.

But remember your code instantiates a new CountdownTimer for every new post added to the list (where 50 posts equals 50 Countdowntimers and 50 posts does not necessarily mean 50 views because some of the views would probably be recycled by the RecyclerView; you get the logic), hence it allows the possibility for multiple CountdownTimer accessing same view which causes the flash and will surely lead to a memory leak causing an OutOfMemoryError when the user keeps scrolling continuously on a long list.

推荐实施

在 ViewHolder 中创建 CountDownTimer Class

public static class PostViewHolder extends RecyclerView.ViewHolder {
    ...
    CountDownTimer countdowntimer;

    public FeedViewHolder(View itemView) {
        ...
    }
}

在 setPost 方法中访问定时器

   void setPost(final Post post) { 

          ...

        long currentTime = date.getTime();
        long endTime = post.getTimerEndDate().getTime();
        long timeLeft = endTime - currentTime;

        if (countdowntimer != null) {
           //there is an existing timer so we cancel it to prevent duplicates
           countdowntimer.cancel();
           }
        countdowntimer = new CountDownTimer(timeLeft, 1000) {

            @SuppressLint("DefaultLocale")
            public void onTick(long millisUntilFinished) {

                timer.setText(String.format("%02d:%02d:%02d",
                        (int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
                        (int) ((millisUntilFinished / (1000 * 60)) % 60),
                        (int) (millisUntilFinished / 1000) % 60));
                //here you can have your logic to set text to edittext
            }

            public void onFinish() {
                timer.setText("done!");
            }

        }.start();

  }