I want to implement a long press of a CardView inside a RecyclerView
The layout:
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:scrollbars="vertical"
android:longClickable="true"
android:hapticFeedbackEnabled="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
I tried this:
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(Utils.context));
mRecyclerView.setOnLongClickListener(new AdapterView.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
Toast.makeText(Utils.context,"dsfd",Toast.LENGTH_LONG).show();
return true;
}
});
And I also tried to implement this event in the RecyclerView.ViewHolder class but nothing works. I have the long press effect but the event itself is not triggered. There is no onItemLongClickListener. I also tried this: RecyclerView onClick .
What am I missing?
You can create Interface where you should implement your onLongClicked(int position) method. And use it.
Follow this steps to create your onLongClickListener:
Create your Interface:
public interface IRecyclerViewClickListener {
void onLongClicked(int position);
}
Create listener object in your activity/fragment and pass this listener object to your adapter in the create adapter method. Example:
// ... some code ...
adapter = new MoviesListAdapter(getActivity(), list, listener);
rvDialogs.setAdapter(adapter);
// ... code ...
In your adapter in your owned ViewHolder constructor set long listener on itemView:
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView Title;
// ...
public MessageViewHolder(View itemView) {
super(itemView);
Title = (TextView) itemView.findViewById(R.id.tvDialogTitle);
// ...
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
listener.onLongClicked(getAdapterPosition());
return false;
}
});
}
}
After in your activity/fragment setup your IListener:
listener = new IRecyclerViewClickListener() {
#Override
public void onLongClicked(int position) {
Toast.makeText(getActivity(), String.valueOf(position), Toast.LENGTH_SHORT).show();
}
};
Hope it help you! Good luck and sorry for my bad english ;)
Related
I have referred few links regarding this topic but I still couldn't understand how to pass data from a RecyclerView to a Fragment[opening a Fragment on Cardview click]. Following is my RecyclerView class:
public class PhotoAdapter extends RecyclerView.Adapter<PhotoAdapter.PhotosHolder>
{
static class PhotosHolder extends RecyclerView.ViewHolder
{
CardView cv;
ImageView photo_img;
PhotosHolder(final View itemView)
{
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv_photo);
photo_img = (ImageView) itemView.findViewById(R.id.thum_photo);
}
}
private List<PhotoInitialise> photo;
private Activity mContext;
public PhotoAdapter(List<PhotoInitialise> photos, Context mContext)
{
this.photo=photos;
this.mContext= (Activity) mContext;
}
#Override
public PhotosHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.phototab_layout,parent,false);
return new PhotosHolder(layoutView);
}
#Override
public void onBindViewHolder(PhotosHolder holder, final int position)
{
holder.photo_count.setText(photo.get(position).gettotalImages());
holder.cv.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
// want to pass the value to a Fragment here and invoke the Fragment
}
});
}
#Override
public int getItemCount()
{
return photo.size();
}
}
This is the XML Layout of the Fragment that should be displayed:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/photo_detail_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/photodetail_description"
android:textSize="20sp"
android:paddingLeft="5dp"
android:layout_marginTop="5dp"
android:paddingRight="5dp">
</RelativeLayout>
I already have an idea of how to pass the value to fragment but I never could able to invoke the layout. Now what should I write in my onClick() to initiate the Fragment Layout ?
Implement interface to handle the click in the specified Fragment.
Sample code structure will be like below. You can customize and use it according to your need:
public InterfaceListItemClickListener{
void listItemClickAction(<parameters to pass>);
}
In your fragment class implement this interface:
public class SampleFragment extends BaseFrgment implements InterfaceListItemClickListener{
#Override
public void listItemClickAction(<parameters to pass>) {
//override the interface function
//handle your click action here
}
//pass the instance of your interface to your adapter like below line
YourAdapter yourAdapter = new YourAdapter(otherParameters, this);
}
In constructor of your Adapter access the interface:
public YourAdapter(otherParameters, InterfaceListItemClickListener clickListenerInferface ){
//other assignations
this.clickListenerInferface = clickListenerInferface;
}
and then onClick callback you can call your onclick function:
clickListenerInferface.listItemClickAction(<parameter to pass>);
I want to be able to add items to my ReclycerView dynamically.
When an item loads -> setText() -> I add another item on list.
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
This is what I have inside onBindViewHolder. When I am on the first case if(), and I click the button, the item is added to the list. On the second Case else(), I would like for the text to be set on this current item and than already add another one.
How can I achieve this?
Moreover, why add() works inside onClickListener but not outside of it?
The error I get is:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
Thanks! :)
The error itself is self explanatory ... It is dangerous to setOnClickListener in onBindViewHolder. This method is step of refresh each recycler item.
You should move method setOnClickListener to ViewHolder which is inner class on your adapter.
class MyViewHolder extends RecyclerView.ViewHolder
{
private final OnClickListener mOnClickListener = new MyOnClickListener();
Button mAnswer1Button, mAnswer2Button;
public MyViewHolder (View itemView) {
super(itemView);
mAnswer1Button = (Button) itemView.findViewById(R.id.item);
mAnswer2Button = (Button) itemView.findViewById(R.id.item);
mAnswer1Button.setOnClickListener(mOnClickListener);
}
#Override
public void onClick(final View view) {
//Now your Logic ....
}
}
One more thing you could do is set Create an interface OnItemClickListener
and declare onItemClick and then make the activity from where you are setting up the adapter for the particular recycler view implment OnItemClickListener this and over there you can dynamically add another item and setAdapter again or notifyDataSetChanged()
Your InterFace
public interface OnItemClickListener{
public void onItemClick(int position);
}
Your MainActivity
class MainActivity implements OnItemClickListener{
RecyclerView mRecyclerView ;
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.layout);
mRecyclerView = (RecyclerView ) findViewById(R.id.recyclerview);
mAdapter= new MyAdapter(ArrayList, getContext(), MainActivity.this);
}
#Overrride
public void onItemClick(int position)
{
//Now your Logic ....
}
}
Now you can call this method onItemClick from the Adpater clas by setting onClickListener mAnswer1Button in the ViewHolder class and calling this method with in the onClick
As commented, I think it should work like this:
private int position;
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
this.position = position;
}
#Override
public void onViewAttachedToWindow(ViewHolder holder) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
In onBindViewHolder(), you apparently cannot change the dataset - I guess as the framework is still busy displaying the previous dataset. But when saving the position in an instance variable, and updating the dataset in onViewAttachedToWindow(), the RecylerView should be ready for more data.
To be honest though, I wouldn't add all this logic to the ViewHolder, but pass an interface to it so the logic can be kept in a more central place, like a presenter.
here is my layout xml.If relativelayout click works that is also fine.Not getting any click events other than item clicks.
<RelativeLayout
android:id="#+id/layoutItems"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/viewDivider">
<android.support.v7.widget.RecyclerView
android:id="#+id/item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:padding="8dp"></android.support.v7.widget.RecyclerView>
</RelativeLayout>
There are some different ways to do it. The first way is to do like this:
RecyclerView recyclerView = findViewById(R.id.recycler);
recyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(context, recyclerView ,new RecyclerItemClickListener.OnItemClickListener() {
#Override public void onItemClick(View view, int position) {
// do whatever
}
#Override public void onLongItemClick(View view, int position) {
// do whatever
}
})
);
Or you can do it in a ViewHolder.
We will need a listener interface.
public interface OnItemClickListener {
public void onClick(View view, int position);
}
In your ViewHolder class in your RecyclerView adapter, implement View.OnClickListener, bind the listener to the view. In the onClick method, call the onClick method of the interface OnItemClickListener. This should be passed in from your RecycyclerView’s constructor. The actual implementation of the onclick event will be from an activity or fragment that contains this RecyclerView. The important line here is clickListener.onClick(view, getPosition()); where clickListener is a global variable in your RecyclerView class, again it should’ve passed in from your RecyclerView’s constructor.
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView cityName;
public ViewHolder(View view) {
super(view);
cityName = (TextView) view.findViewById(R.id.city_name);
itemView.setOnClickListener(this); // bind the listener
}
#Override
public void onClick(View view) {
clickListener.onClick(view, getPosition()); // call the onClick in the OnItemClickListener
}
}
The onClick implementation in the Activity class, the important line here is mAdapter.setClickListener(this); and the onClick method. The onClick method gets triggered from the ViewHolder’s onClick method in your RecyclerView class, which passes the view and position of the clicked item.
public class CityActivity extends Activity implements ItemClickListener {
private RecyclerView mRecyclerView;
private CityAdapter mAdapter;
private List<City> cities;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_city);
cities = CityManager.getInstance(this.getApplicationContext()).getCites();
mRecyclerView = (RecyclerView)findViewById(R.id.list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new CityAdapter(cities, R.layout.row_city, this);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setClickListener(this); // Bind the listener
}
#Override
public void onClick(View view, int position) {
// The onClick implementation of the RecyclerView item click
final City city = cities.get(position);
Intent i = new Intent(this, CityviewActivity.class);
i.putExtra("city", city.name);
i.putExtra("desc", city.description);
i.putExtra("image", city.imageName);
Log.i("hello", city.name);
startActivity(i);
}
}
So I am trying to make my cards clickable and as they are clicked it opens a new activity (not implemented in the code below though), here is my code:
For Interface
import android.view.View;
public interface ClickListener {
public void itemClicked(View view , int position);
}
For Adapter:
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.SensorViewHolder> {
private ClickListener clicklistener = null;
List<SensorData> sensors;
RVAdapter(List<SensorData> sensors) {
this.sensors = sensors;
}
public class SensorViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView sensorName;
TextView sensorDesc;
ImageView sensorPhoto;
private LinearLayout main;
SensorViewHolder(final View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv);
sensorName = (TextView) itemView.findViewById(R.id.sensor_name);
sensorDesc = (TextView) itemView.findViewById(R.id.sensor_desc);
sensorPhoto = (ImageView) itemView.findViewById(R.id.sensor_photo);
main = (LinearLayout) itemView.findViewById(R.id.main);
main.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(itemView.getContext(), "Position:" + Integer.toString(getPosition()), Toast.LENGTH_SHORT).show();
if(clicklistener != null){
clicklistener.itemClicked(v, getAdapterPosition());
}
}
});
}
}
public void setClickListener(ClickListener clickListener){
this.clicklistener = clickListener;
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public SensorViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
SensorViewHolder pvh = new SensorViewHolder(v);
return pvh;
}
#Override
public void onBindViewHolder(SensorViewHolder SensorViewHolder, int i) {
SensorViewHolder.sensorName.setText(sensors.get(i).name);
SensorViewHolder.sensorDesc.setText(sensors.get(i).descriptor);
SensorViewHolder.sensorPhoto.setImageResource(sensors.get(i).iconID);
}
#Override
public int getItemCount() {
return sensors.size();
}
}
and here is the code for MainActivity:
public class MainActivity extends AppCompatActivity {
private List<SensorData> sensorData;
private RecyclerView rv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Find the recycler view for the <code>
rv = (RecyclerView) findViewById(R.id.rv);
//Initialising a linear layout manager
final LinearLayoutManager llm = new LinearLayoutManager(this);
rv.setLayoutManager(llm);
rv.setHasFixedSize(true);
initializeData();
initializeAdapter();
rv.setClickListener(this);
}
private void initializeData() {
sensorData = new ArrayList<>();
sensorData.add(new SensorData("Accelerometer", "Measures the acceleration of a moving or vibrating body.", R.mipmap.ic_accl));
sensorData.add(new SensorData("GPS Sensor", "Provides real-time user location and time information.", R.mipmap.ic_gps));
sensorData.add(new SensorData("Proximity Sensor", "Provides ambient light and proximity sensing.", R.mipmap.ic_als));
}
private void initializeAdapter() {
RVAdapter adapter = new RVAdapter(sensorData);
rv.setAdapter(adapter);
}
}
And yet it seems I cannot access setClickListener. I must mention I am quite a beginner in this.
Where have I mistaken? Is this the correct way to implement clicks on a card or I am missing something?
Also here is the MainActivity XML if needed.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:id="#+id/main">
<android.support.v7.widget.RecyclerView
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/rv">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
and items.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cv"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sensor_photo"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sensor_name"
android:layout_toRightOf="#+id/sensor_photo"
android:layout_alignParentTop="true"
android:textSize="30sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sensor_desc"
android:layout_toRightOf="#+id/sensor_photo"
android:layout_below="#+id/sensor_name" />
</RelativeLayout>
</android.support.v7.widget.CardView>
EDIT: Just to avoid confusion, **I want the use to be able to click the cards and go to a new activity. ** How can I implement that in the simplest possible way? I am really confused on how to do it in the RecyclerView.
Thanks a lot and regards.
Since there is a difference between the old ListView and the new RecyclerView component, I have a reusable piece of code that I use to handle click events (both regular clicks and LongClicks):
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
#SuppressWarnings("CanBeFinal")
private GestureDetector mGestureDetector;
#SuppressWarnings("CanBeFinal")
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener)
{
mListener = listener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener()
{
#Override
public boolean onSingleTapUp(MotionEvent e)
{
return true;
}
#Override
public void onLongPress(MotionEvent e)
{
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView != null && mListener != null)
{
mListener.onItemLongClick(childView, recyclerView.getChildAdapterPosition(childView));
}
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e)
{
View childView = view.findChildViewUnder(e.getX(), e.getY());
if(childView != null && mListener != null && mGestureDetector.onTouchEvent(e))
{
mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent){}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
Then simply do this in your activity or fragment where you want to handle click events:
mRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, mRecyclerView, new RecyclerItemClickListener
.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
//start new activity here
}
#Override
public void onItemLongClick(View view, int position) {
}
}));
That should be the easiest way to do it!
I hope this helps you! Good luck and happy coding!
Basically you need to set the listener in the Viewholder
public class SensorViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView sensorName;
TextView sensorDesc;
ImageView sensorPhoto;
private LinearLayout main;
SensorViewHolder(final View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv);
//Add the following line below
cv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//The intent firing goes here
}
});
sensorName = (TextView) itemView.findViewById(R.id.sensor_name);
sensorDesc = (TextView) itemView.findViewById(R.id.sensor_desc);
sensorPhoto = (ImageView) itemView.findViewById(R.id.sensor_photo);
main = (LinearLayout) itemView.findViewById(R.id.main);
main.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(itemView.getContext(), "Position:" + Integer.toString(getPosition()), Toast.LENGTH_SHORT).show();
if(clicklistener != null){
clicklistener.itemClicked(v, getAdapterPosition());
}
}
});
}
}
Hope it helps.
To fix the compiler error, the correct way to set a OnClickListener is with
rv.setOnClickListener()
Note that the error is because you are missing the word On. Generally, you should use autocomplete in Android Studio to help avoid these types of issues. If autocomplete doesn't pop up automatically, you can push Ctrl-Space to get it.
As a side note, you can build your project by clicking Build -> Make Project in the main menu. This will give a window with the error messages where you can copy and paste more easily than the usual error balloons.
first of all set tag for your card in onBindViewHolder:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cv.setTag(position);
}
then add on click listener for your card in your view holder in adapter:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CardLayout cv;
public ViewHolder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(Rid.cv);
cv.setOnClickListener(this);
}
#Override
public void onClick(View v) {
//for find item that hold in list
int postition = (Integer) v.getTag();
...
}
In your findViewById you are casting the view to a RecyclerView and therefore you are referring the standard RecyclerView implementation of the Android framework. So this would never work.
The next thing is you are trying to set a listener to the RecyclerView but define the setListener method inside your adapter.
So what you should do is to either get a reference to your adapter via:
((RvAdapter) rv.getAdapter()).setClickListener();
after setting it or even better set the listener before you call
rv.setAdapter()
via
RvAdapter adapter = new RvAdapter();
adapter.setClickListener(listener)
I've implemented an nested ReyclerView (horizontal into vertical) and I wan't to add an click listener for the entire row which is wrapped in a CardView element.
The issue I'm having is that the inside RecyclerView captures all the touch events and the root CardView does not respond to the onClick event.
I've also tried to make the CardView intercept the touch events, but with this approach the ripple effect (in fact any feedback) wasn't working.
Can someone recommend an solution for how to implement a click listener on a row while having nested RecyclerView?
Thank you.
--LE--
This is the current implementation:
fragment layout
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/list"
android:name=".NestedRecyclerViewsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
tools:context=".NestedRecyclerViewsFragment"
tools:listitem="#layout/fragment_nested_recyclerview_item"
/>
Fragment onCreateView() implementation
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_nested_recyclerview_list, container, false);
// Set the adapter
if (view instanceof RecyclerView) {
Context context = view.getContext();
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
recyclerView.setHasFixedSize(true); // Improves performance - as we know the size doesn't change
//Initialize and set the adapter
mAdapter = new RootAdapter(context, mListener);
recyclerView.setAdapter(mAdapter);
final GestureDetector mGestureDetector =
new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
//TODO: intercept simple gestures like onClick and/or onLongClick
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && mGestureDetector.onTouchEvent(e)) {
//TODO: handle the intercept??
child.callOnClick();
return true;
}
return super.onInterceptTouchEvent(rv, e);
}
});
recyclerView.setNestedScrollingEnabled(true);
}
return view;
}
**Root Adapter layout **
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="#dimen/row_height"
android:layout_gravity="center_horizontal"
android:layout_margin="#dimen/none"
android:padding="#dimen/none"
app:cardCornerRadius="#dimen/none"
tools:context=".NestedRecyclerViewActivity"
<!-- Simple selector for API < 21 and ripple effect for APi >= 21 -->
android:foreground="#drawable/selector_default"
android:clickable="true"
android:focusable="true"
>
<!-- Horizontal image gallery inside row item -->
<android.support.v7.widget.RecyclerView
android:id="#+id/child_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
...
</android.support.v7.widget.CardView>
Root Adapter onBindView() implementation:
...
holder.childRecyclerView.setLayoutManager(new LinearLayoutManager(holder.mView.getContext(),
LinearLayoutManager.HORIZONTAL, false));
holder.childRecyclerView.setHasFixedSize(true); // We know the image don't change size
holder.childRecyclerView.setAdapter(new ChildAdapter(items));
...
Root Adapter ViewHolder implementation:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ItemClickListener mClickListener;
/**
* The whole view - useful if you need to place some touch listener on the entire row.
*/
public final View mView;
/**
* The model associated with this view. - will be updated on bind method
*/
public Object mItem;
#Bind(R.id.child_recycler_view)
RecyclerView childRecyclerView;
...
public ViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
mView = view;
mView.setOnClickListener(this);
// Set the click listener bound to the fragment or activity
mClickListener = RootAdapter.this;
}
#Override
public void onClick(View v) {
if (null != mClickListener) {
mClickListener.onClick(v, getAdapterPosition());
}
}
}
The inner RecyclerView (the child) is a very simple and standar implementation without any listener set to it.
Although this solution works and I get the click event and the inner RecyclerView scroll works, I encountered the issue where the feedback of the click is not shown.
LE: Solution
My requirements changed a little since I posted this Issue, having to replace the inner RecyclerView with an PagerAdapter, but the solution I implemented should work with the nested RecyclerView also.
Basically I use an custom ItemClickSupport class which will pass the click events back to the parent as soon as they are intercepted:
/**
* Utility class which adds the ability to add Click Support for RecyclerViews without the need to implement click
* listeners into the adapter or in the ViewHolder's implementation.
* <p>
* Use it by simply binding an click listener to the desired RecyclerView.
* <pre><code>
* ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
* {#literal#}Override
* public void onItemClicked(RecyclerView recyclerView, int position, View v) {
* // Handle the clicked item
* }
* });
* </code></pre>
* </p>
* Based on <a href="http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/">Handle-Android-RecyclerView
* -Clicks</a>, <br/><b>Hugo Visser</b>. Which is very similar with the implementation from <a
* href="https://github.com/lucasr/twoway-view">TwoWay-View</a>.
* <p/>
* Created by ionut on 22.03.2016.
*/
public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener =
new RecyclerView.OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
#Override
public void onChildViewDetachedFromWindow(View view) {
}
};
private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static ItemClickSupport addTo(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}
public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
void onItemClicked(int position);
}
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
public static class SimpleOnItemClickListener implements OnItemClickListener {
#Override
public void onItemClicked(int position) {
}
#Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
}
}
}
When creating the root adapter, I set the item click listener for it, like this:
ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(this);
And I also send this listener for the inner adapter through an setter method, like this:
rootAdapter.setOnItemClickListener(this);
rootAdapter is the root adapter, and this is the current fragment I'm in, which implements the ItemClickSupportListener.
In the root adapter, when binding the items and creating the inner adapter, I pass on the item click support listener to the inner adapter, like this:
innerAdapter.setOnItemClickListener(new ItemClickSupport.SimpleOnItemClickListener() {
#Override
public void onItemClicked(int position) {
// Here we pass the click to the parent provided click listener.
// We modify the position with the one of the ViewHolder so that we don't get the
// position of the horizontal RecyclerView adapter - we are interested on the
// vertical item actually.
if (null != mItemClickListener) {
mItemClickListener.onItemClicked(holder.getAdapterPosition());
}
}
});
mItemClickListener is actually the listener set by the fragment through the setter method, described above.
In the inner adapter, when creating the views, I set an click listener on the root of the layout which I inflated and pass that event back to my custom click listener:
// Detect the click events and pass them to any listeners
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != mOnItemClickListener) {
mOnItemClickListener.onItemClicked(position);
}
}
});
mOnItemClickListener is actually the item click support which was passed by adapter described above.
Another important thing is to use an FrameLayout for the content of the adapter item views, like this:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:addStatesFromChildren="true"
android:foreground="#drawable/selector_default"
>
The important thing here is the foreground which will use an selector and the flag android:addStatesFromChildren. This is set for the root adapter items.
The inner adapter items should also use an FrameLayout as content view so that we can also set it's selector using foreground attribute:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="#drawable/selector_default"
>
This is needed, because when the inner adapter item will capture the click event, the selector to be activated, otherwise the parent should react to the click event and activate it's selector.
try setting click listener to the view in adapter
public class NormalItem extends RecyclerView.ViewHolder {
CardView cardView;
TextView textView;
public NormalItem(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
cardView = (CardView) itemView.findViewById(R.id.cardview);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// to perform click on entire row do like this.
}
});
textView.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
// perform clicks to views inside the items.
}
}