Intereting Posts
Глобальное решение ошибкиHandling с RxJava только тогда, когда onError не реализовано Маскирование Drawable / Bitmap на Android Использование AsyncTask Поддерживает ли Android KitKat устройства, поддерживающие Bluetooth LE, в качестве периферийного устройства? Как мы получим фрагмент Android, который появится в реале? Что является самым точным способом узнать, является ли посетитель для мобильного веб-клиента уникальным? Конвертирование xml в json android Android: Google MapView показывает индикатор прогресса во время загрузки карты Nexus 6 и MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); Почему у Android нет адреса mac для 3g, когда iOS? Размер изображения для Android (только портрет) для mdpi, hdpi, xhdpi, xxhdpi Андроид HTTP HTTP-запрос с использованием самозаверяющего сертификата и CA Android M, запрашивающий разрешения с правами доступа Измерение скорости загрузки с помощью Java / Android Тестирование обновления adb и установка?

Android: несколько таймеров в ListView с обработчиком и runnable. 2 Проблемы

Я создаю приложение, которое содержит ListView с двумя столбцами. В первом столбце должен отображаться обратный отсчет, а во втором столбце – дополнительный текст, объясняющий, для чего обратный отсчет. Ниже вы видите мой код, который работает … более или менее. У меня есть список с несколькими строками, и таймеры тикают. Одна из проблем: set.Text () в моем runnable, кажется, переопределяет все строки. Например, runnable для строки 1 задает текст также для строк 2 и 3, runnable для строки 2 устанавливает текст также для 1 и 3 и так далее. Это приводит к тому, что первый столбец мигает (с правильными значениями и значениями других строк). Как установить текст для определенной строки в списке?

Следующая проблема: 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); } } 

Я бы сделал следующее:

  1. Используйте таймер ( http://developer.android.com/reference/java/util/Timer.html ) и пусть он периодически запускается каждую секунду или что-то еще.
  2. Создайте объект Timer в Activity onCreate () и отмените () таймер в Activity onDestroy () (или даже лучше запустите его в onResume и отмените его в onPause ()). Таким образом, это самый простой способ избежать утечек памяти и запуска вашего таймера, даже если активность закрыта.
  3. Обновите ListView из работающего таймера. Есть два варианта:

    • Просто вызовите adapter.notifyDatasetChanged() из вашего таймера. Но это может привести к «миганию» списка. Однако вы можете попробовать и проверить, может ли это работать в вашем приложении, поскольку это самая простая реализация.
    • Обновите прямо TextViews, которые в настоящее время видны в 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, ie call a methot on your data item vh.tvTimer.setText(item.getElapsedTime()); } } 

      }

Поэтому я предполагаю, что второй вариант будет лучшим. Вы можете задаться вопросом, что такое ViewHolder. ViewHolder – это шаблон, который повышает производительность вашего ListView. http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder Проблема вашего адаптера заключается в том, что findViewById () будет вызываться каждый раз, когда пользователь прокручивает. FindViewById () – дорогостоящий вызов метода, поскольку он должен пересекать все дочерние элементы просмотра, чтобы найти тот, у кого есть данный идентификатор. В вашем простом адаптере это может не сильно повлиять (поскольку у вас есть только одно дочернее представление TextView). Но ViewHolder – это то, что вы всегда должны использовать в своей реализации Adapter

Итак, здесь мой код с изменениями:

Основная деятельность:

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

Как уже упоминалось, это не работает. Обе строки в моем списке пустые.