Reputation: 794
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
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
Reputation: 633
For me focusableInTouchMode & focusable doesn't work.
Add this line to RecyclerView :
android:nestedScrollingEnabled="false"
Enjoy¡
Upvotes: 2
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
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
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