0

I'm having some troubles with correct identification of items in a ListView.

There are 4 classes that matter, it's a lot of code so at first I'm going to explain the logic of those classes.

  • Enter the ListActivity and initialize its ListView
  • execute an AsyncTask that downloads JSON response from the server, parses it, populates the ListView with Objects and sets the adapter while showing a ProgressDialog
  • the PlaylistItem class includes methods which simply get the data from a single JSONObject. It is used to parameterize the ArrayList with its Objects
    • after the AsyncTask is done the list is filled with items and looks like |Button| Artist(TextView) - Title(TextView)

UPDATE

resolved 1st issue but still can't figure out what's wrong with buttons

2). I set an OnClickListener to my buttons in the Adapter's getView() method. To find out if the button is identified correctly I did nothing but just changed its background. BUT a click on a certain button forces the background of every 11th or 12th button to be changed. Can't figure it out so far.

I can't proceed to getting url and streaming audio until those problems are resolved, so any help is greatly appreciated. My classes go below, please ask if something appears unclear.

AudioList

         public class AudioList extends ListActivity {
private ListView lv;
private PlaylistLoader loader;
private AudioListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_audio_list);
    init(); // initialize the ListView

    /*--- populate the list with user's audio in case network connection is available ---*/
    loader = new PlaylistLoader(this, lv, adapter);
    if (Utils.isNetworkAvailable(this)) {
        loader.execute();
    } else {
        APP_CONSTANTS.NO_DATA_CONNECTION(this);
    }

}

@Override
protected void onResume() {
    super.onResume();
    lv.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                long arg3) {

            Toast.makeText(getApplicationContext(), Integer.toString(arg2),
                    Toast.LENGTH_SHORT).show();
            }
    });

}

private void init() {
    lv = getListView();
    lv.setTranscriptMode(0x00000000);
    lv.setDividerHeight(1);
    lv.setSmoothScrollbarEnabled(true);
    lv.setVerticalFadingEdgeEnabled(true);

}

