带倒数计时器的 ListView。滚动列表视图时调光器闪烁

ListView with Coundown timer. Timer flickers when scrolling the listview

在我的应用程序中,我必须为列表视图中的每个项目显示倒数计时器。我已经能够使用 CountDownTimer 做到这一点。但是,问题是向上或向下滚动列表视图时,计时器开始闪烁。找了很多都没找到解决办法

我的适配器class

public class BuyListingListAdapter extends BaseAdapter  {

private Context mContext;
private ArrayList<VehicleAttributes> mVehicleList = new ArrayList<VehicleAttributes>();
private Date currentDate;
//Saving position in map to check if timer has been    //started for this row
private HashMap<Integer, Boolean> timerMap = new HashMap<Integer, Boolean>();

public BuyListingListAdapter(Context context,
        ArrayList<VehicleAttributes> vehicleList, boolean showGridView) {
    this.mContext = context;
    this.mVehicleList = vehicleList;
    this.showGridView = showGridView;
    currentDate = new Date(System.currentTimeMillis());

    int listSize = mVehicleList.size();
    for (int i = 0; i < listSize; i++)
        timerMap.put(i, false);
}

@Override
public int getCount() {
    return mVehicleList.size();
}

@Override
public Object getItem(int position) {
    return mVehicleList.get(position);
}

@Override
public long getItemId(int position) {
    return 0;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    final ViewHolder holder;
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        convertView = inflater.inflate(R.layout.row_buy_listing_grid,
                    parent, false);
        holder = new ViewHolder();
                    holder.timer_layout = (LinearLayout) convertView
                .findViewById(R.id.timer_layout);
                    holder.txt_remaining_days = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_days);
        holder.txt_remaining_hours = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_hours);
        holder.txt_remaining_mins = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_mins);
        holder.txt_remaining_secs = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_secs);
        holder.listing_status = (ImageView) convertView
                .findViewById(R.id.listing_status);

        convertView.setTag(holder);
    } else
        holder = (ViewHolder) convertView.getTag();

    final VehicleAttributes vehicleAttributes = mVehicleList.get(position);

    AuctionModel mAuctionModel = vehicleAttributes.getAuctionDetailModel();
    if (mAuctionModel != null) {
        holder.img_auction.setVisibility(View.VISIBLE);

        Date auctionStartDate = Util
                .getDateFromString(mAuctionModel.getStarted_at(),
                        "yyyy-MM-dd hh:mm:ss", "GMT");
        Date auctionEndDate = Util.getDateFromString(
                mAuctionModel.getEnded_at(), "yyyy-MM-dd hh:mm:ss", "GMT");
        long diff = currentDate.getTime() - auctionEndDate.getTime();

        if (diff < 0)
            diff = -diff;
        else
            diff = 0;

        if (timerMap.get(position) == null || !timerMap.get(position)) {

            timerMap.put(position, true);
            MyCountDown countDown = new MyCountDown(position, diff, 1000,
                    holder.txt_remaining_days, holder.txt_remaining_hours,
                    holder.txt_remaining_mins, holder.txt_remaining_secs);
            countDown.start();
        }
    } 
    return convertView;
}

private class MyCountDown extends CountDownTimer {
    RobotoCondensedBoldTextView txt_remaining_days;
    RobotoCondensedBoldTextView txt_remaining_hours;
    RobotoCondensedBoldTextView txt_remaining_mins;
    RobotoCondensedBoldTextView txt_remaining_secs;
    int position;

    public MyCountDown(int position, long millisInFuture, long countDownInterval,
            RobotoCondensedBoldTextView txt_remaining_days,
            RobotoCondensedBoldTextView txt_remaining_hours,
            RobotoCondensedBoldTextView txt_remaining_mins,
            RobotoCondensedBoldTextView txt_remaining_secs) {
        super(millisInFuture, countDownInterval);
        this.position = position;
        this.txt_remaining_days = txt_remaining_days;
        this.txt_remaining_hours = txt_remaining_hours;
        this.txt_remaining_mins = txt_remaining_mins;
        this.txt_remaining_secs = txt_remaining_secs;
    }

    @Override
    public void onFinish() {

    }

    @Override
    public void onTick(long millisUntilFinished) {
        updateTimerLabel(position, millisUntilFinished, txt_remaining_days,
                txt_remaining_hours, txt_remaining_mins, txt_remaining_secs);
    }
}

