如何在ListView中处理多个倒计时器?

18

我有一个ListView(带有自定义列表适配器),我需要在每一行上显示倒计时。

例如,如果我的列表包含4个项目,我将有4行。此时,我需要处理4个不同的倒计时(每行一个),因为时间是不同的。

enter image description here

到目前为止,我是这样处理的:在自定义列表适配器中,在getView()方法内创建一个新的CountDownTimer,并在TextView中显示剩余时间。

但问题是它会使活动变得非常缓慢,最糟糕的情况下我甚至无法正确滚动(因为每次显示一个行时,都会创建一个新的CountDownTimer)。

我搜索了很多更好的解决方案,但没有一个令人满意的。

是否有一种更清洁、更平稳的解决方案来处理ListView中的多个倒计时?

谢谢

4个回答

37

不是尝试显示所有剩余时间,而是更新可见项的剩余时间。

请按照以下示例代码操作并告诉我:

MainActivity:

public class MainActivity extends Activity {

private ListView lvItems;
private List<Product> lstProducts;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    lvItems = (ListView) findViewById(R.id.lvItems);
    lstProducts = new ArrayList<>();
    lstProducts.add(new Product("A", System.currentTimeMillis() + 10000));
    lstProducts.add(new Product("B", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("C", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("D", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("E", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("F", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("G", System.currentTimeMillis() + 30000));
    lstProducts.add(new Product("H", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("I", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("J", System.currentTimeMillis() + 40000));
    lstProducts.add(new Product("K", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("L", System.currentTimeMillis() + 50000));
    lstProducts.add(new Product("M", System.currentTimeMillis() + 60000));
    lstProducts.add(new Product("N", System.currentTimeMillis() + 20000));
    lstProducts.add(new Product("O", System.currentTimeMillis() + 10000));

    lvItems.setAdapter(new CountdownAdapter(MainActivity.this, lstProducts));
}

private class Product {
    String name;
    long expirationTime;

    public Product(String name, long expirationTime) {
        this.name = name;
        this.expirationTime = expirationTime;
    }
}


public class CountdownAdapter extends ArrayAdapter<Product> {

    private LayoutInflater lf;
    private List<ViewHolder> lstHolders;
    private Handler mHandler = new Handler();
    private Runnable updateRemainingTimeRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (lstHolders) {
                long currentTime = System.currentTimeMillis();
                for (ViewHolder holder : lstHolders) {
                    holder.updateTimeRemaining(currentTime);
                }
            }
        }
    };

    public CountdownAdapter(Context context, List<Product> objects) {
        super(context, 0, objects);
        lf = LayoutInflater.from(context);
        lstHolders = new ArrayList<>();
        startUpdateTimer();
    }

    private void startUpdateTimer() {
        Timer tmr = new Timer();
        tmr.schedule(new TimerTask() {
            @Override
            public void run() {
                mHandler.post(updateRemainingTimeRunnable);
            }
        }, 1000, 1000);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = lf.inflate(R.layout.list_item, parent, false);
            holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
            holder.tvTimeRemaining = (TextView) convertView.findViewById(R.id.tvTimeRemaining);
            convertView.setTag(holder);
            synchronized (lstHolders) {
                lstHolders.add(holder);
            }
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.setData(getItem(position));

        return convertView;
    }
}

private class ViewHolder {
    TextView tvProduct;
    TextView tvTimeRemaining;
    Product mProduct;

    public void setData(Product item) {
        mProduct = item;
        tvProduct.setText(item.name);
        updateTimeRemaining(System.currentTimeMillis());
    }

    public void updateTimeRemaining(long currentTime) {
        long timeDiff = mProduct.expirationTime - currentTime;
        if (timeDiff > 0) {
            int seconds = (int) (timeDiff / 1000) % 60;
            int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
            int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);
            tvTimeRemaining.setText(hours + " hrs " + minutes + " mins " + seconds + " sec");
        } else {
            tvTimeRemaining.setText("Expired!!");
        }
    }
}
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
    android:id="@+id/lvItems"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</RelativeLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">

<TextView
    android:id="@+id/tvProduct"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:text="Product Name"
    android:textSize="16dp"
    android:textStyle="bold" />

<TextView
    android:id="@+id/tvTimeRemaining"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:text="Time Remaining : " />

</LinearLayout>

1
任何人都可以使用RecyclerView来完成这项工作,但我已经付出了两天的时间,请帮帮我! - Ahmad Alkhatib
1
@AhmadAlkhateeb,你得到解决方案了吗? - Rishabh Srivastava
@AhmadAlkhateeb,你有没有找到recyclerview的解决方案? - H Raval
谢谢,我使用了你的逻辑找到了解决方案。 - H Raval
我尝试了您的解决方案,使用Recycler View,它完美地运行!谢谢。 - Tiago Martins
显示剩余4条评论

17

虽然有点晚,但这是一个基于@Eldhose M Babu出色答案的PHP和JSON的recyclerView版本。希望对你有所帮助 :)

多重倒计时照片展示

Adapter.java

    public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {

    private Context context;
    private final List<ViewHolder> lstHolders;
    public List<Model> lst;

    private Handler mHandler = new Handler();
    private Runnable updateRemainingTimeRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (lstHolders) {
                long currentTime = System.currentTimeMillis();
                for (ViewHolder holder : lstHolders) {
                    holder.updateTimeRemaining(currentTime);
                }
            }
        }
    };

    public Adapter(List<Model> lst, Context context){
        super();
        this.lst = lst;
        this.context = context;
        lstHolders = new ArrayList<>();
        startUpdateTimer();
    }

    private void startUpdateTimer() {
        Timer tmr = new Timer();
        tmr.schedule(new TimerTask() {
            @Override
            public void run() {
                mHandler.post(updateRemainingTimeRunnable);
            }
        }, 1000, 1000);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list, parent, false);
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(lst.get(position));
        synchronized (lstHolders) {
            lstHolders.add(holder);
        }
        holder.updateTimeRemaining(System.currentTimeMillis());
    }

    @Override
    public int getItemCount() {
        return lst.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{
        public TextView textViewName;
        public TextView tvTimeRemaining;
        Model mModel;

        public void setData(Model item) {
            mModel = item;
            textViewName.setText(item.name);
            updateTimeRemaining(System.currentTimeMillis());
        }

        public void updateTimeRemaining(long currentTime) {
            long timeDiff = mModel.expirationTime - currentTime;
            if (timeDiff > 0) {
                int seconds = (int) (timeDiff / 1000) % 60;
                int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
                int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);

                tvTimeRemaining.setText(hours + " hrs " + minutes + " mins " + seconds + " sec");
            } else {
                tvTimeRemaining.setText("Expired!!");
            }
        }

        public ViewHolder(View itemView) {
            super(itemView);
            tvTimeRemaining = (TextView) itemView.findViewById(R.id.cd);
            textViewName = (TextView) itemView.findViewById(R.id.textViewName);
        }
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

private List<Model> lst;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private RecyclerView.Adapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setHasFixedSize(true);
    layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);
    lst = new ArrayList<>();
    getData();


    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show();
        }
    });
}

