I have RecyclerView in my Main Activity. I want to change different fragments after clicking on different recyclerview Items. I try this by using position from onbindviewholder() but i am not able to change the fragment when i click on recyclerview items. I am not getting any error but at the same time nothing is happening onclick of recyclerview. Please check my code and tell me where i am wrong or which line of code i am missing.
RecyclerAdapter Code:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
ArrayList<NewModel> newModels;
Context context;
public RecyclerAdapter(ArrayList<NewModel>newModels, Context context){
this.newModels = newModels;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recy_layout, parent,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
holder.imageView.setImageResource(newModels.get(position).getImg());
holder.textView.setText(newModels.get(position).getText());
holder.imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position ==1){
((FragmentActivity)context).getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, new FirstFragment());
}
else if(position ==2){
((FragmentActivity)context).getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, new SecondFragment());
}
}
});
}
#Override
public int getItemCount() {
return newModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public ViewHolder(#NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.cirlceimg);
textView = itemView.findViewById(R.id.text1);
}
}
}
you forgot to call commit()
do this:
(((FragmentActivity)context).getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, new SecondFragment()).commit();
Fragment is not rendered until you call commit() on the transaction , because transaction use builder pattern and in builder pattern there is a method to say that i don't want to do anything else just create the object, normally these method are name create() , build(), and commit() in this case
You can handle the onClickListener from your activity using callback implementation with an interface.
Interface:
public interface OnItemClickListener {
void onItemClick(int position);
}
Add the listener to your adapter's constructor
ArrayList<NewModel> newModels;
Context context;
private final OnItemClickListener listener;
public RecyclerAdapter(ArrayList<NewModel>newModels, Context context, OnItemClickListener listener){
this.newModels = newModels;
this.context = context;
this.listener = listener;
}
In your onBindViewHolder set the clicklistener
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
holder.imageView.setImageResource(newModels.get(position).getImg());
holder.textView.setText(newModels.get(position).getText());
holder.imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onItemClick(position);
}
});
}
In your activity:
RecyclerAdapter rvAdapter = new RecyclerAdapter(newModels, this, new RecyclerAdapter.OnItemClickListener() {
#Override public void onItemClick(int item) {
if(position ==1){
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout, new FirstFragment()).commit();;}
else if(position ==2){
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout, new SecondFragment()).commit();;
}
}});
recycler.setAdapter(rvAdapter);
Inside onBindViewHolder
holder.imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position ==1){
((FragmentActivity)context).getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, new FirstFragment()).addToBackStack(null).commit();
}
else if(position ==2){
((FragmentActivity)context).getSupportFragmentManager().beginTransaction()
.replace(R.id.framelayout, new SecondFragment()).addToBackStack(null).commit();
}
}
});
}
Hope this helps you.
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 3 years ago.
I, have implemented image slider using view-pager in my project. Now I want when I click on particular image of the slider. It will open a new Detail Fragment. Where according to that Image Slider information is available. So I tried to implement interface by getting image position using setOnClickListner. But now the problem is I am getting exception when I click on image. and my app gets crashed. Below is my code...
Slider Image Adapter:
public class SliderImageAdapter extends SliderViewAdapter<SliderImageAdapter.SliderAdapterVH>{
public List<Banner> bannerList;
public Context context;
private OnItemClicked onClick;
public interface OnItemClicked {
void onItemClick(int position);
}
public SliderImageAdapter(Context context, List<Banner> bannerList) {
this.bannerList = bannerList;
this.context = context;
}
#Override
public SliderAdapterVH onCreateViewHolder(ViewGroup parent) {
View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_slider_myshop, parent, false);
return new SliderAdapterVH(inflate);
}
#Override
public void onBindViewHolder(final SliderAdapterVH viewHolder, final int position) {
Glide.with(viewHolder.itemView)
.load(bannerList.get(position).getmSliderImage())
.fitCenter()
.into(viewHolder.imageViewBackground);
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onClick.onItemClick(position);
}
});
}
#Override
public int getCount() {
return bannerList.size();
}
public int getItemPosition (Object object) {
return POSITION_NONE;
}
public void setOnClick(OnItemClicked onClick) {
this.onClick = onClick;
}
class SliderAdapterVH extends SliderViewAdapter.ViewHolder {
View itemView;
ImageView imageViewBackground;
ImageView imageGifContainer;
public SliderAdapterVH(View itemView) {
super(itemView);
imageViewBackground = itemView.findViewById(R.id.iv_auto_image_slider);
imageGifContainer = itemView.findViewById(R.id.iv_gif_container);
this.itemView = itemView;
}
}
}
Here I am getting the exception in Image Slider Adapter: java.lang.NullPointerException: Attempt to invoke interface method SliderImageAdapter$OnItemClicked.onItemClick(int)' on a null object reference.
#Override
public void onBindViewHolder(final SliderAdapterVH viewHolder, final int position) {
Glide.with(viewHolder.itemView)
.load(bannerList.get(position).getmSliderImage())
.fitCenter()
.into(viewHolder.imageViewBackground);
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onClick.onItemClick(position);
}
});
}
Home Fragment Code:
public class HomeFragment extends Fragment implements SliderImageAdapter.OnItemClicked {
private SliderImageAdapter sliderImageAdapter;
private List<Banner> bannerList;
bannerList = new ArrayList<>();
sliderImageAdapter = new SliderImageAdapter(getActivity(), bannerList);
sliderMyshop = view.findViewById(R.id.imageSlider);
sliderMyshop.setSliderAdapter(sliderImageAdapter);
sliderMyshop.setIndicatorAnimation(IndicatorAnimations.WORM); //set indicator animation by using SliderLayout.IndicatorAnimations. :WORM or THIN_WORM or COLOR or DROP or FILL or NONE or SCALE or SCALE_DOWN or SLIDE and SWAP!!
sliderMyshop.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION);
sliderMyshop.setIndicatorSelectedColor(Color.WHITE);
sliderMyshop.setIndicatorUnselectedColor(Color.GRAY);
sliderMyshop.startAutoCycle();
mFirestore.collection("Banner").orderBy("mSliderImage", Query.Direction.DESCENDING).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#javax.annotation.Nullable QuerySnapshot documentSnapshots, #javax.annotation.Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.d(TAG, "Error : " + e.getMessage());
}
assert documentSnapshots != null;
for (DocumentChange doc : documentSnapshots.getDocumentChanges()) {
if (doc.getType() == DocumentChange.Type.ADDED) {
String doc_id = doc.getDocument().getId();
Banner banner = doc.getDocument().toObject(Banner.class).withDocId(doc_id);
bannerList.add(doc.getNewIndex(), banner);
sliderImageAdapter.notifyDataSetChanged();
} else if (doc.getType() == DocumentChange.Type.MODIFIED) {
String docID = doc.getDocument().getId();
Banner changedModel = doc.getDocument().toObject(Banner.class).withDocId(docID);
if (doc.getOldIndex() == doc.getNewIndex()) {
// Item changed but remained in same position
bannerList.set(doc.getOldIndex(), changedModel);
} else {
// Item changed and changed position
bannerList.remove(doc.getOldIndex());
bannerList.add(doc.getNewIndex(), changedModel);
}
} else if (doc.getType() == DocumentChange.Type.REMOVED) {
// remove
bannerList.remove(doc.getOldIndex());
}
sliderImageAdapter.notifyDataSetChanged();
}
}
});
#Override
public void onItemClick(int position) {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container_dashboard, SliderClickFragment.newInstance(bannerList.get(position).getmSliderImage()));
ft.addToBackStack(null); // this line responsible for adding fragment in backstack.
ft.commit();
}
Detail Fragment Code:
public abstract class SliderClickFragment extends Fragment {
public static Fragment newInstance(String getmSliderImage) {
Bundle args = new Bundle();
HomeFragment fragment = new HomeFragment();
fragment.setArguments(args);
return fragment;
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable final Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.slider_click_listner, container, false);
return view;
}
}
So the issue would probably be that you are calling the OnItemClicked.onClick without an instance of OnItemClicked.
Just a quick look through
private OnItemClicked onClick;
public interface OnItemClicked {
void onItemClick(int position);
}
public SliderImageAdapter(Context context, List<Banner> bannerList) {
this.bannerList = bannerList;
this.context = context;
}
And then you use it here
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onClick.onItemClick(position);
}
});
You are using trying to call the OnClick method on a null instance of OnItemClicked.
One way to do it would be to add OnItemClicked onclick to the contructor as a parameter and then have what ever fragment implement the OnItemClicked interface.
Something like this.
private OnItemClicked onClick;
public interface OnItemClicked {
void onItemClick(int position);
}
public SliderImageAdapter(Context context, List<Banner> bannerList,
OnItemClicked onClick) {
this.bannerList = bannerList;
this.onClick = onClick;
this.context = context;
}
Then at the HomeFragment
public class HomeFragment extends Fragment implements
SliderImageAdapter.OnItemClicked{
private SliderImageAdapter sliderImageAdapter
= new SliderImageAdapter(getActivity(), bannerList, this);
}
I am trying to run some code I got from some online tutorial of writing a fragment with a recyclerview in it but I am experiencing some difficulty in opening an activity from the onclick event. My adapter is below
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.MyViewHolder> {
private final ArrayList<ItemModel> mArrayList;
private Context mcontext;
ItemAdapter(ArrayList<ItemModel> mArrayList) {
this.mArrayList = mArrayList;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
mcontext = parent.getContext();
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listing_item, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
//Glide.with(mcontext).load(mArrayList.get(position).getImage()).into(holder.item_image);
holder.item_name.setText(mArrayList.get(position).getTitle());
holder.item_description.setText(mArrayList.get(position).getDescription());
holder.item_tags.setText(mArrayList.get(position).getTags());
Log.d("MyAdapter", "position: " + position);
}
#Override
public int getItemCount() {
return mArrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private final ImageView item_image;
private final TextView item_name;
private final TextView item_description;
private final TextView item_tags;
private final LinearLayout cardViewLayout;
MyViewHolder(View view) {
super(view);
item_image = view.findViewById(R.id.item_image);
item_name = view.findViewById(R.id.item_name);
item_description = view.findViewById(R.id.item_description);
item_tags = view.findViewById(R.id.item_tags);
cardViewLayout = view.findViewById(R.id.cardViewLayout);
cardViewLayout.setOnClickListener(this);
}
#Override
public void onClick(View view) {
Intent intent = new Intent(mcontext, ItemView.class);
mcontext.startActivity(intent);
}
}
}
When I run the code nothing happens when i click on a item and neither do I see any error in the logcat
Instead, try like this,
ItemAdapter(Context context, ArrayList<ItemModel> mArrayList) {
this.mArrayList = mArrayList;
this.mContext = context;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listing_item, parent, false);
return new MyViewHolder(view);
}
In your fragment, while setting adapter
ItemAdapter itemAdapter=new ItemAdapter(getActivity(),itemList);
You can have a listener sent from the fragment to adapter. Instead of trying to start an activity from the adapter, you can send it back to the fragment to open the activity.
1. Define an interface
public interface MyAdapterListener {
void openActivity(/*any values to be sent*/);
}
2. The fragment should implement MyAdapterListener
3. send the listener object to an adapter in a constructor
ItemAdapter(ArrayList<ItemModel> mArrayList, MyAdapterListener listener) {..
mListener = listener;
}
4. On Adapter - View Holder: in onClick() return to fragment
public void onClick(View view) {
mListener.openActivity(/*any values to be sent*/);
}
I was of the idea that on your application of the adapter in some activity you simply call the activity on the setOnItemClickListener of the adapter
private void loadItemsData() {
RecyclerView itemsRecyclerView = view.findViewById(R.id.posts_recycler_view);
itemsRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
itemsRecyclerView.setLayoutManager(layoutManager);
ItemAdapter myadapter = new ItemAdapter(new ArrayList<MyItem>(), getContext());
myadapter.setOnItemClickListener(new ItemAdapter.OnItemClickListener() {
#Override
public void onItemClick(View view, PostItems items) {
ViewItem.passingIntent(getActivity(), items.postid);
}
});
itemsRecyclerView.setAdapter(ListAdapter );
}
then once done that you come to the receiver activity and make way to get the variables being passed on to it
public static void passingIntent(Activity activity, Integer postid){
Intent intent = new Intent(activity, ViewItem.class);
intent.putExtra(MY_ITEM, postid);
activity.startActivity(intent);
}
I have used this approach in one of my apps lately
Try setting your click listener in onBindViewHolder like below
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
//Glide.with(mcontext).load(mArrayList.get(position).getImage()).into(holder.item_image);
holder.item_name.setText(mArrayList.get(position).getTitle());
holder.item_description.setText(mArrayList.get(position).getDescription());
holder.item_tags.setText(mArrayList.get(position).getTags());
holder.setOnClickListener(this) // put this line
Log.d("MyAdapter", "position: " + position);
}
Then of course you will override onClick in adapter rather than viewholder
I use RecyclerView adapter to display data inside an activity, I want to implement onClickListener inside the activity, currently, I am setting onClickListener inside adapter as usual which works fine.
public void onBindViewHolder(MyHolder holder, final int position) {
final Listdata data = listdata.get(position);
holder.vname.setText(data.getName());
holder.vname.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
}
});
}
However I want to implement it inside activity so I have greater control. This doesn't serve my purpose. I think it'll be useful for a lot of us.
You need to check this tutorial here for better understanding on how you can achieve the behaviour that you want.
In case of handling the onClickListener from your activity you need to work based on a callback implementation with an interface. Pass the interface from the activity to your adapter and then call the callback function from your adapter when some items are clicked.
Here's a sample implementation from the tutorial.
Let us first have the interface.
public interface OnItemClickListener {
void onItemClick(ContentItem item);
}
You need to modify your adapter to take the listener as the parameter like the one stated below.
private final List<ContentItem> items;
private final OnItemClickListener listener;
public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}
Now in your onBindViewHolder method, set the click listener.
#Override public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}
public void bind(final ContentItem item, final OnItemClickListener listener) {
...
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
Now setting the adapter in your RecyclerView.
recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
#Override public void onItemClick(ContentItem item) {
Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
}
}));
So the whole adapter code looks like the following.
public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {
public interface OnItemClickListener {
void onItemClick(ContentItem item);
}
private final List<ContentItem> items;
private final OnItemClickListener listener;
public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}
#Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
return new ViewHolder(v);
}
#Override public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}
#Override public int getItemCount() {
return items.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
private TextView name;
private ImageView image;
public ViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
image = (ImageView) itemView.findViewById(R.id.image);
}
public void bind(final ContentItem item, final OnItemClickListener listener) {
name.setText(item.name);
Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
}
}
Registering clickListener inside onCreateViewHolder instead of onBindViewHolder is more performant since you only add listener when a view is created not ever time recyclerView is scrolled.
And i use ListAdapter with DiffUtil callback instead of RecyclerViewAdapter
abstract class BaseListAdapter<ItemType>(
callBack: DiffUtil.ItemCallback<ItemType> = DefaultItemDiffCallback(),
private inline val onItemClicked: ((ItemType, Int) -> Unit)? = null
) : ListAdapter<ItemType, BaseItemViewHolder>(
AsyncDifferConfig.Builder<ItemType>(callBack)
.setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
.build()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseItemViewHolder {
return BaseItemViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
getLayoutRes(viewType),
parent, false
)
).apply {
onViewHolderCreated(this, viewType, binding)
}
}
fun createCustomViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return BaseItemViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
getLayoutRes(viewType),
parent, false
)
)
}
override fun onBindViewHolder(
holder: BaseItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
val item: ItemType? = currentList.getOrNull(position)
item?.let {
holder.binding.setVariable(BR.item, item)
onViewHolderBound(holder.binding, item, position, payloads)
holder.binding.executePendingBindings()
}
}
override fun onBindViewHolder(holder: BaseItemViewHolder, position: Int) {
}
/**
* get layout res based on view type
*/
protected abstract fun getLayoutRes(viewType: Int): Int
/**
* Called when a ViewHolder is created. ViewHolder is either created first time or
* when data is refreshed.
*
* This method is not called when RecyclerView is being scrolled
*/
open fun onViewHolderCreated(
viewHolder: RecyclerView.ViewHolder,
viewType: Int,
binding: ViewDataBinding
) {
binding.root.setOnClickListener {
onItemClicked?.invoke(getItem(viewHolder.bindingAdapterPosition), viewHolder.bindingAdapterPosition)
}
}
/**
* bind view while RecyclerView is being scrolled and new items are bound
*/
open fun onViewHolderBound(
binding: ViewDataBinding,
item: ItemType,
position: Int,
payloads: MutableList<Any>
) {
}
}
open class BaseItemViewHolder(
val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root)
class DefaultItemDiffCallback<ItemType> : DiffUtil.ItemCallback<ItemType>() {
override fun areItemsTheSame(
oldItem: ItemType,
newItem: ItemType
): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(
oldItem: ItemType,
newItem: ItemType
): Boolean {
return oldItem.hashCode() == newItem.hashCode()
}
}
Another better user experience is using onBindViewHolder with payLoad which lets you only update some part of the rows instead of whole row. For instance you have image, title and body in rows, and only body changes frequently, without payload image flashes and provides bad user experience. But with payload you can decide which part of the row should be updated allowing you not to reload parts that were not updated.
very simple and clean solution is:
create a class with the name of RecyclerTouchListener:
public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {
private GestureDetector gestureDetector;
private ClickListener clickListener;
public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
this.clickListener = clickListener;
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
#Override
public void onLongPress(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null && clickListener != null) {
clickListener.onLongClick(child, recyclerView.getChildPosition(child));
}
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
clickListener.onClick(child, rv.getChildPosition(child));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
public interface ClickListener {
void onClick(View view, int position);
void onLongClick(View view, int position);
}
}
in your recyclerview activity:
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {
#Override
public void onClick(View view, int position) {
speech(countries_list_code[position]);
}
#Override
public void onLongClick(View view, int position) {
}
}));
I found super duper easy method! I recommend this one
Example Code:
public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {
public interface OnItemClickListener {
void onItemClick(ContentItem item);
}
private final List<ContentItem> items;
private final OnItemClickListener listener;
public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}
#Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
return new ViewHolder(v);
}
#Override public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}
#Override public int getItemCount() {
return items.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
private TextView name;
private ImageView image;
public ViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
image = (ImageView) itemView.findViewById(R.id.image);
}
public void bind(final ContentItem item, final OnItemClickListener listener) {
name.setText(item.name);
Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
}
}
And Use RecyclerView Adapter using below code:
recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
#Override public void onItemClick(ContentItem item) {
Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
}
}));
i found this from here
Hope it helped you.
In my way, I just created a single instance of ClickListener, And it dispatches click event to both RecyclerView and Activity or Fragment:
class LeagueAdapter(
onLeagueSelected: (League, Int, View) -> Unit
) : RecyclerView.Adapter<LeagueHolder>() {
private val dataSet = arrayListOf<League>()
private val clickListener = View.OnClickListener { view ->
val adapterPosition = view.tag as Int
onLeagueSelected(dataSet[adapterPosition], adapterPosition, view)
// perform adapter related action here ...
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun onBindViewHolder(holder: LeagueHolder, position: Int) {
// put item position in tag field
holder.itemView.tag = position
holder.itemView.setOnClickListener(clickListener)
}
}
And inside Activity, we have something like this:
private val headerAdapter = LeagueAdapter { league, i, view ->
Log.e(TAG, "item clicked $i")
}
Create an interface for the adapter class
private OnItemClickListener mListener;
public CustomAdapter(List<Listdata> listdata, OnItemClickListener listener) {
mListener = listener;
...
...
}
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ViewHolder(View view) {
...
...
view.setOnClickLister(this);
}
#override
public void onClick(View v) {
mListener.onAdapterItemClick(getAdapterPosition())
}
}
interface OnItemClickListener {
void onAdapterItemClick(int position);
}
Let the activity implement the interface
public class CustomListActivity extends AppCompatActivity implements OnItemClickListener {
...
...
#override
public void onAdapterItemClick(int position) {
Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
}
There is another way of doing this, check out this implementation
You can let your Activity implements View.OnClickListener and pass it to adapter. Below is an example.
class RAdapter extends RecyclerView.Adapter<>{
View.OnClickListener listner;
public RAdapter(View.OnClickListener listner) {
this.listner = listner;
}
public void onBindViewHolder(MyHolder holder, final int position) {
holder.vname.setOnClickListener(listner);
}
}
But to handle click in Activity you will going to need clicked position. You can have it with adapter.getAdapterPosition() to validate which item is clicked.
Apart from that To pass click event to the Fragment/Activity you can use a Custom callback listener this way your Adapter will be reusable .
A better way to handle clicks in ViewHolder. See the below example.
class Holder extends RecyclerView.ViewHolder implements View.OnClickListener {
Button button;
public Holder(View itemView) {
super(itemView);
button=itemView.findViewById(R.id.b1);
button.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.b1){
int position=getAdapterPosition();
// Call the call method here
// with position or data Object itself
}
}
}
If I understood correctly you want to set the on click logic in the Activity.
You can do this by setting the OnClickListener in the Activity and passing it in the Adapter constructor.
MyAdapter myAdapter = new MyAdapter(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(activity, "clicked on " +position, Toast.LENGTH_SHORT).show();
}
}));
And your MyAdapter Constructor would be:
final private OnClickListener onClickListener;
public MyAdapter(OnClickListener onClickListener) {
this.OnClickListener = OnClickListener;
}
So your new code would be something like this
public void onBindViewHolder(MyHolder holder, final int position) {
final Listdata data = listdata.get(position);
holder.vname.setText(data.getName());
holder.vname.setOnClickListener(onClickListener);
}
RecyclerView widget only has 2 useful listeners for this scenario:
RecyclerView.OnChildAttachStateChangeListener - covered here
RecyclerView.OnItemTouchListener - the one that I will be covering
the code is inspired by TouchEvents sample related to Accessibility, and works in Activity/Fragment without setting any listeners in the Adapter
recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
var downTouch = false
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> downTouch = true
MotionEvent.ACTION_UP -> if (downTouch) {
downTouch = false
recyclerView.findChildViewUnder(e.x, e.y)?.let {
val position = rv.getChildAdapterPosition(it)
Toast.makeText(rv.context, "clicked on $position", Toast.LENGTH_SHORT)
.show()
}
}
else -> downTouch = false
}
return super.onInterceptTouchEvent(rv, e)
}
})
There's another very simple way documented in CodePath.
ItemClickSupport.addTo(recyclerView).setOnItemClickListener(
new ItemClickSupport.OnItemClickListener() {
#Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do stuff
}
}
);
The implementation of ItemClickSupport.
Personally, I like to handle this via RxJava subjects:
A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable. Because it is an observer, it can subscribe to one or more Observables, and because it is an Observable, it can pass through the items it observes by re-emitting them, and it can also emit new items.
For more info read Understanding RxJava Subject — Publish, Replay, Behavior and Async Subject.
in Adapter:
public static PublishSubject<MyData> onClickSubject = PublishSubject.create();
ViewHolder:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
.
.
.
#Override
public void onClick(View view) {
onClickSubject.onNext(getItem(getAdapterPosition()));
}
}
Add your disposables to a CompositeDisposable and dispose them in onDestroy():
private CompositeDisposable compositeDisposable = new CompositeDisposable();
in onCreate():
compositeDisposable.add(MyAdapter.onClickSubject.subscribe(myData -> {
//do something here
}));
in onDestroy():
compositeDisposable.dispose();
Note:
1. getItem() is a method of androidx.recyclerview.widget.ListAdapter and androidx.paging.PagedListAdapter if you are extending RecyclerView.Adapter you can get item from your data list by position.
2. to use Disposables you need RxJava2 or above
Kotlin
I'd better to add item click in onCreateViewHolder like this
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int):
ProductViewHolder {
val view: View = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout_product_item, viewGroup, false)
return ProductViewHolder(view).also { viewHolder ->
viewHolder.itemView.setOnClickListener {
val position = viewHolder.layoutPosition
if (position != RecyclerView.NO_POSITION) {
// do what you want with data[position]
}
}
}
}
You can implement the View.OnClickListener interface in your RecyclerView.ViewHolder class and call it from there.
In your Adapter class create a public interface.
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClick(int position);
}
private OnItemClickListener onItemClickListener;
On your ViewHolder class, you can implement the View.OnClickListener interface and set an onclick listener to the itemView.
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView myText;
public WalletViewHolder(#NonNull View itemView) {
super(itemView);
myText= itemView.findViewById(R.id.my_text_view);
// Set click listener for each item view
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(getAdapterPosition());
}
}
}
Then your OnItemClickListener will be created only once.
I always have one Generic Adapter in my project to avoid make a Adapter class every I use a Recyclerview. Here some example
public class AdapterRecyclerviewTextOnly extends RecyclerView.Adapter<AdapterRecyclerviewTextOnly.ViewHolder> {
private RecyclerView recyclerView;
private OnRecyclerviewListener onRecyclerviewListener;
public interface OnRecyclerviewListener {
void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position);
void onRecyclerviewClick(RecyclerView recyclerView, int position);
int onItemCount(RecyclerView recyclerView);
}
public void setOnRecyclerviewListener(OnRecyclerviewListener listener) { this.onRecyclerviewListener = listener; }
public AdapterRecyclerviewTextOnly(RecyclerView recyclerView) {
super();
this.recyclerView = recyclerView;
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
RecyclerView recyclerView;
public TextView textView;
ViewHolder(RecyclerView recyclerView, View itemView) {
super(itemView);
this.recyclerView = recyclerView;
this.itemView.setOnClickListener(this);
this.textView = itemView.findViewById(R.id.textview_title);
}
void onBind(int position) { onRecyclerviewListener.onRecyclerviewBind(this.recyclerView, this, position); }
#Override
public void onClick(View v) {
onRecyclerviewListener.onRecyclerviewClick(this.recyclerView, getAdapterPosition());
}
}
#Override
public AdapterRecyclerviewTextOnly.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View inflatedView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview_text_only, parent, false);
return new ViewHolder(this.recyclerView, inflatedView);
}
#Override
public void onBindViewHolder(AdapterRecyclerviewTextOnly.ViewHolder holder, int position) {
holder.onBind(position);
}
#Override
public int getItemCount() {
return onRecyclerviewListener.onItemCount(this.recyclerView);
}
}
And then in your Activity Class, you can use this adapter with :
this.recyclerView = findViewById(R.id.recyclerview);
this.recyclerView.setHasFixedSize(true);
this.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
AdapterRecyclerviewTextOnly recyclerViewAdapter = new AdapterRecyclerviewTextOnly(this.recyclerView);
this.recyclerView.setAdapter(this.recyclerViewAdapter);
this.recyclerViewAdapter.setOnRecyclerviewListener(new AdapterRecyclerviewTextOnly.OnRecyclerviewListener() {
#Override
public void onRecyclerviewBind(RecyclerView recyclerView, AdapterRecyclerviewTextOnly.ViewHolder viewHolder, int position) {
}
#Override
public void onRecyclerviewClick(RecyclerView recyclerView, int position) {
}
#Override
public int onItemCount(RecyclerView recyclerView) {
}
});
You can reuse this with 2 or 3 recyclerview too.
First, declare a globar listener private AdapterRecyclerviewTextOnly.OnRecyclerviewListener listener;.
Then init the listener with new object then set the your every recyclerview with the listener. Use specific identifier:
if (recyclerView == recyclerViewA){ } else if (recyclerView == recyclerViewB) { } to manage your recyclerview inside the adapter.