views:

34

answers:

1

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

Catch the longpress and prevent the dataset from being updated. Override the listview's onlongpress method. I'll post code in a minute

Update: Actually. That might not work since it could update while you're pressing but before the longpress method is called. If what I suggested above doesn't work, I would implement onClickListener on your ListActivity, listen for any motion event, set the global variable preventing the update, and return false in the onMotionEvent() method so that the list item can consume the click.

Falmarri
I found a solution based partly on your answer :) Thanks.
brambury