1

I am new to Android development and I am creating a Music Player app. I am done with the logical part. Now I am focusing on the appearance of the app. One of the things I want to do is to highlight a row when the corresponding 'Play' button in the row is clicked.

Desired Effect: When Play button of a row is clicked the row gets highlighted (Background color changes) and simultaneously the background of the last clicked row gets reset.

Attempt: I tried storing the previously selected View and make required changes in the current and previous views.

Following is the code I wrote in my custom Adapter for the ListView:

(Relevant code is written in the onClickListener() of the buttons)

package com.example.mediaplayer;

import android.app.Activity;
import android.content.ContentUris;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;

public class MusicAdapter extends ArrayAdapter<Music> {

    private MediaPlayer mp;
    private ArrayList<Integer> Lengths = new ArrayList<>();
    private int mCurrentPosition = 0;
    private LinearLayout mpreviousView = null;
    private LinearLayout mitemView = null;
    private IAdapterToFragment listener;

    private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            releaseMediaPlayer();
            Lengths.set(mCurrentPosition, 0);
            mitemView.setBackgroundColor(Color.parseColor("#FDBCA8"));
        }
    };

    public interface IAdapterToFragment {
        void onSuccess(String songName);
    }

    public MusicAdapter(Activity context, ArrayList<Music> songs, IAdapterToFragment listener) {

        super(context, 0, songs);
        for(int i = 0; i < songs.size(); i ++) {
            Lengths.add(0);
        }

        this.listener = listener;
    }

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

        mpreviousView = mitemView;

        View musicItemView = convertView;

        if(musicItemView == null) {
            musicItemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.music_item, parent, false);
        }

        Music currentSong = getItem(position);

        LinearLayout itemView = musicItemView.findViewById(R.id.item);
        mitemView = itemView;

        TextView songnameView = musicItemView.findViewById(R.id.song_name);
        songnameView.setText(currentSong.getMusicName());

        Button stop = (Button) musicItemView.findViewById(R.id.stop_button);
        Button pause = (Button) musicItemView.findViewById(R.id.pause_button);
        Button play = (Button) musicItemView.findViewById(R.id.play_button);

        play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                releaseMediaPlayer();

                if(listener != null)
                    listener.onSuccess(currentSong.getMusicName());

                mCurrentPosition = position;

                itemView.setBackgroundColor(Color.parseColor("#FAA185"));

                long songID = currentSong.getMusicResourceId();
                Uri uri = ContentUris.withAppendedId(
                        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        songID);
                mp = MediaPlayer.create(getContext(), uri);

                mp.start();
                mp.seekTo(Lengths.get(position));

                mp.setOnCompletionListener(mCompletionListener);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(listener != null)
                    listener.onSuccess("Play another song!");

                releaseMediaPlayer();
                itemView.setBackgroundColor(Color.parseColor("#FDBCA8"));
                Lengths.set(position, 0);
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int currentPosition = 0;
                if(mp != null) {
                    currentPosition = mp.getCurrentPosition();
                    mp.stop();
                    Lengths.set(position, currentPosition);
                    releaseMediaPlayer();
//                    mp.setOnCompletionListener(mCompletionListener);
                }
            }
        });

        return musicItemView;
    }

    private void releaseMediaPlayer() {
        if (mp != null) {
            mp.release();
            mp = null;

            if(mpreviousView != null)
                mpreviousView.setBackgroundColor(Color.parseColor("#FDBCA8"));

//            mp.abandonAudioFocus(mOnAudioFocusChangeListener);
        }
    }
}

This however doesn't work because ListView recycles the views. So I am not accessing a distinct row, rather, I am making changes to many rows simultaneously.

Questions:

  1. Is there a way I can uniquely access a row in a ListView in the adapter class itself?

  2. Is there an easier way to achieve the desired effect?

Thank you.

EDIT:

After researching a lot and trying several methods from related questions, I still haven't figured out a way to get the desired effect. All the methods I tried result in multiple rows getting highlighted when one button is clicked, again, due to recycling of views. If you are looking for an alternate solution, you can either:

  1. (Not Optimal) Replace ListView with some layout that doesn't recycle views. This way the exactly one item gets highlighted and remains highlighted even when you scroll past it.

  2. Remove all types of buttons from the list, create a list item selector and set it as the background for you list_item. Follow the steps mentioned here: Reset background color in a ListView. This is the one I am using for my app now.

Mihir Sahu
  • 11
  • 2
  • Try add `if (mCurrentPosition == position) itemView.setBackgroundColor(Color.parseColor("#FAA185")); else itemView.setBackgroundColor(Color.parseColor("#FDBCA8"));` before `return musicItemView;` in getView(). – i_A_mok Mar 19 '21 at 03:58
  • @i_A_mok I think you misunderstood what the mCurrentPosition variable does. It stores the time at which a given song is paused. Anyway, I realized that this code was a total mess so I don't blame you. What were you suggesting by (mCurrentPosition == position) ? – Mihir Sahu Mar 19 '21 at 11:33

