2016-10-27 46 views
1

我在我的android應用程序中有一個ListView,它應該包含秒錶計時器以及每一行中的開始和暫停按鈕。問題是,當我在一行中啓動計時器,並且如果我在ListView中啓動另一行屬於另一行,則第二個也從第一個位於的同一時間開始。所以基本上他們不是獨立的。我如何修改我的代碼,以便每行都有一個獨立的計時器,並且只有當它按下(開始或停止)按鈕時纔會生效。ListView中的多個秒錶定時器

這裏是我的自定義adapater類

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

UPDATE

我改變了代碼如下:

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

回答

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。 爲了使其獨立,您需要更改函數以不計入相同的變量。我的第一個想法是創建一個新的線程調用計時器的開始和克隆變量。但是我在相當短的時間內沒有爲android編程。

1

其實有2件事情你做錯了。

  1. 您正在爲每個計時器使用相同的變量updateTime。相反,你應該爲你的每個定時器保持一個狀態。所以最好有一個單獨的課程。對於例如

     
    
    class TimerState { 
        boolean isPaused = true; 
        long currentTime = 0; 
        String timeSinceStarted; // matters only when the timer is started 
        } 
    
    

所以上面的類的數組列表傳遞給您的適配器。所以timerList將是TimerState的arrayList。

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

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


    timerList.add(new TimerState()); 
    timerList.add(new TimerState()); 
    timerList.add(new TimerState()); 
    timerList.add(new TimerState()); 

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

} 
  • 這一個是有點複雜。什麼適配器所做的是重用視圖,並且您正在更新某個視圖項目的定時器(使用postDelayed),即使它對用戶不可見。所以你應該只更新那些對用戶可見的視圖的計時器。我創建了一個hashmap,它將保持用戶可見的所有視圖的持有者,並且我們將擁有一個只更新那些視圖的runnable。
  • 適配器將

     
    
        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; 
        } 
    
    
    +0

    感謝您的回答。但我仍然沒有得到如何在兩個按鈕和處理程序函數的偵聽器中使用timerstate數組。 –

    +0

    邏輯將類似於你編碼的內容,但對於每個單獨的計時器。它將進入OnScroll方法。 –

    +0

    這實際上很整潔。我非常喜歡這個解決方案。這比使用與處理程序結合的線程更好。如果我想一想,應該節省資源。 @Dev先生,所以你必須做的是將你的邏輯從'holder.startTimer.setOnClickListener'放入'OnScrol'l方法中。改變它有點使它適用於新的邏輯,它應該工作 – Nico