2

Here I am trying to make a music streaming app which works using an API, and I have utilized the MusicPlayer class available in Android. Here what I am trying to do is to get the response from the API in a listview and then click on any item in the listview to stream that particular song. The songs are present on a server and I am getting the correct responses from the server and music streaming is also implemented correctly according to me. The music progress is shown with the help of a SeekBar and I am 100% sure it is also working as I wanted it to be.

The new thing which I added to it recently is that, whenever I click on a particular song item from list, the headphone icon present on left of that item changes to play icon as shown in the screenshot attached below

Screenshot of the app from my phone

Here, play icon on left of songs show that they are recently played, but I want play icon only on those songs which is currently playing, not on recently played songs too.

But I cannot figure out way to do this, I have tried some things like

  1. Storing the clicked item position somewhere in the class, to use it later.
  2. Searching on internet, a way to modify only those list item which are not clicked in listview.
  3. Modifying the image resource of the recently clicked item when stop icon is clicked.

But 3rd one didn't work, and haven't found a way to achieve the 1st and 2nd one..

Code snippet of ListView implementation

        CustomSongList adapter = new CustomSongList(Songs.this, songdetails);
        list = (ListView) findViewById(R.id.list);

        list.setAdapter(adapter);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                url = songdetails.get(position).getUrl(); // your URl
                pos = position;
                ImageView listenImage = (ImageView) view.findViewById(R.id.musicicon);
                listenImage.setImageResource(R.drawable.play);   /* I am changing icon from headphone icon to play icon here*/

                if (mediaPlayer != null) {
                    mediaPlayer.stop();
                }

                mediaPlayer = new MediaPlayer(); 
                mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                try {
                    mediaPlayer.setDataSource(url);
                    mediaPlayer.prepare();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mediaFileLengthInMilliseconds = mediaPlayer.getDuration();// gets the song length in milliseconds from URL
                mediaPlayer.start();
                imPlay.setVisibility(View.INVISIBLE);
                imPause.setVisibility(View.VISIBLE);
                primarySeekBarProgressUpdater();
            }
        });

Here is a code snippet of ListView Adapter class.

public View getView(int position, View view, ViewGroup parent) {
    LayoutInflater inflater = context.getLayoutInflater();

    View rowView = inflater.inflate(R.layout.songlist_item, null, true);

    TextView Title = (TextView) rowView.findViewById(R.id.title);
    TextView Singer = (TextView) rowView.findViewById(R.id.singer);
    TextView Duration = (TextView) rowView.findViewById(R.id.duration);

    Title.setText(songlist.get(position).getTitle());
    Singer.setText("Singer : " + songlist.get(position).getSinger());
    Duration.setText("Duration : " + songlist.get(position).getDuration()+" mins");
    return rowView;
}

And this is the snippet of songs layout file

<ListView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/seekBar"
    android:layout_below="@+id/songheading"
    android:layout_margin="10dp"
    android:background="@android:color/transparent"
    android:cacheColorHint="@android:color/transparent"
    android:divider="@drawable/transperent_color"
    android:dividerHeight="5dip" />

<SeekBar
    android:id="@+id/seekBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/buttonPlay" />

<ImageView
    android:id="@+id/buttonPlay"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:background="@drawable/play" />

<ImageView
    android:id="@+id/buttonPause"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:background="@drawable/pause" />


<ImageView
    android:id="@+id/buttonStop"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentEnd="true"
    android:background="@drawable/stop" />

The headphone icon set for each item of listview is done by following code snippet

 <ImageView
    android:id="@+id/musicicon"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:contentDescription="@string/app_name"
    android:gravity="center"
    android:layout_marginTop="10dp"
    android:src="@drawable/listen_icon" />

Edit

Applied the solution given by @KalaBalik

thanks @KalaBalik for posting the steps to mark the song, but I have applied your steps step by step, and what I found is that my app now doesn't show the list itself, when loading is complete, here is the screenshot No item loaded in listview

And here are the edits performed by me on the code as you said

Code snippet of listview

