ListView中的多个秒表计时器

时间:2016-10-27 11:12:54

标签: android listview stopwatch

我的Android应用程序中有一个ListView,它应该包含秒表计时器以及每行中的开始和暂停按钮。问题是,当我在一行中启动计时器时,如果我在ListView中启动属于另一行的另一行,则第二行也从第一行所在的同一时间开始。所以基本上他们并不是独立的。如何修改我的代码,以便每一行都有一个独立的计时器,只有当按下它(启动或停止)按钮时,它才会生效。

这是我的自定义适配器类

public class CustomAdapterStopWatch extends BaseAdapter {

private final LayoutInflater layoutInflater;
Context context;
public static List<String> timerList;
private long startTime = 0L;
private Handler customHandler;
long timeInMilliseconds = 0L;
long timeSwapBuff = 0L;
long updatedTime = 0L;

public CustomAdapterStopWatch(Context context, List<String> timerList) {

    this.context = context;
    this.timerList = timerList;
    layoutInflater = LayoutInflater.from(context);
    customHandler = new Handler();
}

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

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


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

@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    final ViewHolder holder;

    if (convertView == null) {
        convertView = layoutInflater.inflate(R.layout.stop_watch_list_view, null);
        holder = new CustomAdapterStopWatch.ViewHolder();
        holder.timerTextView = (TextView) convertView.findViewById(R.id.timerInListView);
        holder.startTimer = (Button) convertView.findViewById(R.id.startTimerInListView);
        holder.pauseTimer = (Button) convertView.findViewById(R.id.stopTimerInListView);
        holder.timerTextView.setTag(position);
        holder.startTimer.setTag(position);
        holder.pauseTimer.setTag(position);
        convertView.setTag(holder);

        // Alarm alarm = (Alarm) getItem(position);
    } else {

        holder = (CustomAdapterStopWatch.ViewHolder) convertView.getTag();
    }
    holder.timerTextView.setText(timerList.get(position));

    holder.startTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            startTime = SystemClock.uptimeMillis();
            customHandler.postDelayed( new Runnable() {
                public void run() {
                    timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
                    updatedTime = timeSwapBuff + timeInMilliseconds;
                    int secs = (int) (updatedTime / 1000);
                    int mins = secs / 60;
                    secs = secs % 60;
                    int milliseconds = (int) (updatedTime % 1000);
                    holder.timerTextView.setText("" + mins + ":"
                            + String.format("%02d", secs) + ":"
                            + String.format("%03d", milliseconds));
                    customHandler.postDelayed(this, 0);
                }
            }, 0);

        }
    });


    holder.pauseTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            timeSwapBuff += timeInMilliseconds;
            customHandler.removeCallbacks(new Runnable() {
                public void run() {
                    timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
                    updatedTime = timeSwapBuff + timeInMilliseconds;
                    int secs = (int) (updatedTime / 1000);
                    int mins = secs / 60;
                    secs = secs % 60;
                    int milliseconds = (int) (updatedTime % 1000);
                    holder.timerTextView.setText("" + mins + ":"
                            + String.format("%02d", secs) + ":"
                            + String.format("%03d", milliseconds));
                    customHandler.postDelayed(this, 0);
                }
            });
        }
    });

    return convertView;
}

我使用的布局文件如下:

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


    <TextView
        android:id="@+id/timerInListView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:text="00:00:00"
        android:textSize="25dp"
        android:textColor="@color/colorGray"/>

    <Button
        android:id="@+id/stopTimerInListView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="20dp"
        android:text="Pause"
        android:background="@color/colorGreen"/>

  <Button
    android:id="@+id/startTimerInListView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginRight="22dp"
    android:text="Start"
    android:background="@color/colorGreen"
    android:layout_alignParentTop="true"
    android:layout_toLeftOf="@+id/stopTimerInListView"
    android:layout_toStartOf="@+id/stopTimerInListView"
    android:layout_marginEnd="22dp" />

 </RelativeLayout>

我使用适配器的秒表类如下:

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

    listView = (ListView) findViewById(R.id.listViewInStopWatch);
    timerList = new ArrayList<String>();


    timerList.add("00:00:000");
    timerList.add("00:00:000");
    timerList.add("00:00:000");
    timerList.add("00:00:000");

    customAdapter = new CustomAdapterStopWatch(StopWatch.this, timerList);
    listView.setAdapter(customAdapter);
 }

更新

我将代码更改为:

