I have a ListView which is updated by some service sending intent. If update event arrives while I'm pressing some item in the ListView I get some weird behavior. The default orange rectangle in the pressed item disappears and some other item(s)'s text becomes darker (as if its item is being pressed).
How do I postpone ListView update after it becomes "not pressed"? Or more specifically which events should I listen to in order to determine that ListView is no longer pressed? (I can create some thread executed periodically to update when it's appropriate but I think it's overkill). Or maybe there are better solution or workaround.
Here is sample code illustrating the problem. Service sends update intents every 2 seconds. If I try to long press some item in the list I get the weird behavior I described above.
The activity:
package org.me.listviewupdate;
import android.app.ListActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ArrayAdapter;
public class MyActivity extends ListActivity {
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_DATA:
mAdapter.notifyDataSetChanged();
break;
default:
super.handleMessage(msg);
break;
}
}
}
private class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
}
private static final int MSG_UPDATE_DATA = 0;
private String[] mItems = new String[] { "Item1", "Item2", "Item3", "Item4" };
private ArrayAdapter<String> mAdapter;
private Handler mHandler;
private BroadcastReceiver mBroadcastReceiver;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mHandler = new MyHandler();
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
android.R.id.text1, mItems);
setListAdapter(mAdapter);
mBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(mBroadcastReceiver, new IntentFilter(MyService.UPDATE_EVENT));
startService(new Intent(this, MyService.class));
}
@Override
protected void onDestroy() {
unregisterReceiver(mBroadcastReceiver);
stopService(new Intent(this, MyService.class));
super.onDestroy();
}
}
The service:
package org.me.listviewupdate;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MyService extends Service {
private class MyUpdateTask implements Runnable {
public void run() {
sendBroadcast(new Intent(UPDATE_EVENT));
}
}
public static final String UPDATE_EVENT =
"org.me.listviewupdate.intent.event.UPDATED";
private static final int UPDATE_INTERVAL = 2;
private ScheduledExecutorService mUpdater;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mUpdater = Executors.newSingleThreadScheduledExecutor();
}
@Override
public void onDestroy() {
mUpdater.shutdownNow();
super.onDestroy();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
mUpdater.scheduleAtFixedRate(new MyUpdateTask(), UPDATE_INTERVAL,
UPDATE_INTERVAL, TimeUnit.SECONDS);
}
}
Thank you.
My solution. Well, just in case it may help somebody here is my solution. The code added to MyActivity.onCreate()
:
getListView().setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean isReleased = event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL;
if (mHasPendingUpdate && isReleased) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
return false;
}
});
getListView().setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
boolean isKeyOfInterest = keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|| keyCode == KeyEvent.KEYCODE_ENTER;
boolean isReleased = event.getAction() == KeyEvent.ACTION_UP;
if (mHasPendingUpdate && isKeyOfInterest && isReleased) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
return false;
}
});
Also I added a variable mHasPendingUpdate
and modified MyHandler
:
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_DATA:
if (getListView().isPressed()) {
mHasPendingUpdate = true;
} else {
mAdapter.notifyDataSetChanged();
mHasPendingUpdate = false;
}
break;
default:
super.handleMessage(msg);
break;
}
}
}