<ListView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/seekBar"
    android:layout_below="@+id/songheading"
    android:layout_margin="10dp"
    android:background="@android:color/transparent"
    android:cacheColorHint="@android:color/transparent"
    android:choiceMode="singleChoice"                       // -----here
    android:divider="@drawable/transperent_color"
    android:dividerHeight="5dip" />

CheckedTextView implementation

<CheckedTextView
        android:id="@+id/tvChecked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:drawableStart="@drawable/playing" />

Compound drawable for checkedTextview,
playing.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/play" android:state_checked="true" />
<item android:drawable="@drawable/listen_icon" />

SpannableStringBuilder implementation

String finalString = title + "\n" + singer + "\n" + duration;
    SpannableStringBuilder sb = new SpannableStringBuilder();
    Typeface exoMedium = Typeface.createFromAsset(context.getAssets(), "fonts/Exo-Medium.ttf");
    TypefaceSpan exoMediumSpan = new CustomTypeFaceSpan("", exoMedium);
    sb.setSpan(exoMediumSpan, finalString.indexOf(title), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.setSpan(new ForegroundColorSpan(Color.BLUE), finalString.indexOf(singer), singer.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    sb.setSpan(new ForegroundColorSpan(Color.CYAN), finalString.indexOf(duration), duration.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

I have created a CustomTypeFaceSpan using this link

setting SpannableStringBuilder in checkedTextView holder.checkedTextView.setText(sb); here I Used ViewHolder pattern

and setting listview checked true when item clicked

list.setAdapter(adapter);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                list.setItemChecked(position, true);

But result is in the screenshot above, tell me where I have done something wrong.

Divya Gupta
  • 494
  • 8
  • 24
  • You would greatly increase the likelihood of getting an answer if you asked **one question per post** and condensed the code to a [minimal, yet complete and verifiable example](https://stackoverflow.com/help/mcve). – kalabalik Nov 13 '17 at 14:13
  • In this case, "complete and verifiable" means that you have to include your (condensed) adapter and your XML layout and item layout files. – kalabalik Nov 13 '17 at 15:43

2 Answers2

1

What you need is to somehow mark or "check" the song currently playing. This can be done with a CheckedTextView, for example.

  1. Put this in your ListView XML: android:choiceMode="singleChoice"

  2. Inside your ListItem XML, I recommend having only one CheckedTextView. You can still do your formatting by (a) using a compound drawable for your image (for example: android:drawableStart="@drawable/playing") and (b) setting the text to a String from a SpannableStringBuilder programmatically. That way you can have different font sizes, styles and colors within one string which you put in the (one) CheckedTextView. In theory, you can have more complex listItem layouts, but it gets complicated quickly.

  3. In your listener, you simply check the item at the current position, like so: list.setItemChecked(position, true);

  4. Now that (after initialising) you always have one and just one checked list item, all you have to do is react to it. You can do so with a state list on the compound drawable. This state list would hold instructions about when (under which state: checked or not) one or the other drawable will be shown.

  5. In order for the ListView to run efficient it is recommended (but not necessary) to implement the ViewHolder pattern, like so:

Inside the adapter:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder viewHolder;
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_item, parent, false);
        viewHolder = new ViewHolder(convertView);
        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }
    viewHolder.checkedTextView.setText(filteredList.get(position));
    return convertView;
}

static class ViewHolder {
    final CheckedTextView checkedTextView;

    private ViewHolder(View convertView) {
        checkedTextView = (CheckedTextView) convertView.findViewById(R.id.listentry);
    }
}
kalabalik
  • 3,792
  • 2
  • 21
  • 50
  • so what you are trying to say is to add an additional checkedTextview to my pre- existing code in listitem, which already contains two textview? I understood the rest of the answer which you gave, but, not just the statement containing "checkTextView" – Divya Gupta Nov 14 '17 at 03:17
  • Thanks @kalaBalik, as you gave some solutions, I have tried to apply them , I hope I am right but let me post the edits you suggested, as the app now doesn't even show the list itself, after loading is complete, here is the screenshot – Divya Gupta Nov 14 '17 at 05:10
  • 1
    @DivyaGupta I need the full code to debug: activity, adapter, layout XML, itemview layout XML and the data holding class (`Songdetails`). – kalabalik Nov 14 '17 at 06:54
  • Sure, I am attaching the drive links. [This](https://drive.google.com/open?id=1Oiqpp6K0DUdoYFvLY9uoplp_zwuhhUK2) link is of SongDetails data holder class. [This](https://drive.google.com/open?id=1o5OOEoY99SisOu1bfxxMoCpANOOU4VEf) link is of CustomSongsList(adapter class). [This](https://drive.google.com/open?id=1zOIfphjvFETAwUmWIo9jZTiocZd8n5ib) link is of activity class of Songs. [This](https://drive.google.com/open?id=1hAUePo2iWMM8a5JU6XFnG7VCCX5gFu1f) link is of listitem layout. [This](https://drive.google.com/open?id=1hihGyxClSO_rxd8z7yiOPYkVbVODDXf-) link is of layout xml. – Divya Gupta Nov 14 '17 at 07:48
1

The code you linked does not contain all the changes I proposed. For example, songlist_item does not reference the selector. Most importantly, I do not want to deal with the details of MediaPlayer prepare callbacks. Therefore I made a minimal, yet complete and verifiable example of your problem. Please test and understand the code separately and then weave it into your project.

MainActivity:

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ListView listView = findViewById(R.id.list);
        ArrayList<String> songdetails = new ArrayList<>();
        songdetails.add("Song1");
        songdetails.add("Song2");
        songdetails.add("Song3");
        CustomArrayAdapter adapter = new CustomArrayAdapter(this, R.layout.listitem, songdetails);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                listView.setItemChecked(i, true);
                // the selected song can be found at position i of songdetails
                // start playing the selected song here
            }
        });
    }
}