public class CustomAdapterStopWatch extends BaseAdapter {

private final LayoutInflater layoutInflater;
Context context;
public static List<TimerState> timerList;
private long startTime = 0L;
private Handler customHandler;
long timeInMilliseconds = 0L;
long timeSwapBuff = 0L;
long updatedTime = 0L;
HashMap<ViewHolder,Integer> mHashHolder;
TimerState state;

public CustomAdapterStopWatch(Context context, final List<TimerState> timerList) {

    this.context = context;
    this.timerList = timerList;
    layoutInflater = LayoutInflater.from(context);
    customHandler = new Handler();
    mHashHolder = new HashMap<ViewHolder,Integer>();
    state = new TimerState();

    // assuming you need to update the timer after every second.
    customHandler.postDelayed(new Runnable() {
        @Override
        public void run() {

            for (ViewHolder holder: mHashHolder.keySet()) {
                state = timerList.get(mHashHolder.get(holder));

                // update the state
            }
        }
    }, 1000);
}

 @Override
public View getView(final int position, View convertView, ViewGroup parent) {

    ViewHolder holder = null;

    if (convertView == null) {
        convertView = layoutInflater.inflate(R.layout.stop_watch_list_view, null);
        holder = new CustomAdapterStopWatch.ViewHolder();
        holder.timerTextView = (TextView) convertView.findViewById(R.id.timerInListView);
        holder.startTimer = (Button) convertView.findViewById(R.id.startTimerInListView);
        holder.pauseTimer = (Button) convertView.findViewById(R.id.stopTimerInListView);
        holder.timerTextView.setTag(position);
        holder.startTimer.setTag(position);
        holder.pauseTimer.setTag(position);
        convertView.setTag(holder);
        mHashHolder.put(holder,position);

        // Alarm alarm = (Alarm) getItem(position);
    } else {
        // holder is being used again, so need to update its new position
        // following line should be synchronized if timer is updated
        // in a separate thread
        mHashHolder.put(holder, position);
        holder = (CustomAdapterStopWatch.ViewHolder) convertView.getTag();
    }
    holder.timerTextView.setText(timerList.get(position).currentTime);

    holder.startTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            startTime = SystemClock.uptimeMillis();
            if (state.isPaused) {
                state.timeSinceStarted = String.valueOf(SystemClock.uptimeMillis());
                state.isPaused = false;
            } else {
                // nothing
            }

        }
    });


    holder.pauseTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if (state.isPaused) {
                state.isPaused = true;
            }
        }
    });

    return convertView;
}


public void startTime(final TextView t) {
     Runnable updateTimerThread = new Runnable() {
        public void run() {
            timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
            updatedTime = timeSwapBuff + timeInMilliseconds;
            int secs = (int) (updatedTime / 1000);
            int mins = secs / 60;
            secs = secs % 60;
            int milliseconds = (int) (updatedTime % 1000);
            t.setText("" + mins + ":"
                    + String.format("%02d", secs) + ":"
                    + String.format("%03d", milliseconds));
            customHandler.postDelayed(this, 0);
        }
    };
}

然而,按下按钮后,没有任何反应。我错过了什么吗?

更新2

 public CustomAdapterStopWatch(Context context, final List<TimerState> timerList) {

    this.context = context;
    this.timerList = timerList;
    layoutInflater = LayoutInflater.from(context);
    customHandler = new Handler();
    mHashHolder = new HashMap<ViewHolder,Integer>();
    state = new TimerState();

    // assuming you need to update the timer after every second.
    customHandler.postDelayed(new Runnable() {
        @Override
        public void run() {

            for (ViewHolder holder: mHashHolder.keySet()) {
                state = timerList.get(mHashHolder.get(holder));

                long tmp;
                if (!state.isPaused) {
                    tmp = SystemClock.uptimeMillis();
                    state.currentTime += tmp - state.timeSinceStarted;
                    state.timeSinceStarted = tmp;
                }
                updatedTime = state.currentTime;
                int secs = (int) (updatedTime / 1000);
                int mins = secs / 60;
                secs = secs % 60;
                int milliseconds = (int) (updatedTime % 1000);
                // set the text of the view in holder
                holder.timerTextView.setText("" + mins + ":"
                        + String.format("%02d", secs) + ":"
                        + String.format("%03d", milliseconds));

                // update the state
            }
        }
    }, 1000);
}

更新3

