2013-10-06 12 views
3

我正在創建一個包含2列的ListView的應用程序。在第一列上應該顯示倒計時,在第二列上顯示一個附加文本,說明倒計時的內容。 下面你看到我的代碼可以工作......或多或少。 我有一個列表視圖與多行定時器滴答作響。 一個問題是: 我的runnable中的set.Text()似乎覆蓋了所有行。例如。對於行1可運行將文本也設置爲行2和3,對於行2可運行對於1和3也設置文本等等。這具有第一列閃爍的效果(具有正確的值和其他行的值)。 如何爲列表視圖中的特定行設置文本?Android:帶有處理程序和可運行的ListView中的多個定時器。 2問題

下一個問題: 即使我從處理程序中刪除回調,runnable仍在運行。但是當活動在後臺或關閉時,定時器滴答不再需要,我不想浪費系統資源。

我的活動:

public class TimerActivity extends ListActivity { 

MyTimerAdapter myTimerAdapter = null; 
ArrayList<Long> timerList = new ArrayList<Long>(); 
ArrayList<String> textList = new ArrayList<String>(); 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.listactivity); 

    myTimerAdapter = new MyTimerAdapter(this, R.layout.row, R.id.tv_timer, R.id.tv_text); 
    setListAdapter(myTimerAdapter); 
} 

@Override 
protected void onResume() { 
    super.onResume(); 
    refreshView(); 
} 

@Override 
protected void onPause() { 
    myTimerAdapter.clear(); 
    myTimerAdapter.notifyDataSetChanged(); 
    super.onPause(); 
} 

private void refreshView() { 

    myTimerAdapter.clear(); 
    timerList.clear(); 
    textList.clear(); 

    // some code to read database and fill 
    // array timerList with a long value (used for displaying a countdown) 
    // and textList with some additional text 

    myTimerAdapter.add(timerList, textList); 
    myTimerAdapter.notifyDataSetChanged(); 
} 

} 

我的適配器:

public class MyTimerAdapter extends ArrayAdapter<String> { 
private Activity mContext; 
private ArrayList<Long> mTimer; 
private ArrayList<String> mText; 
private int mViewId; 
private int mViewIdFieldTimer; 
private int mViewIdFieldText; 
private int listSize; 
private ArrayList<Handler> handlerList = new ArrayList<Handler>(); 
private ArrayList<TimerRunnable> runList = new ArrayList<TimerRunnable>(); 

public MyTimerAdapter(Activity context, int textViewResId, int tv1, int tv2) { 
    super(context, textViewResId); 
    mContext = context; 
    mViewId = textViewResId; 
    mViewIdFieldTimer = tv1; 
    mViewIdFieldText = tv2; 
    listSize = 0; 
} 

public void add(ArrayList<Long> timer, ArrayList<String> text) { 
    mTimer = timer; 
    mText = text; 
    listSize = mText.size(); 
    handlerList.clear(); 
    runList.clear(); 
} 

@Override 
public void clear() { 
    super.clear(); 
    int i; 
    for (i=0; i<listSize; i++) { 
     handlerList.get(i).removeCallbacksAndMessages(runList.get(i)); 
     runList.get(i).stopHandler(); 
    } 
} 

@Override 
public int getCount() { 
    return listSize; 
} 

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
    View v = convertView; 
    if (v == null) { 
     LayoutInflater vi = mContext.getLayoutInflater(); 
     v = vi.inflate(mViewId, null); 
    } 

    long timerLine = mTimer.get(position); 
    if (timerLine != 0) { 
     TextView tvTimer = (TextView) v.findViewById(mViewIdFieldTimer); 

     if (tvTimer != null) { 
      tvTimer.setTag(position); 
      final Handler mTimerHandler = new Handler(); 
      TimerRunnable timerTask = new TimerRunnable(tvTimer, tvTimer.getTag().toString(), timerLine); 
      mTimerHandler.post(timerTask); 

      // save in array to stop later 
      handlerList.add(mTimerHandler); 
      runList.add(timerTask); 
     } 
    } 

