In the activity when user click on add client button I want to add new view to the screen which contains a spinner with list of client names retrieved from api and a button that will do some action on click.
So I thought I would use a recycleview and adapter for this but I think I'm wrong
in the activity I have the adapter
private ClientAdapter clientAdapter;
When I retrieve clients name from API I set the adapter as
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
At this point I don't want the recycle view to render anything until user click on add new client button then I want to display one item that has spinner with client names and a button.
Then if he clicks again on add client button I want to show another spinner and button and so on.
However now I'm having 3 clients so recycleview render 3 view items which make sense.
But what the trick that I should do to achieve my goal?
Here's my adapter
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
#Override
public int getItemCount() {
if (clientsList == null)
return 0;
return clientsList.size();
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}
and here's my list item
From comments:
The problem is I'm passing list of clients to the adapter (size of 3) then the adapter render 3 items. I don't want this behavior I want to have 0 item if user click on add I will render one item and so on
You are using a single ArrayList<Client> for two different purposes:
The list of clients to choose from in the spinner
The number of spinners to display in the RecyclerView.
These are two separate things, so you need two separate lists.
You can do that with just adding integer value for your ClientsAdapter. Set its default value as 0 and create a method for changing it's value. When you want to add new item (new Spinner and Button) use that method and notify your adapter.
Add a new field called count for your ClientsAdapter.
private int count;
Inside constructor assign its value to 0. So on start its value will be 0 and RecyclerView will show nothing.
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context){
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
Change getItemCount method's return value. According to your code getItemCount returns size of your List. That List is for Spinner and has no relation with this method. Instead of returning your List's size return count.
#Override
public int getItemCount() {
return count;
}
Create a method for changing count's value. count starts with 0 (assigned it 0 inside constructor) and when you click Button (add new Spinner and Button) this method will change its value.
public void addItem(int count) {
this.count = count;
}
Whenever you click Button simply call addItem method and pass new count value and notify your clientAdapter.
addClient.setOnClickListener(v -> {
int count = clientRecyclerView.getChildCount();
clientAdapter.addItem(count+1);
clientAdapter.notifyItemInserted(count);
});
NOTE: I don't get it why you're setting podAdapter for RecyclerView.
clientRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
clientAdapter= new clientAdapter(clientList , this , this);
clientRecyclerView.setAdapter(podAdapter);
You're creating clientAdapter reference for your ClientsAdapter but while setting adapter for RecyclerView, you're using different reference (podAdapter).
Full code for ClientsAdapter:
public class ClintsAdapter extends RecyclerView.Adapter<ClintsAdapter.ViewHolder> {
private List<Clients> clientsList;
private EventListener listener;
private int count;
public ClintsAdapter(List<Clients> clientsList, EventListener listener , Context context) {
this.clientsList = clientsList;
this.EventListener = listener;
count = 0;
}
#NonNull
#Override // To inflate view
public ClintsAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_client, parent, false);
ViewHolder viewHolder = new ViewHolder(view, listener);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ClintsAdapter.ViewHolder holder, int position) {
ClintsAdapter = new ArrayAdapter<Client>(context, R.layout.spinner_text_view, clientsList);
ClintsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.clientSpinner.setAdapter(ClintsAdapter);
holder.clientSpinner.setTitle("Choose client");
}
public void addItem(int count) {
this.count = count;
}
#Override
public int getItemCount() {
return count;
}
public interface PODListener {
void onClick(int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SearchableSpinner clientSpinner , collectMethodSpinner;
EventListener listener;
public ViewHolder(View itemView, final EventListener listener) {
super(itemView);
this.listener = podListener;
clientSpinner = itemView.findViewById(R.id.spinner_client);
btnComment = itemView.findViewById(R.id.btn_comment);
btnComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ( listener != null ) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
listener.onClick(position);
}
}
}
});
}
#Override
public void onClick(View v) {
}
}
}
I've been duelling with this problem for a good few hours now. I have a nested RecyclerView (i.e. a RecyclerView that encompasses an inner Recycler view). Both the parent and child recycler view's are dynamic. The problem I encounter is that I cannot find a way to correctly notify the child (inner) recycler view when a CRUD, in particular a delete, occurs. At first it works ok, but then I get all sorts of random errors from "You must be a direct descend view" or getAdapterPosition returning -1 or just simply incorrect positions. I think my implementation is pretty standard so I ask what is the correct way to notify the inner recycler view.
I am pretty close to returning to my former implementation which involved an array of fragments each containing a recycling view, but I question about the performance of such design. My code is as follows:
Parent RecyclerView
public class RecipeRecyclerAdapter extends RecyclerView.Adapter<RecipeRecyclerAdapter.ViewHolder>
{
public interface OnRecipeRecyclerListener
{
//--------------------------- Proxy methods for OnDishRecyclerListener -----------------
void renameDish(int DishPosition, int RecipePosition);
void deleteDish(int DishPosition, int RecipePosition);
//--------------------------- OnRecipeRecyclerListener methods ----------------------------
void deleteRecipe(int RecipePosition);
void renameRecipe(int RecipePosition);
}
//Recycler Pool and tools
private RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
//Recycler Parameters
private ArrayList<Recipe> allRecipes;
private Context context;
//Listener
#Setter
private OnRecipeRecyclerListener onRecipeRecyclerListener;
public RecipeRecyclerAdapter(Context context, ArrayList<Recipe> allRecipes)
{
this.allRecipes = allRecipes;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_Recipe, parent, false);
return new RecipeRecyclerAdapter.ViewHolder(view, onRecipeRecyclerListener, context);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position)
{
Recipe Recipe = allRecipes.get(position);
holder.RecipeName.setText(Utils.colourFirstLetter(context, Recipe.getRecipeName(), R.color.progressFxBar));
holder.RecipeDate.setText(Utils.getDate(Recipe.getTimestamp()));
// Create layout manager with initial prefetch item count
LinearLayoutManager layoutManager = new LinearLayoutManager(
holder.DishsRecycler.getContext(),
LinearLayoutManager.VERTICAL,
false
);
layoutManager.setInitialPrefetchItemCount(Recipe.getDishs().size());
DishRecyclerAdapter DishsRecyclerAdapter = new DishRecyclerAdapter(Recipe.getDishs(), holder, context);
holder.DishsRecycler.setLayoutManager(layoutManager);
holder.DishsRecycler.setAdapter(DishsRecyclerAdapter);
holder.DishsRecycler.setRecycledViewPool(viewPool);
}
#Override
public int getItemCount()
{
return allRecipes.size();
}
static class ViewHolder extends RecyclerView.ViewHolder implements DishRecyclerAdapter.OnDishRecyclerListener
private OnRecipeRecyclerListener onRecipeRecyclerListener;
private Context context;
TextView RecipeName, RecipeDate;
ImageView addDish;
//The Dishs Recycler
RecyclerView DishsRecycler;
public ViewHolder(#NonNull View itemView, OnRecipeRecyclerListener onRecipeRecyclerListener, Context context)
{
super(itemView);
this.onRecipeRecyclerListener = onRecipeRecyclerListener;
this.context = context;
RecipeName = itemView.findViewById(R.id.RecipeName);
RecipeDate = itemView.findViewById(R.id.RecipeDate);
addDish = itemView.findViewById(R.id.addDish);
DishsRecycler = itemView.findViewById(R.id.DishsRecyclerView);
loadListeners(itemView);
}
private void loadListeners(#NonNull View initView)
{
RecipeName.setOnClickListener(v ->
{
PopupMenu popup = new PopupMenu(context, v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.Recipe_floating_menu, popup.getMenu());
popup.show();
popup.setOnMenuItemClickListener(item ->
{
switch (item.getItemId())
{
case R.id.menuDeleteRecipe:
onRecipeRecyclerListener.deleteRecipe(getAdapterPosition());
return true;
case R.id.menuRenameRecipe:
onRecipeRecyclerListener.renameRecipe(getAdapterPosition());
return true;
case R.id.menuRecipeProps:
onRecipeRecyclerListener.RecipeProps(getAdapterPosition());
return true;
default:
return false;
}
});
});
addDish.setOnClickListener(v ->
{
onRecipeRecyclerListener.addDish(getAdapterPosition());
});
}
//******************************* OnDishRecyclerListener *******************************
#Override
public void renameDish(int position)
{
onRecipeRecyclerListener.renameDish(position, getAdapterPosition());
}
#Override
public void deleteDish(int position)
{
onRecipeRecyclerListener.deleteDish(position, getAdapterPosition());
}
}
}
Child (inner) RecyclerView
public class DishRecyclerAdapter extends RecyclerView.Adapter<DishRecyclerAdapter.ViewHolder>
{
public interface OnDishRecyclerListener
{
void renameDish(int position);
void deleteDish(int position);
}
private OnDishRecyclerListener onDishRecyclerListener;
private ArrayList<Dish> allDishs;
private Context context;
public DishRecyclerAdapter(ArrayList<Dish> allDishs, OnDishRecyclerListener onDishRecyclerListener, Context context)
{
this.onDishRecyclerListener = onDishRecyclerListener;
this.allDishs = allDishs;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_Dishs, parent, false);
return new ViewHolder(context, view, onDishRecyclerListener);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position)
{
Dish Dish = allDishs.get(position);
holder.DishName.setText(Dish.getDishName());
}
#Override
public int getItemCount()
{
return allDishs.size();
}
public class ViewHolder extends RecyclerView.ViewHolder
{
private Context context;
TextView DishName; //plus a bunch of other Views I just removed for the sake of simplicity
OnDishRecyclerListener onDishRecyclerListener;
public ViewHolder(Context context, #NonNull View itemView, OnDishRecyclerListener onDishRecyclerListener)
{
super(itemView);
this.context = context;
DishName = itemView.findViewById(R.id.DishName);
this.onDishRecyclerListener = onDishRecyclerListener;
loadListeners(itemView);
}
private void loadListeners(#NonNull View v)
{
//Rename an Dish
DishName.setOnClickListener(view ->
{
PopupMenu popup = new PopupMenu(context, v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.Dish_floating_menu, popup.getMenu());
popup.show();
popup.setOnMenuItemClickListener(item ->
{
switch (item.getItemId())
{
case R.id.menuDeleteDish:
onDishRecyclerListener.deleteDish(getAdapterPosition());
return true;
case R.id.menuRenameDish:
onDishRecyclerListener.renameDish(getAdapterPosition());
return true;
case R.id.menuDishProps:
return true;
default:
return false;
}
});
});
}
}
}
An extraction of the fragment calling the parent recycler view:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_Recipe_panel, container, false);
recyclerRecipe = view.findViewById(R.id.RecipeRecyclerView);
SimpleItemAnimator simpleItemAnimator = (SimpleItemAnimator) recyclerRecipe.getItemAnimator();
if(simpleItemAnimator !=null)
{
simpleItemAnimator.setSupportsChangeAnimations(true);
}
RecipeAdapter = new RecipeRecyclerAdapter(getContext(), allRecipes);
RecipeAdapter.setOnRecipeRecyclerListener(this);
//recyclerRecipe.setHasFixedSize(true);
recyclerRecipe.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerRecipe.setAdapter(RecipeAdapter);
return view;
}
public void createRecipe(String RecipeName)
{
Recipe Recipe = new Recipe(RecipeName, getContext());
allRecipes.add(0,Recipe);
RecipeAdapter.notifyItemInserted(0);
}
#Override
public void deleteRecipe(int RecipePosition)
{
allRecipes.remove(RecipePosition);
RecipeAdapter.notifyItemRemoved(RecipePosition);
}
#Override
public void addDish(int RecipePosition)
{
allRecipes.get(RecipePosition).getDishs().add(new Dish(DishName));
RecipeAdapter.notifyItemChanged(RecipePosition);
}
#Override
public void deleteDish(int DishPosition, int RecipePosition)
{
Recipe Recipe = allRecipes.get(RecipePosition);
Dish Dish = Recipe.getDishs().get(DishPosition);
Dish.getTimer().destroyTimer();
Recipe.getDishs().remove(DishPosition);
RecipeAdapter.notifyItemChanged(RecipePosition);
}
I figured out what the problem was (after LOADS OF HOURS). I needed to notify first the parent recycler and then the child recycler in that order.
//adding an item to the inner list
recipeAdapter.notifyItemChanged(recipePosition);
dishsRecycler.getAdapter().notifyItemInserted(recipe.getDishs().size()-1);
//deleting an inner list item
recipeAdapter.notifyItemChanged(recipePosition);
dishsRecycler.getAdapter().notifyItemRemoved()
However the biggest culprit was having a common recyclerPool for all the inner recyclerviews, so removed this line from the code
//REMOVED THESE LINES
private RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
holder.DishsRecycler.setRecycledViewPool(viewPool);
Also, I refrained from using notifyDataSet() as that for some reason throws NO_POSITION (-1).
I'm implementing a similar case.
I have 2 RecyclerViews, one nested. Where you can delete items either from nested or parent RecyclerView.
It guess you must update Recyclers every time an item changed or removed.
For comprehension I read this article first:
https://medium.com/android-news/recyclerview-optimisations-a4b141dd433d
And I agree answer by Ken John, when he said you need to notify RecyclerView updates first to parent then to nested; otherwise you get an error and your app will crash.
However, other important thing is how to do the notification updates.
For the nested RecyclerView, I used
// for items updated
notifyItemChanged(position);
// for items deleted
notifyItemRemoved(position);
but the mentioned above not working fine for parent RecyclerView, really I'm not sure why, but I solved as follow:
// for items updated
notifyItemChanged(position);
// for items deleted
notifyItemRemoved(position); // this line does not work for me
notifyDataSetChanged(); // it works fine
The last instruction spend a more bit of time, but works fine.
Note: I don't know yet why notifyItemRemoved(position) doesn't work for parent, and I have call notifyDataSetChanged()
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.
to begin I would thanks those who help noobi dev like me :p! Let's say that I have a Recyclerview in which every unit element has two images. Actually I am able to find the indice of the unit element clicked to fire the good evenment thanks to this little trick in MyAdapter:
#Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v= LayoutInflater.from(parent.getContext()).inflate(R.layout.model, parent, false);
final MyHolder holder = new MyHolder(v);
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onItemClick(v, holder.getAdapterPosition());
}
});
return holder;
}
Then I start the event this way in the activity:
MyAdapter adapter = new MyAdapter(this, countryCollection.getCountryArray(), new CustomItemClickListener() {
#Override
public void onItemClick(View v, int position) {
//Launch the events
ArrayList<String> languageCodeArray = countryCollection.getLanguageCodeArray();
ArrayList<String> countryIdArray = countryCollection.getCountryIdArray();
if(position >0){
String languageCode = languageCodeArray.get(position-1);
String countryId = countryIdArray.get(position-1);
Intent intent = new Intent(context, LetsSpeakActivity.class);
intent.putExtra("country_id", countryId);
intent.putExtra("language_code", languageCode);
startActivity(intent);
}
}
});
rv.setAdapter(adapter);
But how can I différentiate the two images of my RecyclerView in order to fire the good event? I've been searching for a while how to differentiate a click on the left or right side of the screen but it is obviously not the right way to do ! Any help would be lovely, I am really stuck...
Supposing you have defined both ImageView in your MyHolder class, you could set OnClickListener on both of them in your holder constructor :
public class MyHolder extends RecyclerView.ViewHolder {
public ImageView imageLeft;
public ImageView imageRight;
public CustomItemClickListener listenerLeft;
public CustomItemClickListener listenerRight;
public MyHolder(View v, CustomItemClickListener listenerLeft, CustomItemClickListener listenerRight) {
super(v);
this.listenerLeft = listenerLeft;
this.listenerRight = listenerRight;
imageLeft = v.findViewById(R.id.image_left);
imageRight = v.findViewById(R.id.image_right);
imageLeft.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listenerLeft.onItemClick(v, getPosition());
}
});
imageRight.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listenerRight.onItemClick(v, getPosition());
}
});
}
}
while injecting both listener in onCreateViewHolder :
#Override
public MyAdapter.MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.model, parent, false);
return new MyHolder(v, mImageLeftListener, mImageRightListener);
}
You can introduce both of these listener in your Adapter constructor like you did with listener
Or if you want to pass only one listener, you can define your CustomItemClickListener interface with 2 methods (or just 1 with an additional param to differentiate the image clicked) :
public interface CustomItemClickListener {
void onLeftImageClick(View view, int position);
void onRightImageClick(View view, int position);
}
I have use the Switch in the RecyclerView. It have facing the issue of recycling behaviour. When I switch on the 1st position ,it automatically on the switch at 10 postion ... I think it due to reuse of the view. How to fix it. find the screenshot:
https://www.dropbox.com/s/4ms2jf9e28fyc7u/error.png?dl=0
private void setAdapter(ArrayList data) {
ManageCategoryAdapter adapter = new ManageCategoryAdapter(data);
adapter.SetOnItemClickListener(listClick);
mRecyclerView.setAdapter(adapter);
}
public class ManageCategoryAdapter extends RecyclerView.Adapter<ManageCategoryAdapter.ViewHolder> {
private ArrayList<String> catData=new ArrayList<>();
private OnItemClickListener mItemClickListener;
public ManageCategoryAdapter(ArrayList<String> listadap) {
catData=listadap;
System.out.println("$$$$$$$$$"+"adapterclass");
}
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_manage_list, parent, false);
return new ViewHolder(v);
}
public void onBindViewHolder(ViewHolder holder, int position) {
holder.category.setText(catData.get(position));
}
public int getItemCount() {
return catData.size();
}
public void onClick(View view) {
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView category;
public Switch switchClick;
public ViewHolder(View itemView) {
super(itemView);
category=(TextView)itemView.findViewById(R.id.cat_text);
switchClick=(Switch)itemView.findViewById(R.id.switch_btn);
switchClick.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(v, getPosition());
}
}
}
public void myNotifyDataSetChanged(ArrayList list)
{
System.out.println("$$$notify");
catData.addAll(list);
this.notifyDataSetChanged();
}
public interface OnItemClickListener {
public void onItemClick(View view, int position);
}
public void SetOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
}
This how I set the adapter class
You need to use this in the adapter:
#Override
public int getItemViewType(int position) {
return position;
}
This is a very common problem with RecyclerView and there are lots of answers there in Stackoverflow.
You already have understood your problem i.e. reusing the views. So you might take a look at these answers to get a better idea about how you can overcome it.
Put an else condition everywhere you need to update a view of a list
item dynamically.
Using another list to keep track of the list items in which the
Switch is enabled or disabled. You can see this answer here.
These will do the trick for you I hope.