2 Answers2

0

Set it such that all ListView item's Layout has a corresponding boundary or whatever with the transparent color. When that particular item has been accessed, simply change the color of that widget to something else. Don't forget to change back the color to transparent once it's task has finished. You can do multiple boundaries/ whaever you want provided it is one ListView item and not multiple. There are other ways to handle that, but in most cases, is not a good design pattern.

mindoverflow
  • 730
  • 4
  • 13
0

Stop checking convertView for null will stop ListView from recycle item views, so change:

    View musicItemView = convertView;

    if(musicItemView == null) {
        musicItemView = LayoutInflater.from(getContext()).inflate(
                R.layout.music_item, parent, false);
    }

to

    View musicItemView = LayoutInflater.from(getContext()).inflate(R.layout.music_item, parent, false);

Then every row is created with new views.

Try this adapter code:

public class MusicAdapter extends ArrayAdapter<Music> {

private MediaPlayer mp;
private ArrayList<Integer> Lengths = new ArrayList<>();
private int mCurrentPosition = -1; //Changed
//private LinearLayout mpreviousView = null;
private LinearLayout mitemView = null;
private IAdapterToFragment listener;

private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        releaseMediaPlayer();
        Lengths.set(mCurrentPosition, 0);
        mitemView.setBackgroundColor(Color.parseColor("#FDBCA8"));
        mitemView = null; //Added
        mCurrentPosition = -1; //Added
    }
};

public interface IAdapterToFragment {
    void onSuccess(String songName);
}

public MusicAdapter(Activity context, ArrayList<Music> songs, IAdapterToFragment listener) {

    super(context, 0, songs);
    for(int i = 0; i < songs.size(); i ++) {
        Lengths.add(0);
    }

    this.listener = listener;
}

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

    //mpreviousView = mitemView;

    //View musicItemView = convertView;

    //if(musicItemView == null) {
        View musicItemView = LayoutInflater.from(getContext()).inflate(R.layout.music_item, parent, false);
    //}

    Music currentSong = getItem(position);

    LinearLayout itemView = musicItemView.findViewById(R.id.item);
    mitemView = itemView;

    TextView songnameView = musicItemView.findViewById(R.id.song_name);
    songnameView.setText(currentSong.getMusicName());
    if (mCurrentPosition == position) itemView.setBackgroundColor(Color.parseColor("#FAA185")); //Added

    Button stop = (Button) musicItemView.findViewById(R.id.stop_button);
    Button pause = (Button) musicItemView.findViewById(R.id.pause_button);
    Button play = (Button) musicItemView.findViewById(R.id.play_button);

    play.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            releaseMediaPlayer();

            if(listener != null)
                listener.onSuccess(currentSong.getMusicName());

            mCurrentPosition = position;

            itemView.setBackgroundColor(Color.parseColor("#FAA185"));
            mitemView = itemView; //Added

            long songID = currentSong.getMusicResourceId();
            Uri uri = ContentUris.withAppendedId(
                    android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    songID);
            mp = MediaPlayer.create(getContext(), uri);

            mp.start();
            mp.seekTo(Lengths.get(position));

            mp.setOnCompletionListener(mCompletionListener);
        }
    });

    stop.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(listener != null)
                listener.onSuccess("Play another song!");

            releaseMediaPlayer();
            itemView.setBackgroundColor(Color.parseColor("#FDBCA8"));
            Lengths.set(position, 0);
            mitemView = null; //Added
        }
    });

    pause.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int currentPosition = 0;
            if(mp != null) {
                currentPosition = mp.getCurrentPosition();
                mp.stop();
                Lengths.set(position, currentPosition);
                releaseMediaPlayer();
//                    mp.setOnCompletionListener(mCompletionListener);
            }
        }
    });

    return musicItemView;
}

private void releaseMediaPlayer() {
    if (mp != null) {
        mp.release();
        mp = null;

        if (mitemView != null) //Changed
            mitemView.setBackgroundColor(Color.parseColor("#FDBCA8")); //Changed

//            mp.abandonAudioFocus(mOnAudioFocusChangeListener);
    }
}
}
i_A_mok
  • 2,744
  • 2
  • 11
  • 15
  • I tried your suggestion of not checking for musicItemView == null and it helps to avoid multiple rows from being highlighted from one button click. However, when the user scrolls past one highlighted row and comes back again, the highlight goes away, ie the color resets. What I am really looking for is an effect similar to `android:state_activated="true"`as shown [here](https://stackoverflow.com/questions/10149581/reset-background-color-in-a-listview?noredirect=1&lq=1) but with a button click. Nonetheless, thank you for trying to help. – Mihir Sahu Mar 24 '21 at 11:26