    String textLine = mText.get(position); 
    if (textLine != null) { 
     TextView tvText = (TextView) v.findViewById(mViewIdFieldText); 
     if (tvText != null) { 
      tvText.setText(textLine); 
     } 
    } 

    return v; 
} 
} 

我的Runnable:

public class TimerRunnable implements Runnable { 
private TextView tv; 
final Handler mTimerHandler = new Handler(); 
String tag; 
long endtime; 
long sec; 

public TimerRunnable (TextView tv, String tag, long endtime) { 
    this.tv = tv; 
    this.tag = tag; 
    this.endtime = endtime; 
} 

public void run() { 
    if (tv.getTag().toString().equals(tag)) { 
     Calendar cal = Calendar.getInstance(); 
     sec = endtime - (cal.getTimeInMillis()/1000); //endtime - aktuelle Zeit 
     if (sec >= 0) { 

      // some code formatting the time in seconds to something like hh:mm:ss (var String txt) 

      tv.setText(txt); 

      System.out.println(txt); // only for tests; so I could see that runnable is still running 

      mTimerHandler.postDelayed(this, 1000); 
     } 
    } 
} 

public void stopHandler() { 
    mTimerHandler.removeCallbacksAndMessages(null); 
} 
} 

回答

5

我會做如下:

  1. 使用定時器 (http://developer.android.com/reference/java/util/Timer.html)和 讓它每隔一秒鐘或週期運行一次。
  2. 在Activity onCreate()中創建Timer對象,並在Activity onDestroy()中取消()該定時器(或者甚至更好地在onResume中啓動它並在onPause()中取消它)。所以這就是如何避免內存泄漏以及即使活動已關閉,計時器仍在運行的最簡單方法。
  3. 從運行計時器更新ListView。有兩種選擇:

    • 只需撥打adapter.notifyDatasetChanged()即可。但是這可能會導致「閃爍」的列表視圖。不過,如果這可以在您的應用程序中運行,您可以嘗試並檢出,因爲這是最簡單的實現。
    • 更新直接當前可見在ListView

      private void updateTime(){ 
      
      int firstVisibleItemIndex = listView.getFirstVisiblePosition(); 
      
      for (int i = 0; i < listView.getChildCount(); i++) { 
          View v = listView.getChildAt(i); 
      
           YourItem item = (YourItem)adapter 
             .getItem(firstVisibleItemIndex + i)); 
      
           ViewHolder vh = (ViewHolder) v.getTag(); 
                // Calculate the time somehow, i.e. call a methot on your data item 
                vh.tvTimer.setText(item.getElapsedTime()); 
      
      
          } 
      
      } 
      

      }

的TextViews所以我想第二個選項將是最好的。您可能想知道ViewHolder是什麼。 ViewHolder是一種增加ListView性能的模式。 http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder 你的適配器的問題是,findViewById()將在每次用戶滾動時被調用。 findViewById()是一個昂貴的方法調用,因爲它必須遍歷所有視圖子元素才能找到具有給定id的元素。在你的簡單適配器中,它可能不會有太大的影響(因爲你只有一個子視圖,即TextView)。但是ViewHolder是你應該在你的適配器實現始終使用

+0

謝謝,但我的ViewHolder似乎不起作用。當我使用setText()時,我通過ViewHolder獲得的TextViews保持空白。我不得不說這是我爲Android編程的第一個應用程序之一。 – yvi

+0

如果您發佈您的當前代碼,我可以嘗試幫助您 – sockeqwe

+0

嗨,而不是使用ViewHolder我現在使用findViewById()和現在的計時器和顯示列表的作品。謝謝! – yvi

1

所以在這裏我的代碼變化在於:

主要活動:

public class TimerActivity extends ListActivity { 

MyTimerAdapter myTimerAdapter = null; 
ArrayList<Long> timerList = new ArrayList<Long>(); 
ArrayList<String> textList = new ArrayList<String>(); 
ViewHolder vh = new ViewHolder(); 
ListView lv = null; 
View v = null; 
Timer t = null; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.listactivity); 
} 