CustomArrayAdapter:

public class CustomArrayAdapter extends ArrayAdapter<String> {

    private final Activity context;
    private final int resource;
    private ArrayList<String> songlist;

    CustomArrayAdapter(Activity context, int resource, ArrayList<String> songlist) {
        super(context, resource);
        this.context = context;
        this.resource = resource;
        this.songlist = songlist;
    }

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

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        final ViewHolder holder;
        String song = songlist.get(position);

        LayoutInflater inflater = context.getLayoutInflater();
        if (convertView == null) {
            convertView = inflater.inflate(resource, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.checkedTextView.setText(song);

        return convertView;
    }

    static class ViewHolder {
        final CheckedTextView checkedTextView;

        private ViewHolder(View convertView) {
            checkedTextView = convertView.findViewById(R.id.tvChecked);
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:choiceMode="singleChoice" />

listitem.xml

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tvChecked"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:drawableStart="@drawable/checkedstatelist" />

And checkedstatelist.xml (residing in res/drawable/):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@android:drawable/ic_media_play" android:state_checked="true" />
    <item android:drawable="@android:drawable/ic_media_pause" android:state_checked="false" />
</selector>

And this is the result:

enter image description here

kalabalik
  • 3,792
  • 2
  • 21
  • 50
  • I added the selector in the songlist_item with the the selector XML file name as `playing.xml` , you might have noticed that, but I still find the same output, not as it is shown in your posted output,not even list is getting populated in my output in my posted screenshot above – Divya Gupta Nov 14 '17 at 11:41
  • Yes, but this is a question about `MediaPlayer`. – kalabalik Nov 14 '17 at 12:22
  • The problem is solved now, listview is getting populated, all I had to do was remove the viewholder pattern from adapter. Thanks @KalaBalik for helping me to know the checkable behaviour of listview, now listview icons are changing as they should. – Divya Gupta Nov 14 '17 at 13:27
  • I just wanted to know @kalabalik, `checkedTextView` is so rare thing anyone encounters.How did you arrive at the solution about this so accurately?? – Divya Gupta Apr 02 '18 at 14:00
  • 1
    @DivyaGupta Selection (and unselect and the attribute `selected`) in `ListView` can be a real pain. When I struggled with such a problem, I found the `CheckedTextView` solution somewhere on the internet and adopted it to my purposes. – kalabalik Apr 03 '18 at 14:29