private void updateTimerLabel(int position, long millis,
        RobotoCondensedBoldTextView txt_remaining_days,
        RobotoCondensedBoldTextView txt_remaining_hours,
        RobotoCondensedBoldTextView txt_remaining_mins,
        RobotoCondensedBoldTextView txt_remaining_secs) {
    String days = String.format("%02d",
            TimeUnit.MILLISECONDS.toDays(millis));
    String hours = String.format(
            "%02d",
            TimeUnit.MILLISECONDS.toHours(millis)
                    - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS
                            .toDays(millis)));
    String mins = String.format(
            "%02d",
            TimeUnit.MILLISECONDS.toMinutes(millis)
                    - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS
                            .toHours(millis)));
    String secs = String.format(
            "%02d",
            TimeUnit.MILLISECONDS.toSeconds(millis)
                    - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS
                            .toMinutes(millis)));

    txt_remaining_days.setText(days);
    txt_remaining_hours.setText(hours);
    txt_remaining_mins.setText(mins);
    txt_remaining_secs.setText(secs);
}

static class ViewHolder {
    NetworkImageView mVehicleImage;
    RobotoCondensedBoldTextView txt_remaining_days, txt_remaining_hours,
            txt_remaining_mins, txt_remaining_secs;
    LinearLayout timer_layout;
}
}

您应该将列表视图和倒数计时器分成两个片段,以避免无意的干扰。 Android 不是为这样的实现而设计的。

碎片是要走的路:http://developer.android.com/guide/components/fragments.html

编辑: 对于您的情况,您可以 运行 每个列表项的单独计时器数据库,然后只需在列表视图中列出您的项目。当用户需要知道时间时,他们会单击您的列表项,这会调用数据库获取当前时间。这个当前时间然后显示在列表项上。这不是直播,所以时间不会改变。这样做可以避免闪烁问题。

好的先说说问题

if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        convertView = inflater.inflate(R.layout.row_buy_listing_grid,
                    parent, false);
        holder = new ViewHolder();
                    holder.timer_layout = (LinearLayout) convertView
                .findViewById(R.id.timer_layout);
                    holder.txt_remaining_days = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_days);
        holder.txt_remaining_hours = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_hours);
        holder.txt_remaining_mins = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_mins);
        holder.txt_remaining_secs = (RobotoCondensedBoldTextView) convertView
                .findViewById(R.id.txt_remaining_secs);
        holder.listing_status = (ImageView) convertView
                .findViewById(R.id.listing_status);

        convertView.setTag(holder);
    } else
        holder = (ViewHolder) convertView.getTag();

上面列表视图中的代码将重用组件的项目,例如,当我们加载列表时,列表中只显示 6 个项目,列表视图创建这些项目,但是当您上下滚动而不是创建时新项目它将为下一个位置重用相同的视图,因此当您将视图传递给倒计时计时器以更新时间时,相同的视图正在使用不同的倒计时计时器进行更新

现在解决方案是什么,您必须弄清楚哪些项目视图在屏幕上可见,并停止其他视图的倒数计时器。或者有一个很简单但不推荐的方法,因为它可能会导致内存问题

    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    convertView = inflater.inflate(R.layout.row_buy_listing_grid,
                parent, false);
    holder = new ViewHolder();
                holder.timer_layout = (LinearLayout) convertView
            .findViewById(R.id.timer_layout);
                holder.txt_remaining_days = (RobotoCondensedBoldTextView) convertView
            .findViewById(R.id.txt_remaining_days);
    holder.txt_remaining_hours = (RobotoCondensedBoldTextView) convertView
            .findViewById(R.id.txt_remaining_hours);
    holder.txt_remaining_mins = (RobotoCondensedBoldTextView) convertView
            .findViewById(R.id.txt_remaining_mins);
    holder.txt_remaining_secs = (RobotoCondensedBoldTextView) convertView
            .findViewById(R.id.txt_remaining_secs);
    holder.listing_status = (ImageView) convertView
            .findViewById(R.id.listing_status);

    convertView.setTag(holder);

简单删除 if(convertview==null) 并允许为每一行扩充新视图。

更新

我在这里更新了我的答案看看:(它使用 cardview 和 recyclerview,但同样的原则也适用于 listview)

您在创建倒数计时器实例和取消它时都必须小心。如果您不取消计时器,它将更新错误的视图。

当我遇到同样的问题,即为每个视图持有者使用单独的计时器时,对我有用的是在 onBindViewHolder() 中设置计时器,并在此之前检查它是否不为空。在那种情况下我会取消它:

if (holder.mCountDownTimer != null) {
      holder.mCountDownTimer.cancel();
    }

    if (mPodEvents.get(position).getState() == PodEvent.State.FUTURE) {
      holder.mCountDownTimer = new CountDownTimer(podEvent.getMilliesTillTime(), 1000 / 60) {
        @Override
        public void onTick(long millisUntilFinished) {
          holder.hour.setText(String.format(Locale.getDefault(), "%d", (int) TimeUnit.MILLISECONDS.toHours(millisUntilFinished)));
          holder.minute.setText(String.format(Locale.getDefault(), "%d", (int) (TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished)))));
        }

        @Override
        public void onFinish() {

        }
      }.start();
    }