I have a List view of ringtones with a play image view in each row for each ringtone ..
This is the view of it..
Now obviously when user clicks on a play button it should switch into pause button.. I have implemented this and it's all good so far :
Interface(In adapter)
public interface PlayPauseClick {
void playPauseOnClick(int position);
}
private PlayPauseClick callback;
public void setPlayPauseClickListener(PlayPauseClick listener) {
this.callback = listener;
}
Adapter(In getView)
Product product = (Product) mDataItems.get(position);
holder.playPause=(ImageView)v.findViewById(R.id.playPause);
holder.playPause.setImageResource(product.getPlayPauseId());
holder.playPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (callback != null) {
callback.playPauseOnClick(position);
}
}
});
Activity
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if (product.paused) {
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
};
Now I have a problem :
I don't know how to track the currently playing row,I mean if user wants to play a song from another row, first I have to change currently pause toggle to play, then play the new one.
Can you help me with this please !?
After some digging I found Two answers..
1: We can store the index of the current song which is played in the adapter.
So we just need to turn on pause when we click on an other button.
In Adapter :
int currentPosition = -1;
...
if (callback != null) {
callback.playPauseOnClick(position);
if (currentPosition == -1){
currentPosition = position;
} else if (currentPosition == position){
currentPosition = -1;
} else if (currentPosition != -1 && currentPosition != position){
callback.playPauseOnClick(currentPosition);
currentPosition = position;
}
}
2: Set resources for every listview data item
inside callback or anywhere can get listview data:
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if (product.paused) {
for(int i=0;i< productList.size();i++)
{
movieList.get(i).setPlayPauseId(R.drawable.ic_play);
movieList.get(i).paused = true;
}
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
}
You can do this
first, declare a global variable
int lastposition=null;
then
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if(lastposition!=null)
{
Product previous = productList.get(lastposition);
previous.setPlayPauseId(R.drawable.ic_pause);
previous.paused=false;
lastposition=position;
} else lastposition=position;
if (product.paused) {
//new loadSingleView().execute(product.image_url);
//Log.d(LOG, product.image_url);
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
};
Replace the above code with code in your activity
You could hold a variable "mCurrentPosition" to hold the currently playing track.
Then create two separate methods, one for play and one for pause. Simply pause the current track and play the one that was clicked.
Remember to also set the current position when you play a new track.
Something like this:
#Override
public void onClick(View v) {
if (callback != null) {
callback.pause(mCurrentPosition);
mCurrentPosition = position;
callback.play(position);
}
}
You may need to adjust it a bit as I don't know how you've setup your "callback" or where "position" is coming from. I'm assuming something like "getAdapterPosition()". But hopefully you can see how to do it by this example. If not please comment.
Edit in response to updated question:
For the interface do something like this:
public interface PlayPauseClick {
void pause(int position);
void play(int position);
}
Edit2:
#Override
void pause(int position) {
if(position != null) {
Product product = productList.get(position);
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
adapter.notifyDataSetChanged();
}
}
#Override
void play(int position) {
// Homework exercise.
}
Related
So, I followed this link for single click functionality using a radio button in my case inside a recyclerview. Its working almost fine. Only facing one issue, is when you try tapping twice on the first item of recyclerview, it always stays true even though you try hitting any other item after that. Below is the logic which I tried to implement.
private List<Profile> profileList;
private Context context;
private RadioButton lastChecked = null;
private int lastCheckedPos = 0;
public void onBindViewHolder(#NonNull MyHolder holder, int position) {
Profile profile = profileList.get(position);
holder.imgRadioButton.setChecked(profile.isSelected());
holder.imgRadioButton.setTag(position);
holder.imgRadioButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int clickpos = (Integer) holder.imgRadioButton.getTag();
if (lastCheckedPos != clickpos) {
if (holder.imgRadioButton.isChecked()) {
if (lastChecked != null) {
lastChecked.setChecked(false);
profileList.get(lastCheckedPos).setSelected(false);
}
lastChecked = holder.imgRadioButton;
lastCheckedPos = clickpos;
} else {
lastChecked = null;
}
profileList.get(clickpos).setSelected(holder.imgRadioButton.isChecked());
}
}
});
}
Dont know where its messing up any logic. But I want it to work in a regular way how radio button functions, but in a recyclerview.
You need to try this.
Add lastChecked.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
lastCheckededPos = getAdapterPosition();
notifyDataSetChanged();
}
});
to the ViewHolder() constructor.
Then use this in onBindViewHolder() method:
// this condition un-checks previous selections
holder.lastChecked.setChecked(lastCheckedPos == position);
That's it. Now your radio button is selected One at a time and selecting other radio button will un-select previous selection.
I have a list view of songs with play and pause button in every row..
I can't have two pause Icon(two playing song) in my list view So I need to first reset all of them to play Icon then set the selected view to pause Icon..
How can I do this ? Or can you offer a better solution for this ?
This is my code :
In model class ( Product ) :
public int currentPosition= -1;
in Adapter:
public interface PlayPauseClick {
void playPauseOnClick(int position);
}
private PlayPauseClick callback;
public void setPlayPauseClickListener(PlayPauseClick listener) {
this.callback = listener;
}
.
.
.
holder.playPauseHive.setImageResource(product.getPlayPauseId());
holder.playPauseHive.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (callback != null) {
callback.playPauseOnClick(position);
if (position == product.currentPosition) {
product.setPlayPauseId(R.drawable.ic_pause);
//set the image to pause icon
}else{
//set the image to play icon
product.setPlayPauseId(R.drawable.ic_play);
}
notifyDataSetChanged();
}
}
});
my Callback inside my Activity:
#Override
public void playPauseOnClick(int position) {
final Product product = songList.get(position);
if(product.currentPosition == position){
product.currentPosition = -1; //pause the currently playing item
}else{
product.currentPosition = position; //play the item
}
this.adapter.notifyDataSetChanged();
}
For my case I use a variable to store the current playing item position. Let say
int x = -1; //-1 can indicate nothing was currently playing
So in the playPauseOnClick() you can do something like this
#Override
public void playPauseOnClick(int position) {
if(x == position){
x = -1; //pause the currently playing item
}else{
x = position; //play the item
}
this.adapter.notifyDataSetChanged();
}
The reason i remove product.setPlayPauseId() is because you really don't need them. You just need to set the play or pause icon based on your x varible which I created earlier. Do Something like this in your getView()
Product product = songList.get(position);
if (position == x) {
//set the image to pause icon
}else{
//set the image to play icon
}
So once you call adapter.notifyDataSetChanged() your adapter will do all the work for you. Whenever the x variable having the value -1 none of the icon will be display the pause icon, this can also make sure only one position will showing the pause icon as well.
Hope it help you.
I have a List view of ringtones with a play image view in each row for each ringtone.
This is the view of it.
Now obviously when a user clicks on a play button it should switch to pause button.
Now I have a problem :
I click on a play button and it will turn into pause button, but in the meantime when I click on a play button from another row it takes two clicks until the second one turns into the pause button.
This is my code :
Adapter
Product product = (Product) mDataItems.get(position);
holder.playPause=(ImageView)v.findViewById(R.id.playPause);
holder.playPause.setImageResource(product.getPlayPauseId());
holder.playPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (callback != null) {
callback.playPauseOnClick(position);
}
}
});
Activity
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if (paused) {
product.setPlayPauseId(R.drawable.ic_pause);
paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
paused = true;
}
adapter.notifyDataSetChanged();
};
I am already guessing the problem is in my condition
if(paused){
}
I would be grateful if you can offer me a better logic
you are using one pause field for all the rows so the first tap changes its status to true the second tap on the other row changes it to false and the sets the drawable
product.setPlayPauseId(R.drawable.ic_pause)
on the first row and the third tap works correctly.
you can define pause field for every product and inside if clause check the products pause filed
if(product.pause){
...
product.paused = false;
}else{
product.paused = true;
}
Here's a slimier case in this what i did is i stream audio ringtones from url in a RecyclerView .. make a class variable as
private int oldPosition = -1; , with this variable you'll keep track of which row is currently playing audio in RecyclerView
#Override
public void onRecyclerViewChildItemClick(BaseRecyclerViewHolder holder, int resourceId) {
super.onRecyclerViewChildItemClick(holder, resourceId);
switch (resourceId) {
case R.id.media_play_container:
int position = holder.getAdapterPosition();
if (oldPosition != -1)
resetResources();
if (oldPosition == position) {
resetResources();
oldPosition = -1;
return;
}
updateViewAtPosition(false, false); // update old selected view
oldPosition = position; // update position
BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) getRecyclerView().getAdapter();
AudioItem item = (AudioItem) adapter.getItemAt(oldPosition);
String url = item.getAudioPreviewUrl();
// if url is valid show progress and play
if (AppUtils.ifNotNullEmpty(url)) {
updateViewAtPosition(true, false);
playRingtone(url);
}
break;
case R.id.iv_media_favorite:
break;
}
}
This method updates old row that is playing
updateViewAtPosition(false, false);
private void updateViewAtPosition(boolean isLoading, boolean isPlaying) {
RecyclerView recyclerView = getRecyclerView();
if (recyclerView == null || oldPosition == -1)
return;
BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) recyclerView.getAdapter();
if (adapter == null)
return;
AudioItem item = (AudioItem) adapter.getItemAt(oldPosition);
item.setPlaying(isPlaying);
item.setLoading(isLoading);
adapter.update(oldPosition, item);
adapter.notifyItemChanged(oldPosition);
}
// reset media player
resetResources();
private void resetResources() {
resetMediaPlayer();
resetViewResources();
}
private void resetMediaPlayer() {
try {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.reset();
}
} catch (Exception e) {
Logger.caughtException(e);
}
}
private void resetViewResources() {
if (oldPosition == -1)
return;
updateViewAtPosition(false, false);
}
I have made a chat app where I have used a RecyclerView. Messages can be either text messages or audio messages. Everything is working fine except when I change the timer text on the TextView (for audio player layout I have made) , of how long has the song been played. I do this in a Runnable. But when I scroll the RecyclerView, the timer TextView changes text at random position.
Here is how I am changing the TextView text:
public void updateTimer(final int position) {
View view = mRecyclerViewChat.getLayoutManager().findViewByPosition(position);
timer = (TextView) view.findViewById(R.id.timer);
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
Here position is the position value I am getting from the onBindViewHolder.
EDIT
Here is the onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (TextUtils.equals(mChats.get(position).senderUid,
FirebaseAuth.getInstance().getCurrentUser().getUid())) {
if (mChats.get(position).mediaUrlLocal == null) {
configureMyChatViewHolder((MyChatViewHolder) holder, position);
} else {
configureMyChatMediaViewHolder((MyChatMediaViewHolder) holder, position);
}
} else {
if (mChats.get(position).mediaUrlLocal == null) {
configureOtherChatViewHolder((OtherChatViewHolder) holder, position);
} else {
configureOtherChatMediaViewHolder((OtherChatMediaViewHolder) holder, position);
}
}
}
and here is the playMedia method which is called from configureMyChatMediaViewHolder method:
private void playMyMedia(final MyChatMediaViewHolder myChatViewHolder, final Chat chat, final int position) {
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
metaRetriever.setDataSource(chat.mediaUrlLocal);
String duration =
metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long dur = Long.parseLong(duration);
String seconds = String.valueOf((dur % 60000) / 1000);
String minutes = String.valueOf(dur / 60000);
String out = minutes + ":" + seconds;
myChatViewHolder.timer.setText(out);
if (chat.isPlay) {
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
} else {
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
}
myChatViewHolder.play.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
if (previousChat != position && previousChat != -1) {
previousChatObj = mChats.get(previousChat);
}
previousChat = position;
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
callback.onPlayClickListener(chat, previousChatObj, position);
}
});
myChatViewHolder.pause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
callback.onPauseClickListener(chat, position);
}
});
}
I have only one media player instance in the fragment from which the adapter for chat is set.
Recycler view is re-using the views that are not currently visible. So if your audio player starts updating one Text View , when you scroll that textview is the same reference but with different text ( song ), but your runnable instance is still active and doing its job to update the Text View with same reference.
You should create a custom Runnable class which
will hold a position value and will be only responsible for the
TextView it should update.
Other approach ( less efficient ) is to use ListView and not to re-use the cells to create new one for each item. This will cause performance issues
If you can send some code i would like to help you out.
*** EDITED ****
Here is some code part that can make things more clear
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.songDurationView.setTag(position);
//TODO: implement some more logic here and start the MusicSongRunnable
}
class MusicSongRunnable implements Runnable {
int positionOfSong;
TextView textView;
public MusicSongRunnable(int positionOfSong, TextView textView) {
this.positionOfSong = positionOfSong;
this.textView = textView;
}
#Override
public void run() {
if (player.isPlaying() && positionOfSong == textView.getTag()) {
//TODO: update the song;
}
}
It is not clear where you call updateTimer, but I assume that you call it from within callback.onPlayClickListener(..) and callback.onPauseClickListener(..).
First, do not pass it the position you've got as a parameter in click listeners, but as documentation states you should use ViewHolder.getAdapterPosition() method. Note, that when you use the value of the position you should also check that it is different from RecuclerView.NO_POSITION each time and only then use it. Make your position parameter not final in all methods. It will prevent you from making mistakes. Each time you want to use position in click listeners use myChatViewHolder.getAdapterPosition()
Second, since you cache position value in the Runnable anyway, this is probably not enough. So you should pass myChatViewHolder.timer as a parameter to updateTimer and get rid of first two lines. In the end you call updateTimer like this:
updateTimer(myChatViewHolder.timer);
and your 'updateTimer' now is:
public void updateTimer(final TextView timer) {
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
More from the documentation on this matter:
RecyclerView will not call onBindViewHolder() method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the position parameter while acquiring the related data item inside this method and should not keep a copy of it.
Hi i have recyclerView row item consists of a play Button(Which change to stop and Play as toggle) and a play seek bar and TextView.
The each row plays different audios .
When i click on play button of respective row i am able to stop the other row audio ..
Addressing to the problem ..
When i am playing a audio of a respective row, the button of earlier row has to stow a play symbol (in the sense it has to change the state ) please help me how to update the other rows of recyclerView when the other item is clicked
#Override
public void onBindViewHolder(final ViewHolder holder, final int i) {
if (holder.viewType == ROW_TYPE){
// holder.play_button.setImageResource(R.drawable.play_button);
if(i == selected_item){
}
else {
// holder.play_button.setImageResource();
}
JSONObject obj;
String mp3_url = null, caption = null, thumbnail_url = null;
try {
obj = voicesArray.getJSONObject(i);
mp3_url = obj.getString("mp3_url");
caption = obj.getString("caption");
thumbnail_url = obj.getString("thumbnail_url");
} catch (JSONException e) {
e.printStackTrace();
}
holder.list_text.setText(caption);
imageLoader.DisplayImage(thumbnail_url, holder.list_image);
final String finalMp3_url = mp3_url;
holder.play_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
selected_item = i;
int ii = holder.getOldPosition();
// holder.play_button.setImageResource();
// updateItem(ii);
if(player != null){
player.reset();
holder.play_button.setImageResource(R.drawable.play_button);
myAudioPlay(holder, finalMp3_url);
}
else {
myAudioPlay(holder,finalMp3_url);
/*if (player == null) {
player = new MediaPlayer();
playAudiFile(finalMp3_url);
holder.play_button.setImageResource(R.drawable.pause_button);
} else if (player.isPlaying()) {
holder.play_button.setImageResource(R.drawable.play_button);
player.stop();
Log.d("player", "" + player);
player.reset();
} else {
holder.play_button.setImageResource(R.drawable.pause_button);
playAudiFile(finalMp3_url);
}*/
}
}
});
}
}
Apparently the purpose of a recyclerView is to recycle the views to minimize memory usage required for rendering view so setting an image in the view is not an apt solution,Their are many solutions for your problem the One i would pick(Pseudo code) is given below
Step 1. Create a Boolean value in your model class
bool isPlay=true;
Step 2. When you are playing an audio loop through your Model for the recycler adapter and set the value for isPlay accourdingly
for(object obj in listOfModels)
if(obj.Id!=idOfthePlayingView)
obj.isPlay=false;
Then notify your adapter
yourRecyclerAdapterObject.notifyDatasetChanged()