@Override 
protected void onResume() { 
    super.onResume(); 

    vh.tvTimer = (TextView) findViewById(R.id.tv_timer); 
    vh.tvText = (TextView) findViewById(R.id.tv_text); 
    lv = (ListView) findViewById(android.R.id.list); 
    lv.setTag(vh); 
    t = new Timer(); 

    myTimerAdapter = new MyTimerAdapter(this, R.layout.timer_row, lv, vh); 
    setListAdapter(myTimerAdapter); 

    refreshView(); 
} 

@Override 
protected void onPause() { 
    myTimerAdapter.clear(); 
    myTimerAdapter.notifyDataSetChanged(); 
    super.onPause(); 
} 

@Override 
protected void onDestroy() { 
    t.cancel(); 
    super.onDestroy(); 
} 

private void refreshView() { 

    myTimerAdapter.clear(); 
    timerList.clear(); 
    textList.clear(); 

    // some code to read database and fill 
    // array timerList with a long value (used for displaying a countdown) 
    // and textList with some additional text 

    myTimerAdapter.add(timerList, textList); 
    myTimerAdapter.notifyDataSetChanged(); 

    updateTime(); 
} 

private void updateTime() { 

     t.scheduleAtFixedRate(new TimerTask() { 

      @Override 
      public void run() { 
       runOnUiThread(new Runnable() { 
        long sec; 
        long timerLine; 
        Calendar cal; 
        ViewHolder rvh; 
        View v; 

        @Override 
        public void run() { 
         if (lv.getChildCount() > 0) { 
         for (int i = 0; i < lv.getChildCount(); i++) { 
          v = lv.getChildAt(i); 
          rvh = (ViewHolder) v.getTag(); 
          timerLine = timerList.get(i); 
          cal = Calendar.getInstance(); 
          sec = timerLine - (cal.getTimeInMillis()/1000); 
          if (sec >= 0) { 

           // some code formatting the time in seconds to something like hh:mm:ss (var String txt) 

           rvh.tvTimer.setText(txt); 
          } 
         } 
         } 
        } 

       }); 
      } 

     }, 0, 1000); 
} 
} 

適配器:

public class MyTimerAdapter extends ArrayAdapter<String> { 
private Activity mContext; 
private ArrayList<Long> mTimer; 
private ArrayList<String> mText; 
private int mViewId; 
private ViewHolder mViewHolder; 
private ListView mListView; 
private int listSize; 

public MyTimerAdapter(Activity context, int textViewResId, ListView lv, ViewHolder vh) { 
    super(context, textViewResId); 
    mContext = context; 
    mViewId = textViewResId; 
    mViewHolder = vh; //(ViewHolder) lv.getTag(); 
    mListView = lv; 
    listSize = 0; 
} 

public void add(ArrayList<Long> timer, ArrayList<String> text) { 
    mTimer = timer; 
    mText = text; 
    listSize = mText.size(); 
} 

@Override 
public void clear() { 
    super.clear(); 
} 

@Override 
public int getCount() { // wird benötigt, wenn die Daten nicht schon im Konstruktor mitgeliefert werden. Ansonsten wird getView nicht aufgerufen! 
    return listSize; 
} 

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
    View v = convertView; 
    if (v == null) { 
     LayoutInflater vi = mContext.getLayoutInflater();//LayoutInflater.from(mContext); 
     v = vi.inflate(mViewId, null); 
    } 

    long timerLine = mTimer.get(position); 
    if (timerLine != 0) { 
     TextView tvTimer = mViewHolder.tvTimer; 
     if (tvTimer != null) { 
      tvTimer.setText("00:00:00"); 
     } 
    } 

    String textLine = mText.get(position); 
    if (textLine != null) { 
     TextView tvText = mViewHolder.tvText; 
     if (tvText != null) { 
      tvText.setText(textLine); 
     } 
    } 

    return v; 
} 
} 

ViewHolder類:

public class ViewHolder { 
TextView tvTimer; 
TextView tvText; 
int position; 
} 

如上所述,這是行不通的。我列表中的兩行都是空的。