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

Posted

技术标签:

【中文标题】如何处理 ListView 中的多个倒数计时器?【英文标题】:How to handle multiple countdown timers in ListView? 【发布时间】:2015-09-12 14:03:08 【问题描述】:

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

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

到目前为止,我正在通过以下方式处理它:在自定义列表适配器中,在 getView() 方法中,我创建了一个新的 CountDownTimer 并在 TextView 中显示剩余时间。

但问题是它大大减慢了活动,在最坏的情况下我什至无法正确滚动(因为每次显示一行时,它都会创建一个新的 CountDownTimer)。

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

是否有更清洁、更流畅的解决方案来处理 listView 中的多个倒计时?

谢谢

【问题讨论】:

【参考方案1】:

我们的想法是更新可见项目的剩余时间,而不是尝试显示所有人的剩余时间。

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

主要活动:

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_
android:layout_>

<ListView
    android:id="@+id/lvItems"
    android:layout_
    android:layout_ />

</RelativeLayout>

list_item.xml

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

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

<TextView
    android:id="@+id/tvTimeRemaining"
    android:layout_
    android:layout_
    android:layout_margin="10dp"
    android:text="Time Remaining : " />

</LinearLayout>

【讨论】:

任何人都可以用 recyclerview 做到这一点,我已经工作了 2 天,请帮忙!! @AhmadAlkhateeb 你有解决办法吗? 我也在做同样的事情。你能把回收站视图的代码发给我吗? @AhmadAlkhateeb 你有没有为 recyclerview 找到任何解决方案 谢谢我用你的逻辑找到了解决方案【参考方案2】:

可能已经晚了,但这是使用 php 和 json 的 recyclerView 版本,基于 @Eldhose M Babu 的出色回答。希望对您有所帮助:)

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_
    android:layout_
    android:fitsSystemWindows="true"
    tools:context="here.math.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_
        android:layout_
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_
            android:layout_
            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_
        android:layout_
        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_
    android:layout_
    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_
            android:layout_>
        </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_
    android:layout_>

    <android.support.v7.widget.CardView
        android:layout_
        android:layout_
        android:layout_marginBottom="20dp">

        <LinearLayout
            android:padding="@dimen/activity_horizontal_margin"
            android:orientation="vertical"
            android:layout_
            android:layout_>


            <TableLayout
                android:layout_
                android:layout_>

                <TableRow>
                    <TextView
                        android:text="Expire time"
                        android:paddingRight="10dp"
                        android:layout_
                        android:layout_ />

                    <TextView
                        android:id="@+id/cd"
                        android:textStyle="bold"
                        android:layout_
                        android:layout_ />

                </TableRow>

                <TableRow>
                    <TextView
                        android:text="Name"
                        android:paddingRight="10dp"
                        android:layout_
                        android:layout_ />

                    <TextView
                        android:id="@+id/textViewName"
                        android:textStyle="bold"
                        android:layout_
                        android:layout_ />

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

【讨论】:

我如何才能只为 2 行做到这一点 @andro,根据我发布的 php 脚本,删除最后两个数组。如果您遇到问题,请告诉我。 在您的示例中,问题是同步的 (lstHolders) lstHolders.add(holder);您总是将持有人添加到列表中。所以说如果当我向上滑动时 der 是 10 行 lstHolders 的大小会增加它应该是最大 10 我的问题陈述是我有时间从服务器现在我必须更新但说我有 50 行但我必须只更新第 10 行的时间 很好的解决方案,你肯定从循环中拯救了马头 :) :) @HamedOkhovvat【参考方案3】:

请查看此Countdown Timer in listview android 示例。

自定义适配器

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


谢谢

【讨论】:

【参考方案4】:

适配器类:-


公共类 CountdownAdapter 扩展 RecyclerView.Adapter 数组列表 mList; 上下文 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();

【讨论】:

以上是关于如何处理 ListView 中的多个倒数计时器?的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 Listview 中的单击事件?

如何处理android中Listview中的点击事件?

c# foreach块程序如何处理ListView?

当 ListView 有 ItemTemplate 时如何处理 ListViewItem 的右键单击?

当ViewPager嵌套在ScrollView/ListView里时,手势冲突如何处理?

如何处理完成闭包中的多个错误