【问题标题】:What could cause timers to flash in RecyclerView?什么可能导致计时器在 RecyclerView 中闪烁?
【发布时间】:2019-07-28 17:20:46
【问题描述】:

我正在尝试为帖子上显示的用户生成帖子创建一个计时器。我正在使用 Firestore 存储有关帖子的所有信息。

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

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

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

    }

【问题讨论】:

  • “不同时间之间的闪烁”很难理解。强调。
  • 已澄清。这样更好吗?

标签: java android google-cloud-firestore countdowntimer


【解决方案1】:

首先,您应该知道 RecyclerView 会在您每次滚动经过屏幕上最后一个可见项目时重用其视图。

根据您每次用户滚动经过 屏幕上最后一个可见的项目或帖子,因为它位于 Recycler查看原始列表中的第一项甚至其他项 被滚动过去被回收,下一个项目成为新的 第一项,但填充了不同的后值 预计。

但请记住,您的代码实例化了一个新的 CountdownTimer 添加到列表中的每个新帖子(其中 50 个帖子等于 50 倒计时和 50 个帖子并不一定意味着 50 个视图,因为 一些视图可能会被 RecyclerView 回收;你得到了逻辑),因此它允许 多个 CountdownTimer 访问同一视图的可能性 导致闪存,并且肯定会导致内存泄漏,从而导致 当用户长时间连续滚动时出现 OutOfMemoryError 列表。

推荐实施

在 ViewHolder 类中创建 CountDownTimer

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

  }

【讨论】:

    【解决方案2】:

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

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

    声明两个数组,如下所示。

    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 的计时器实例。

    【讨论】:

    • 所以我目前在ViewHolder 中有计时器,但是从你的回答中我会将它移到RecyclerView 的适配器上,对吗?
    • ViewHolder 也应该有计时器实例。否则,从适配器中,您实际上无法将计时器设置为ViewHodler 中的计时器实例。是的,你是对的,你需要在你的适配器中移动定时器的实现。
    • 似乎不是滚动触发行为。即使只有一篇文章,它现在也在这样做。
    • 不应该。这个想法不是在每次分配一个计时器时都创建一个新的计时器实例。但是,更多地查看您的代码可能会有所帮助。请在您的问题中发布一些相关代码。
    • 我添加了带有onBindViewHolder的适配器我不确定是否有任何其他代码与此问题相关。
    【解决方案3】:

    您没有具体说明您的逻辑在适配器中的确切位置。

    逻辑应该在onBindViewHolder(…) 内。您可以使用viewHolder.findViewById(...) 访问小部件

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

    【讨论】:

    • PostViewHolder 在包含onBindViewHolder 的片段中运行。我没有包含完整的PostViewHolder,因为其中的很多代码是不相关的。
    • 另外,当我尝试将 timer.setText(…) 调用包装在 runOnUiThread() 中时,我得到一个无法解析符号错误
    猜你喜欢
    • 1970-01-01
    • 2022-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 2013-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多