Search code examples
javaandroidandroid-recyclerviewonclicklistenerandroid-imagebutton

Android RecyclerView Adapter ImageButton Problem


I have my adapters with image buttons and so. When pressing the sixth one it does as suppose to do. This is the last time it was updated correctly.

enter image description here

But then it keeps moving up the list doing the same thing. Now this

enter image description here

@Override
public void onClick(View view) {
    TimeCard card = MyAdapter.cards.get(position);
    switch (view.getId()) {
        case R.id.playButton:
            startTimer(card);
            ///new Logger(TimeCardButton.class).debug("Play button was pressed");
            break;
        case R.id.editButton:
            Intent intent = new Intent(context, TimeCardAdd.class);
            intent.putExtra("cardPosition", position);
            context.startActivity(intent);
            //TODO: Finish the editing so we can modify the timer card
            Toast.makeText(context, "Edit button has been pressed.", Toast.LENGTH_SHORT).show();
            break;

        case R.id.stopButton:
            stopTimer(card);
            break;

        case R.id.pauseButton:
            pauseTimer(card);
            break;
    }
}

Is called only once. Which is correct. But this is called every second from the UI update call

private void sendPlayTimeButtons() {
    cardButtons.get(TimeCardButtonId.PLAY_BUTTON.getId()).setVisibility(View.INVISIBLE);
    cardButtons.get(TimeCardButtonId.EDIT_BUTTON.getId()).setVisibility(View.INVISIBLE);
    cardButtons.get(TimeCardButtonId.PAUSE_BUTTON.getId()).setVisibility(View.VISIBLE);
    cardButtons.get(TimeCardButtonId.STOP_BUTTON.getId()).setVisibility(View.VISIBLE);
   // logger.debug("Sending Play Buttons");
}

Here's the code for my BindViewHolder on the Adapter

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    //TODO: add everything back
    holder.playButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
    holder.editButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
    holder.pauseButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
    holder.stopButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
}

And last but finally the code for my ViewHolder inside the adapter

public class MyViewHolder extends RecyclerView.ViewHolder {

    TextView timerTitle;
    public TextView timeRemaining;

    ImageButton playButton;
    ImageButton editButton;
    ImageButton pauseButton;
    ImageButton stopButton;

    LinkedList<ImageButton> buttons = new LinkedList<>();

    public MyViewHolder(final View view) {
        super(view);

        timerTitle = view.findViewById(R.id.titleCardName);
        timeRemaining = view.findViewById(R.id.timeLeftTextCard);

        playButton = view.findViewById(R.id.playButton);
        editButton = view.findViewById(R.id.editButton);
        pauseButton = view.findViewById(R.id.pauseButton);
        stopButton = view.findViewById(R.id.stopButton);

        buttons.add(playButton);
        buttons.add(editButton);
        buttons.add(pauseButton);
        buttons.add(stopButton);
    }
}

Here's the startTimer function which I have tested and it's called only once.

private void startTimer(TimeCard card) {
    new Logger(TimeCardButton.class).debug("Play button was pressed");
    if (!card.isTimeStarted()) {
        card.setTimeStarted(true);
        sendPlayTimeButtons();
        logger.info("Starting Timer!");
    } else if(card.isTimerPaused() && card.isTimeStarted()) {
        TimerTask.notifyUpdate();
        card.setTimerPaused(false);
        sendPlayTimeButtons();
        logger.info("Resuming from being paused!");
    }
}

By all of them one at a time by my task system. My Task system only sends updates to the recycler from the activity handling the UI calls...

Again On those images, the buttons will move up the list every second having no reason. I tried replacing the button ids with tags. But that still failed.


Solution

  • You need to have a separate array in your adapter having the track of the list playing/paused/stopped. Let us consider the following.

    0 -> Stopped
    1 -> Playing
    2 -> Paused
    

    Now take an array in your adapter like the following.

    // This initializes the array with the number of elements of your list
    // and all initialized by 0 to indicate primarily all tracks were not playing. 
    int[] trackPlayer = new int[cards.size]; 
    

    Now when you are clicking the buttons to do some action you need to update the array with the action as well.

    @Override
    public void onClick(View view) {
        TimeCard card = MyAdapter.cards.get(position);
        switch (view.getId()) {
            case R.id.playButton:
                startTimer(card);
                trackPlayer[position] = 1;  // Playing
                break;
            case R.id.editButton:
                Intent intent = new Intent(context, TimeCardAdd.class);
                intent.putExtra("cardPosition", position);
                context.startActivity(intent);
                //TODO: Finish the editing so we can modify the timer card
                Toast.makeText(context, "Edit button has been pressed.", Toast.LENGTH_SHORT).show();
                break;
    
            case R.id.stopButton:
                stopTimer(card);
                trackPlayer[position] = 0;  // Stopped
                break;
    
            case R.id.pauseButton:
                pauseTimer(card);
                trackPlayer[position] = 2;  // Paused
                break;
        }
    }
    

    Now inside your onBindViewHolder, you need to change the button's visibility based on the trackPlayer values.

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.playButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
        holder.editButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
        holder.pauseButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
        holder.stopButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
    
        // Set the buttons visibility changes here. 
        if(playTrack[position] == 1) { 
            // Item in this position is being played
            playButton.setVisibility(View.INVISIBLE);
            editButton.setVisibility(View.INVISIBLE);
            pauseButton.setVisibility(View.VISIBLE);
            stopButton.setVisibility(View.VISIBLE);
    
        } else if(playTrack[position] == 0) { 
            // Item in this position is not being played/stopped
            playButton.setVisibility(View.VISIBLE);
            editButton.setVisibility(View.VISIBLE);
            pauseButton.setVisibility(View.INVISIBLE);
            stopButton.setVisibility(View.INVISIBLE);
        } else if(playTrack[position] == 2) { 
            // Item in this position is paused
            playButton.setVisibility(View.VISIBLE);
            editButton.setVisibility(View.INVISIBLE);
            pauseButton.setVisibility(View.INVISIBLE);
            stopButton.setVisibility(View.VISIBLE);
        }
    }
    

    And remove the sendPlayTimeButtons function call from your startTimer function. I think you might consider removing the sendPlayTimeButtons function as well.

    private void startTimer(TimeCard card) {
        new Logger(TimeCardButton.class).debug("Play button was pressed");
        if (!card.isTimeStarted()) {
            card.setTimeStarted(true);
            notifyDataSetChanged(); // Call notifyDataSetChanged instead here.
        } else if(card.isTimerPaused() && card.isTimeStarted()) {
            TimerTask.notifyUpdate();
            card.setTimerPaused(false);
            notifyDataSetChanged(); // Call notifyDataSetChanged instead here.
        }
    }
    

    Hope you get the idea.