PlaylistLoader

      public class PlaylistLoader extends AsyncTask<Void, Void, Void> {

private JSONObject usersPlaylist, singleJSONItem;
private JSONArray responseJSONArray;
private ListView lv;
private ArrayList<PlaylistItem> playlist;
private Activity a;
private PlaylistItem audioList;
private SharedPreferences prefs;
private ProgressDialog pd;
AudioListAdapter adapter;

public PlaylistLoader(Activity a, ListView lv, AudioListAdapter adapter) {
    this.lv = lv;
    this.a = a;
    this.adapter = adapter;
}

@Override
protected Void doInBackground(Void... arg0) {
    /*--- create new ArrayList of PlaylistItem Objects ---*/
    playlist = new ArrayList<PlaylistItem>();
    /*--- get the preferences using context of calling activity ---*/
    prefs = PreferenceManager.getDefaultSharedPreferences(a);
    try {
        /*--- download the response JSONObject from server // access_token and 
         * user_id come from activity's defaultSharedPreferences ---*/
        usersPlaylist = Utils.retrieveJsonObjectFromUrl(new URL(
                APP_CONSTANTS.REQUEST_AUDIO_LIST(prefs)), a);
        /*--- get the response array from received object ---*/
        responseJSONArray = usersPlaylist.getJSONArray("response");
        /*--- populate the ArrayList with Objects from the response array ---*/
        for (int i = 0; i < responseJSONArray.length(); i++) {
            singleJSONItem = responseJSONArray.getJSONObject(i);
            audioList = new PlaylistItem(singleJSONItem);
            playlist.add(audioList);

        }

    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (JSONException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

@Override
protected void onPreExecute() {
    super.onPreExecute();
    pd = new ProgressDialog(a);
    pd.setTitle("Please wait");
    pd.setMessage("Retrieving audio list...");
    pd.show();

}

@Override
protected void onPostExecute(Void result) {
    super.onPostExecute(result);
    lv.setVisibility(View.VISIBLE);
    pd.dismiss();
    /*--- set the adapter passed in constructor as an adapter for passed ListView ---*/
    adapter = new AudioListAdapter(a, R.layout.playlist_item, playlist);
    lv.setAdapter(adapter);

}
    }

AudioListAdapter

           public class AudioListAdapter extends ArrayAdapter<PlaylistItem> {
private PlaylistItem pl;
private Context context;
private int layoutResourceId;
private PlaylistItem aud;
private ArrayList<PlaylistItem> data = null;

public AudioListAdapter(Context context, int layoutResourceId,
        ArrayList<PlaylistItem> data) {
    super(context, layoutResourceId, data);
    this.layoutResourceId = layoutResourceId;
    this.context = context;
    this.data = data;

}

@Override
public PlaylistItem getItem(int position) {
    return super.getItem(position);
}

@Override
public int getCount() {
    return data.size();
}

@Override
public int getPosition(PlaylistItem item) {
    return super.getPosition(item);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    pl = new PlaylistItem();
    aud = getItem(position);

    if (convertView == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        convertView = inflater.inflate(layoutResourceId, parent, false);
        pl.btnPlay = (Button) convertView.findViewById(R.id.btn_list_play);
        pl.imgSaved = (ImageView) convertView
                .findViewById(R.id.img_list_audio_saved);
        pl.tvArtist = (TextView) convertView
                .findViewById(R.id.tvListItemArtist);
        pl.tvTitle = (TextView) convertView
                .findViewById(R.id.tvListItemSong);

        convertView.setTag(pl);
    } else {
        pl = (PlaylistItem) convertView.getTag();
        pl.btnPlay.setBackgroundResource(R.drawable.list_button_play);
    }

    pl.tvArtist.setText(aud.getArtist() + " " + "-");
    pl.tvTitle.setText(aud.getTitle());

    pl.btnPlay.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            /*--- vibrate if this option is enabled in the preferences ---*/
            if (APP_CONSTANTS.isHapticFeedbackEnabled(getContext())) {
                APP_CONSTANTS.doVibrate(getContext());

            }
         pl.btnPlay.setBackgroundResource(R.drawable.list_button_pause);

        }
    });

    return convertView;
}

PlayListItem

     public class PlaylistItem {

private String artist, title;
private JSONObject obj;
public Button btnPlay;
public TextView tvArtist, tvTitle;
public ImageView imgSaved;
public int duration;
public int audio_id;
public String url;

/*--- the constructor takes a single JSONObject from the response array ---*/
public PlaylistItem(JSONObject obj) {
    this.obj = obj;
}

public PlaylistItem() {
    // default constructor
}

/*--- the methods below return values by key from the passed JSONObject ---*/

public String getArtist() {
    try {
        artist = obj.getString("artist");
    } catch (JSONException e) {

        e.printStackTrace();
    }
    return artist;

}

public String getTitle() {
    try {
        title = obj.getString("title");
    } catch (JSONException e) {

        e.printStackTrace();
    }
    return title;
}

public int getID() {
    try {
        audio_id = obj.getInt("aid");
    } catch (JSONException e) {

        e.printStackTrace();
    }
    return audio_id;
}

public String getURL() {
    try {
        url = obj.getString("url");
    } catch (JSONException e) {

        e.printStackTrace();
    }
    return url;
}
    }
Droidman
  • 11,485
  • 17
  • 93
  • 141
  • 1) did you do any research on this ... it just happen when you have Clickable element in ListView row ... asked many times ... 2) asked many times too ... ListView is a smart control ... you have to understand it ... http://www.youtube.com/watch?v=wDBM6wVEO70 (if you get what for is `convertView` in `getView` - you'll have an answer...) ... anyway ... question is nicely written :) – Selvin Mar 01 '13 at 15:28
  • yes I did. And I watched this vides. Still can't find any solution that will work in my case – Droidman Mar 01 '13 at 15:31
  • heh store playlist state (pause/play) in PlaylistItem object then set this drawable depend on this state in getView ... and in onClick only set this state in selected PlayListItem – Selvin Mar 01 '13 at 15:34
  • anyway are you sure that youre using same class `PlaylistItem` as Adapter data and View holder? – Selvin Mar 01 '13 at 15:45
  • yep. My 1st version did use a ViewHolder, but I removed it since the PlaylistItem class does both things – Droidman Mar 01 '13 at 15:49

2 Answers2

1

Edit:

Try this

Take a custom Selector in your drawable button_play.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/pause_button"
          android:state_selected="true" />
    <item android:drawable="@drawable/play_button" />
</selector>