public class CustomAdapterStopWatch extends BaseAdapter {

private final LayoutInflater layoutInflater;
Context context;
public static List<TimerState> timerList;
private long startTime = 0L;
private Handler customHandler;
long timeInMilliseconds = 0L;
long timeSwapBuff = 0L;
long updatedTime = 0L;
HashMap<ViewHolder,Integer> mHashHolder;
TimerState state;

public CustomAdapterStopWatch(final Context context, final List<TimerState> timerList) {

    this.context = context;
    this.timerList = timerList;
    layoutInflater = LayoutInflater.from(context);
    customHandler = new Handler();
    mHashHolder = new HashMap<ViewHolder,Integer>();
    state = new TimerState();

    // assuming you need to update the timer after every second.

    customHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            for (ViewHolder holder: mHashHolder.keySet()) {
                state = timerList.get(mHashHolder.get(holder));

                long tmp = 0;
                if (!state.isPaused) {
                    tmp = SystemClock.uptimeMillis();
                    state.currentTime += tmp - state.timeSinceStarted;
                    state.timeSinceStarted = tmp;
                }
                updatedTime = state.currentTime;
                int secs = (int) (updatedTime / 1000);
                int mins = secs / 60;
                secs = secs % 60;
                int milliseconds = (int) (updatedTime % 1000);
                // set the text of the view in holder
                holder.timerTextView.setText("" + mins + ":"
                        + String.format("%02d", secs) + ":"
                        + String.format("%03d", milliseconds));
              //  notifyDataSetChanged();
                Toast.makeText(context,"I got here", Toast.LENGTH_SHORT).show();
                // update the state
            }
            customHandler.postDelayed(this, 1000);
        }
    }, 1000);
}

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

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


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

@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    ViewHolder holder = null;

    if (convertView == null) {
        convertView = layoutInflater.inflate(R.layout.stop_watch_list_view, null);
        holder = new CustomAdapterStopWatch.ViewHolder();
        holder.timerTextView = (TextView) convertView.findViewById(R.id.timerInListView);
        holder.startTimer = (Button) convertView.findViewById(R.id.startTimerInListView);
        holder.pauseTimer = (Button) convertView.findViewById(R.id.stopTimerInListView);
        holder.timerTextView.setTag(position);
        holder.startTimer.setTag(position);
        holder.pauseTimer.setTag(position);
        convertView.setTag(holder);
        mHashHolder.put(holder,position);

        // Alarm alarm = (Alarm) getItem(position);
    } else {
        // holder is being used again, so need to update its new position
        // following line should be synchronized if timer is updated
        // in a separate thread
        mHashHolder.put(holder, position);
        holder = (CustomAdapterStopWatch.ViewHolder) convertView.getTag();
    }
    holder.timerTextView.setText(String.valueOf(timerList.get(position)));

    holder.startTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            startTime = SystemClock.uptimeMillis();
            if (state.isPaused) {
                state.timeSinceStarted = SystemClock.uptimeMillis();
                state.isPaused = false;



            } else {
                // nothing
            }

        }
    });

    holder.pauseTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if (!state.isPaused) {
                state.isPaused = true;
            }
        }
    });

    return convertView;
}


public void startTime(final TextView t) {
     Runnable updateTimerThread = new Runnable() {
        public void run() {
            timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
            updatedTime = timeSwapBuff + timeInMilliseconds;
            int secs = (int) (updatedTime / 1000);
            int mins = secs / 60;
            secs = secs % 60;
            int milliseconds = (int) (updatedTime % 1000);
            t.setText("" + mins + ":"
                    + String.format("%02d", secs) + ":"
                    + String.format("%03d", milliseconds));
            customHandler.postDelayed(this, 0);
        }
    };
}
private class ViewHolder {

    TextView timerTextView;
    Button startTimer;
    Button pauseTimer;
 }
}

2 个答案:

答案 0 :(得分:1)

