zeeks
zeeks

Reputation: 794

android - OnClickListener not working for first click in recyclerview

I am working in a chat application for Android and I am using RecyclerView for listing the messages.

I have written the adapter, but I am having a problem with detecting when an element(TextView in this case) inside the layout is clicked.

This is my adapter:

public class ChatRoomThreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private class ViewHolder extends RecyclerView.ViewHolder {
        TextView message, timestamp;

        private ViewHolder(View view) {
            super(view);

            message = (TextView) itemView.findViewById(R.id.message); 
            timestamp = (TextView) itemView.findViewById(R.id.timestamp); 
        }

    }

    public ChatRoomThreadAdapter(Context mContext, ArrayList<Message> messageArrayList, String userId) {
        this.mContext = mContext;
        this.messageArrayList = messageArrayList;
        this.userId = userId;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView;
        itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat, parent, false);

        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {

        ((ViewHolder) holder).message.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (((ViewHolder) holder).timestamp.getVisibility() == View.GONE) {
                    ((ViewHolder) holder).timestamp.setVisibility(View.VISIBLE);
                } else {
                    ((ViewHolder) holder).timestamp.setVisibility(View.GONE);
                }

            }
        });

    }

    @Override
    public int getItemCount() {
        return messageArrayList.size();
    }
}

The current onClick works but I have to click twice on the message in order for the onClick to trigger. I have been searching and trying endless solutions for 3 days in order to fix this, but none of the solutions on the internet have worked so far.

Upvotes: 17

Views: 7237

Answers (5)

iloo
iloo

Reputation: 986

A tip for those trying to debug this kind of scrolling state problem in recycler view. Create your own MyRecyclerView extending RecyclerView, and override fun onInterceptTouchEvent there just to add logging:

override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
    Log.d(TAG, "MyRecyclerView intercepted a touch event: ${event!!.action}")
    Log.d(TAG, "MyRecyclerView is in state: ${scrollState}")
    return super.onInterceptTouchEvent(event)
}

It helped me to see that my recycler view was stuck in SCROLL_STATE_DRAGGING.

I believe, for child views to receive clicks coming from recycler view, the recycler view has to be in SCROLL_STATE_IDLE state.

So my solution to the problem was based on what @Rocky suggested,

override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {

    val eventConsumed = super.onInterceptTouchEvent(event)

    /*
     * Fix the following problem.
     *
     * When the recycler view received a click, it was still in the DRAGGING mode.
     * It consumed the click, instead of passing it down to the child view.
     * The user had to click a child view again, to be able to interact with it.
     */
    when (event!!.actionMasked) {
        MotionEvent.ACTION_DOWN -> {
            if (scrollState == SCROLL_STATE_DRAGGING) {
                stopScroll()
                return false
            }
        }
    }

    return eventConsumed
}

Upvotes: 1

Terranology
Terranology

Reputation: 633

For me focusableInTouchMode & focusable doesn't work.

Add this line to RecyclerView :

android:nestedScrollingEnabled="false"

Enjoy¡

Upvotes: 2

Badhrinath Canessane
Badhrinath Canessane

Reputation: 3518

The following class will fix this issue :

class WrapperRecyclerView(context: Context, attributeSet: AttributeSet?, defStyle: Int) :
    RecyclerView(context, attributeSet, defStyle) {

  constructor(context: Context) : this(context, null, 0)

  constructor(context: Context, attributeSet: AttributeSet) : this(context, attributeSet, 0)

  override fun onScrollStateChanged(state: Int) {
    super.onScrollStateChanged(state)
    if (state == RecyclerView.SCROLL_STATE_SETTLING) {
      this.stopScroll();
    }
  }
}

Upvotes: 1

Rocky
Rocky

Reputation: 547

After scroll, recycler view items are not clickable and the issue is still open https://issuetracker.google.com/issues/66996774

Found a way to force click if the scroll state is still SCROLL_STATE_SETTLING

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.recyclerview.widget.RecyclerView

class WrapperRecyclerView(context: Context, attributeSet: AttributeSet?, defStyle: Int) :
  RecyclerView(context, attributeSet, defStyle) {

  constructor(context: Context) : this(context, null, 0)

  constructor(context: Context, attributeSet: AttributeSet) : this(context, attributeSet, 0)

  override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
    val eventConsumed = super.onInterceptTouchEvent(event)

    when (event.actionMasked) {
      MotionEvent.ACTION_DOWN -> if (scrollState == SCROLL_STATE_SETTLING) {
        parent.requestDisallowInterceptTouchEvent(false)
        // only if it touched the top or the bottom.
        if (!canScrollVertically(-1) || !canScrollVertically(1)) {
          // stop scroll to enable child view to get the touch event
          stopScroll()
          // do not consume the event
          return false
        }
      }
    }

    return eventConsumed
  }
}

Upvotes: 3

king_abu1918
king_abu1918

Reputation: 284

Make sure you have both focusableInTouchMode & focusable disabled on the button. The first click will get the focus and the second click executes the onClickListener.

.

Upvotes: 20

Related Questions