private void getData(){
    final ProgressDialog loading = ProgressDialog.show(this,"Loading Data", "Please wait...",false,false);

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest("http://192.168.200.102/api.php",
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    loading.dismiss();
                    parseData(response);
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {

                }
            });

    RequestQueue requestQueue = Volley.newRequestQueue(this);
    requestQueue.add(jsonArrayRequest);
}

private void parseData(JSONArray array){
    for(int i = 0; i<array.length(); i++) {
        Model model = new Model();
        JSONObject json = null;
        try {
            json = array.getJSONObject(i);
            model.setexpirationTime(json.getLong("expirationTime"));
            model.setName(json.getString("name"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        lst.add(model);
    }

    adapter = new Adapter(lst, this);
    recyclerView.setAdapter(adapter);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}}

Model.java

public class Model {
public String name;
public long expirationTime;

public long getexpirationTime() {
    return expirationTime;
}

public void setexpirationTime(long expire) {
    this.expirationTime = expire;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="here.math.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="here.math.MainActivity"
    tools:showIn="@layout/activity_main">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </android.support.v7.widget.RecyclerView>
</RelativeLayout>

list.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp">

        <LinearLayout
            android:padding="@dimen/activity_horizontal_margin"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">


            <TableLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TableRow>
                    <TextView
                        android:text="Expire time"
                        android:paddingRight="10dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <TextView
                        android:id="@+id/cd"
                        android:textStyle="bold"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                </TableRow>

                <TableRow>
                    <TextView
                        android:text="Name"
                        android:paddingRight="10dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <TextView
                        android:id="@+id/textViewName"
                        android:textStyle="bold"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                </TableRow>

            </TableLayout>
        </LinearLayout>
    </android.support.v7.widget.CardView>

</RelativeLayout>

针对 url 部分,你可以这样做:

<?php

$arr = array(
array(
        'name' => "Android",
        'expirationTime' => 1456860322 * 1000, // timestamp multiply for display in milliseconds
),
array(
        'name' => "Android 2",
        'expirationTime' => 1456900522 * 1000,
),
array(
        'name' => "Android 3",
        'expirationTime' =>  1459509819 * 1000,
),
array(
        'name' => "Android 4",
        'expirationTime' => 1457021950 * 1000,
),
);

echo json_encode($arr);

我只需要对两行进行操作,该怎么做? - andro
@andro,根据我发布的PHP脚本,删除最后两个数组。如果你遇到问题,请告诉我。 - user3565145
在你的示例问题中,使用了 synchronized (lstHolders) { lstHolders.add(holder); } 的代码块。这意味着你总是将 holder 添加到列表中。因此,假设当我上下滑动时有10行数据,lstHolders 的大小将会增加,但应该最多只能容纳10个元素。 - andro
你有一个包含10行的JSON数组,对吧?它的大小是10,将显示10并将所有行更新为已完成状态。我刚刚测试了55行,它可以正常工作。所以你说当你添加50行时,只有其中的10行有效? - user3565145
1
非常棒的解决方案,你绝对救了我的循环问题 :) :) @HamedOkhovvat - Sanoop Surendran
显示剩余6条评论

1
请看这个 Android中ListView的倒计时定时器 示例。 自定义适配器
import android.os.CountDownTimer;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
public class Adapter extends RecyclerView.Adapter<Adapter.MyViewHolder>{

private ArrayList<String> al_data;

public class MyViewHolder extends RecyclerView.ViewHolder
{
    public TextView tv_timer;
    CountDownTimer timer;

    public MyViewHolder (View view){
        super(view);
        tv_timer = (TextView)view.findViewById(R.id.tv_timer);

    }


}

public Adapter(ArrayList<String> al_data) {
    this.al_data = al_data;
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_layout,parent,false);


    return new MyViewHolder(view);
}

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {

    holder.tv_timer.setText(al_data.get(position));

    if (holder.timer != null) {
        holder.timer.cancel();
    }
     long timer = Long.parseLong(al_data.get(position));

    timer = timer*1000;

    holder.timer = new CountDownTimer(timer, 1000) {
        public void onTick(long millisUntilFinished) {
          holder.tv_timer.setText("" + millisUntilFinished/1000 + " Sec");
        }

        public void onFinish() {
            holder.tv_timer.setText("00:00:00");
        }
    }.start();


}

@Override
public int getItemCount() {
    return al_data.size();
}



}

主要活动
package com.androidsolutionworld.multipletimer;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ArrayList<String> al_data = new ArrayList<>();
private Adapter obj_adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
    al_data.add("1234");
    al_data.add("1257");
    al_data.add("100");
    al_data.add("1547");
    al_data.add("200");
    al_data.add("500");
    al_data.add("2000");
    al_data.add("1000");

    obj_adapter = new Adapter(al_data);
    LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext(),LinearLayoutManager.VERTICAL,false);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(obj_adapter);
}
}

谢谢


1
适配器类:-

公共类CountdownAdapter扩展自RecyclerView.Adapter { ArrayList mList; Context mContext;

public CountdownAdapter(ArrayList<Long> mList, Context mContext) {
    this.mList = mList;
    this.mContext = mContext;
}

public class ViewHolder extends RecyclerView.ViewHolder {
    CountDownTimer timerCount;
    TextView mCountDownTxt;

    public ViewHolder(View convertView) {
        super(convertView);
        mCountDownTxt = (TextView)convertView.findViewById(R.id.countdown_text);
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return null;
}

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    if(holder.timerCount==null) {
        holder.timerCount = new CountDownTimer(mList.get(position), 1000) {
            @Override
            public void onTick(long millis) {
                String hms = String.format("%02d:%02d",  TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
                holder.mCountDownTxt.setText(hms);
            }

            @Override
            public void onFinish() {

            }
        };
    }
    holder.mCountDownTxt.setVisibility(View.VISIBLE);
    holder.timerCount.start();
}

@Override
public int getItemCount() {
    return mList.size();
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接