实际上有两件事你做错了。

  1. 您正在为每个计时器使用相同的变量updateTime。相反,你应该为每个计时器保持一个状态。所以最好有一个单独的类。例如,

    
    class TimerState {
         boolean isPaused = true;
         long currentTime = 0;
         String timeSinceStarted;    // matters only when the timer is started
      }
    
    
  2. 所以将上面类的数组列表传递给你的适配器。所以timerList将是TimerState的arrayList。

    $str1
    1. 这个有点棘手。适配器的作用是重用视图,并且您正在更新某个视图项的计时器(使用postDelayed),即使它对用户不可见。因此,您应该只更新用户可见的那些视图的计时器。我正在创建一个hashmap,它将保留用户可见的所有视图的持有者,并且我们将拥有一个只更新那些视图的runnable。
    2. 您的适配器将

      
          public class CustomAdapterStopWatch extends BaseAdapter {
      
          private final LayoutInflater layoutInflater;
          Context context;
          public static List timerList;
          private long startTime = 0L;
          private Handler customHandler;
          long timeInMilliseconds = 0L;
          long timeSwapBuff = 0L;
          long updatedTime = 0L;
          // this will contain the holder of all visible views
          HashMap mHashHolder;
      
          public CustomAdapterStopWatch(Context context, List timerList) {
      
              this.context = context;
              this.timerList = timerList;
              layoutInflater = LayoutInflater.from(context);
              customHandler = new Handler();
      
              // assuming you need to update the timer after every second.
              customHandler.postDelayed(new Runnable() {
                      @Override
                      public void run() {
                          TimerState state;
      
                          for (ViewHolderItem holder: mHashHolder.keySet()) {
                              state = timerList.get(mHashHolder.get(holder));
                              long tmp;
                              if (!state.isPaused) {
                                 tmp = SystemClock.uptimeMillis();
                                 state.currentTime += tmp - state.timeSinceStarted)
                                 state.timeSinceStarted = tmp;
                              }
                              updatedTime = state.currentTime
      
                              int secs = (int) (updatedTime / 1000);
                              int mins = secs / 60;
                              secs = secs % 60;
                              int milliseconds = (int) (updatedTime % 1000);
                              // set the text of the view in holder
                              holder.t.setText("" + mins + ":"
                                + String.format("%02d", secs) + ":"
                                + String.format("%03d", milliseconds));
                          }
                          customHandler.postDelayed(this, 1000);
                      }
               }, 1000);
          }
      
          @Override
          public int getCount() {
              return timerList.size();
          }
      
          @Override
          public Object getItem(int position) {
              return timerList.get(position);
          }
      
      
          @Override
          public long getItemId(int position) {
              return position;
          }
      
          @Override
          public View getView(final int position, View convertView, ViewGroup parent) {
      
              final ViewHolder holder;
      
              if (convertView == null) {
                  convertView = layoutInflater.inflate(R.layout.stop_watch_list_view, null);
                  holder = new CustomAdapterStopWatch.ViewHolder();
                  holder.timerTextView = (TextView) convertView.findViewById(R.id.timerInListView);
                  holder.startTimer = (Button) convertView.findViewById(R.id.startTimerInListView);
                  holder.pauseTimer = (Button) convertView.findViewById(R.id.stopTimerInListView);
                  holder.timerTextView.setTag(position);
                  holder.startTimer.setTag(position);
                  holder.pauseTimer.setTag(position);
                  convertView.setTag(holder);
      
                  mHashHolder.put(position, holder);
      
                  // Alarm alarm = (Alarm) getItem(position);
              } else {
      
                  // holder is being used again, so need to update its new position
                  // following line should be synchronized if timer is updated
                  // in a separate thread
                  holder = (CustomAdapterStopWatch.ViewHolder) convertView.getTag();
                  mHashHolder.put(holder, position)
              }
              holder.timerTextView.setText(timerList.get(position));
      
              holder.startTimer.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
      
                      startTime = SystemClock.uptimeMillis();
                      if (state.isPaused) {
                          state.timeSinceStarted = SystemClock.uptimeMillis();
                          state.isPaused = false;
                      } else {
                         // nothing
                      }
      
                      // logic for showing the timer will be in onScroll method
                  }
              });
      
      
              holder.pauseTimer.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
      
                      if (!state.isPaused) {
                          state.isPaused = True
      
                      }
                  }
              });
      
              return convertView;
          }
      
      

答案 1 :(得分:0)

holder.startTimer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            startTime = SystemClock.uptimeMillis();
            customHandler.postDelayed( new Runnable() {
                public void run() {
                    timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
                    updatedTime = timeSwapBuff + timeInMilliseconds;
                    int secs = (int) (updatedTime / 1000);
                    int mins = secs / 60;
                    secs = secs % 60;
                    int milliseconds = (int) (updatedTime % 1000);
                    holder.timerTextView.setText("" + mins + ":"
                            + String.format("%02d", secs) + ":"
                            + String.format("%03d", milliseconds));
                    customHandler.postDelayed(this, 0);
                }
            }, 0);

        }
    });

是你的问题。因此,您会看到您声明了一个要计数的变量,当您开始第二个变量时,您会计算相同的变量updatedTime。 要使其独立,您需要将函数更改为不依赖于相同的变量。我的第一个想法是在调用计时器的开始和克隆变量时创建一个新的Thread。但是我在很长一段时间没有为android编程。