Modifty your adapter like this

 public class AudioListAdapter extends ArrayAdapter<PlaylistItem> {
private PlaylistItem pl;
private Context context;
private int layoutResourceId;
private PlaylistItem aud;
private ArrayList<PlaylistItem> data = null;
Button previous;

public AudioListAdapter(Context context, int layoutResourceId,
        ArrayList<PlaylistItem> data) {
    super(context, layoutResourceId, data);
    this.layoutResourceId = layoutResourceId;
    previous=new Button(context);
    this.context = context;
    this.data = data;

}

....
....

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    pl = new PlaylistItem();
    aud = getItem(position);

    if (convertView == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        convertView = inflater.inflate(layoutResourceId, parent, false);
        pl.btnPlay = (Button) convertView.findViewById(R.id.btn_list_play);
pl.btnPlay.setBackGroundResouce(R.drawable.button_play); //you can set here or in xml

        pl.imgSaved = (ImageView) convertView
                .findViewById(R.id.img_list_audio_saved);
        pl.tvArtist = (TextView) convertView
                .findViewById(R.id.tvListItemArtist);
        pl.tvTitle = (TextView) convertView
                .findViewById(R.id.tvListItemSong);

        convertView.setTag(pl);
    } else {
        pl = (PlaylistItem) convertView.getTag();
    }


    pl.tvArtist.setText(aud.getArtist() + " " + "-");
    pl.tvTitle.setText(aud.getTitle());

    pl.btnPlay.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            /*--- vibrate if this option is enabled in the preferences ---*/
            if (APP_CONSTANTS.isHapticFeedbackEnabled(getContext())) {
                APP_CONSTANTS.doVibrate(getContext());
            }
                //for some reason, the background gets changed for every 11th or 12th button in the list
             Button current=((Button)v);
              current.setSelected(true);
              previous.setSelected(false);
              previous=current;
        }
    });

    return convertView;
}

    }

The reason why your button and listitem not clickable is because Your list have a focus item button, so you need to setFocusable=false for your button. Try setting focusable=false for your button in the xml. If it is not worked for you than do like this

In your row xml file

1.set focusable=true for your button. 2.In the same set android:descendantFocusability="blocksDescendants" for your parent item.(i.e parent layout in which your views lie).

In getView() method after setting the onclickListener for the button, set focusable false for the button. It will work for sure. I hope this will help you..

Pragnani
  • 20,075
  • 6
  • 49
  • 74
1

BUT a click on a certain button forces the background of every 11th or 12th button to be changed. Can't figure it out so far.

You are fighting the way ListViews recycle the row layouts.
Think of it this way: if you have a ListView with 10,000 rows but can only fit 9 of them on the screen, then it doesn't make sense to create 10,000 unique layouts. This just waste resources, instead ListView only creates ~10 layouts and reuses them.

Solution: return each row to it's default state when it is reused. In getView() add:

} else {
    pl = (PlaylistItem) convertView.getTag();
    pl.btnPlay.setBackgroundResource(R.drawable.list_button_play);
    // I guessed at the resource's name         ^^^^^^^^^^^^^^^^
}

(Also you can make a few small changes to speed up your code. For instance, you only need one OnClickListener since they all contain the same code, make this a class variable and pass this to each play Button. There are more.)

Sam
  • 86,580
  • 20
  • 181
  • 179
  • just added that. Now the list behaves very strange, when I click a button it changes the background of some other button in the list or no action at all. I think I need a way to connect each button to the PlaylistItem object at corresponding position. Since the button click should result in getting the url from corresponding PlaylistItem object and starting playback. While no other button was clicked the current button should have the pause drawable even when the user scrolls up or down.. – Droidman Mar 01 '13 at 20:38
  • Please update your question with the current code in `getView()` so I can see what's happening. – Sam Mar 01 '13 at 20:41
  • this screenshot shows pretty much the same thing I need http://cs407217.vk.me/v407217837/7b9b/H9d7TKat74M.jpg – Droidman Mar 01 '13 at 20:41
  • Inside `onClick()` you still need to use `v.setBackgroundResource(R.drawable.list_button_pause);` (perhaps you misunderstood the last comment in my answer.) – Sam Mar 01 '13 at 20:44
  • I do this, please check the updated getView(); Now it sets some other button's BG to pause and sets it back to play when this button became invisible due to scrolling – Droidman Mar 01 '13 at 20:47
  • Very close but this small difference is important: inside the else-block your need to use `pl.btnPlay.setBackgroundResource(...)`, but inside `onClick()` you must use `v.setBackgroundResource(...)`. – Sam Mar 01 '13 at 20:50
  • ah, thanks. Now it works but is there a way to persist that state as seen on my screenshot in a previous comment? Since I need the corresponding button to control the MediaPlayer..would using a ToggleButton instead of a button be a solution? – Droidman Mar 01 '13 at 20:52
  • Yes. You need to keep track of which you have pushed, either in PlaylistItem or a separate Array. I answered a similar question: [ListView bug](http://stackoverflow.com/q/15074650/1267661) yesterday, in that case notice how I used `expanded` to show or hide an extra View and I tracked the changes in `expanded` inside the Button's OnClickListener. Does that make sense? – Sam Mar 01 '13 at 20:59