5

It is my third day now dealing with the handling of my view clicks. I originally was using ListView, then I switched to RecyclerView. I have added android:onclick elements to every control on my row_layout and I am handling them in my MainActivity like this:

public void MyMethod(View view) {}

In my old ListView implementation, I have done setTag(position) to be able to get it in MyMethod by doing this inside it:

Integer.parseInt(view.getTag().toString())

This worked nicely without problems. Though now I am dealing with RecyclerView and being forced to use the ViewHolder, which does not offer a setTag method. After searching for 2 hours, I have found that people use setTag like this:

holder.itemView.setTag(position)

This was acceptable. Though when I try to get the value from the MyMethod function using the line:

Integer.parseInt(view.getTag().toString())

The application crashes. I have read several implementation of onclick handling inside the adapter which works but I have to use the MainActivity because I am using something that is unique to that activity.

TL;DR I want to send the position of the clicked row to my MainActivity in a simple manner.

Edit: I apologize for the confusion since my topic was not very thorough. I have a RecyclerView and an adapter. The adapter is linked to my row_layout. This row_layout xml has one root LinearLayout. Inside it there is one TextView, another LinearLayout (which has two TextViews) and one Button (for simplicity). I do not want to suffer for dealing with the clicks on RecylerView like I did with the ListView. So, I have decided to add an android:onclick for every control, then link TextView and LinearLayout to a single method and link the Button (and future Buttons) to their unique methods. What I am missing is that I want to be able to tell the position on each of the receiving methods on my MainActivity. If I must link everything that comes from the adapter and goes into the MainActivity to a single onclick handler, so be it. Although, how would I tell which control fired the click?

Edit 2: The requested layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:onClick="MyMethod"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:weightSum="1">
    <TextView
        android:id="@+id/letter"
        android:onClick="MyMethod"
        android:layout_width="60dp"
        android:layout_height="fill_parent"
        />
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:onClick="MyMethod"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/firstname"
            android:onClick="MyMethod"
            android:layout_width="fill_parent"
            android:layout_height="17dp" />
        <TextView
            android:id="@+id/longname"
            android:onClick="MyMethod"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
    <Button
        android:text="Test"
        android:onClick="OtherMethod"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:id="@+id/process"/>
</LinearLayout>
codewario
  • 19,553
  • 20
  • 90
  • 159
John Anderson
  • 124
  • 1
  • 1
  • 9

2 Answers2

13

You can achieve this by creating an interface inside your adapter for an itemclicklistener and then you can set onItemClickListener from your MainActivity.

Somewhere inside your RecyclerViewAdapter you would need the following:

private onRecyclerViewItemClickListener mItemClickListener;

 public void setOnItemClickListener(onRecyclerViewItemClickListener mItemClickListener) {
        this.mItemClickListener = mItemClickListener;
    }

    public interface onRecyclerViewItemClickListener {
        void onItemClickListener(View view, int position);
    }

Then inside your ViewHolder (which I've added as an inner class inside my adapter), you would apply the listener to the components you'd like the user to click, like so:

class RecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public ImageView imageview;

        RecyclerViewHolder(View view) {
            super(view);
            this.imageview = (ImageView) view
                    .findViewById(R.id.image);
            imageview.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (mItemClickListener != null) {
                mItemClickListener.onItemClickListener(v, getAdapterPosition());
            }
        }
    }

This example shows an onClickListener being applied to the image inside a ViewHolder.

recyclerView.setAdapter(adapter);// set adapter on recyclerview
            adapter.notifyDataSetChanged();// Notify the adapter
            adapter.setOnItemClickListener(new RecyclerViewAdapter.onRecyclerViewItemClickListener() {
                @Override
                public void onItemClickListener(View view, int position) { 
//perform click logic here (position is passed)
                    }
                });

To implement this code, you would setOnItemClickListener to your adapter inside MainActivity as shown above.

EDIT

Because the View is getting passed into the OnItemClickListener, you can perform a switch statement inside the listener to ensure that the right logic is being performed to the right component. All you would need to do is take the logic from the MyMethod function and copy and paste it to the component you wish it to be applied to.

Example:

recyclerView.setAdapter(adapter);// set adapter on recyclerview
                adapter.notifyDataSetChanged();// Notify the adapter
                adapter.setOnItemClickListener(new RecyclerViewAdapter.onRecyclerViewItemClickListener() {
                    @Override
                    public void onItemClickListener(View view, int position) { 
        Switch (view.getId()) {
            case R.id.letter:
               //logic for TextView with ID Letter here
            break;
            case R.id.firstname:
               //logic for TextView with ID firstname here
            break;
            ....
            //the same can be applied to other components in Row_Layout.xml
        }
                        }
                    });

You would also need to change something inside the ViewHolder. instead of applying the OnClickListener to an ImageView, you would need to apply to the whole row like so:

 RecyclerViewHolder(View view) {
                super(view);
                this.imageview = (ImageView) view
                        .findViewById(R.id.image);
                view.setOnClickListener(this);
            }

EDIT 2

Explanation:

So, with every RecyclerView. You need three components, The RecyclerView, RecyclerViewAdapter and the RecyclerViewHolder. These are what define the actual components the user sees (RecyclerView) and the Items within that View. The Adapter is where everything is pieced together and the Logic is implemented. The ins and outs of these components are nicely explained by Bill Phillips with the article RecyclerView Part 1: Fundamentals For ListView Experts over at Big Nerd Ranch.

But to further explain the logic behind the click events, it's basically utilizing an interface to pass information from the RecyclerViewAdapter to the RecyclerViewHolder to your MainActivity. So if you follow the life-cycle of the RecyclerView adapter, it'll make sense.

The adapter is initialized inside your MainActivity, the adapter's constructor would then be called with the information being passed. The components would then be passed into the adapter via the OnCreateViewHolder method. This itself tells the adapter, that's how you would like the list to look like. The components in that layout, would then need to be individually initialized, that's where the ViewHolder comes into play. As you can see like any other components you would initialize in your Activities, you do the same in the ViewHolder but because the RecyclerViewAdapter inflates the ViewHolder you can happily use them within your adapter as shown by Zeeshan Shabbir. But, for this example you would like multiple components to have various logic applied to each individual one in your MainActivity class.

That's where we create the click listener as a global variable (so it can be accessed by both the ViewHolder and the Adapter) the adapter's job in this case is to ensure the listener exists by creating an Interface you can initialize the listener through.

 public interface onRecyclerViewItemClickListener {
    void onItemClickListener(View view, int position);
 }

After you've defined the information you would like the interface to hold (E.G. the component and it's position), you can then create a function in which the adapter will call to apply the logic from your Activity (same way you would called View.OnClickListener) but by creating a setOnItemClickListener, you can customize it.

public void setOnItemClickListener(onRecyclerViewItemClickListener mItemClickListener) {
        this.mItemClickListener = mItemClickListener;
    }

This function then needs onRecyclerViewItemClickListener variable passed to it, as seen in your MainActivity. new RecyclerViewAdapter.onRecyclerViewItemClickListener() in this case it's the interface you created before with the method inside that would need to be implemented hence the

@Override
                public void onItemClickListener(View view, int position) {
                }

is called.

All the ViewHolder does in this scenario is pass the information (The components it's self and the position) into the onItemClickListener with the components attached (inside the onClick function) to finalize the actual click functionality.

if you would like me to update the explanation in anyway, let me know.

BradleyIW
  • 1,338
  • 2
  • 20
  • 37
  • This is really a nice solution. Though, I do not think it will work for my case. I could be wrong. though, since I am still learning. From my understanding, this will work for only one control. If I set it to multiple controls (Button, TextView, etc.). They will all lead to one click handler on my MainActivity. So, I would not be able to distinguish which control got the click and deal with its logic. I am using the android:onclick option to be able to receive the clicks on multiple methods on my activity. If I can only get the setTag to work in my case, that would end my suffering. – John Anderson Dec 08 '16 at 14:47
  • you can apply the logic to several different cases, this is just an example of an image. You should really be adding onclicklisteners to the components within the MainActivity that are unique to that activity (e.g. XML) and I would say apply this logic to any items that are within the RecyclerView. There's no elegant way around using a RecyclerView without a ViewHolder for the layout of the items in the RecyclerView. – BradleyIW Dec 08 '16 at 14:52
  • Can you add the XML for the row_layout? – BradleyIW Dec 08 '16 at 15:12
  • Because you're inflating this layout inside your adapter, this solution would be the cleanest. Ill add an edit to show you how you can differentiate between the different components using a switch statement. – BradleyIW Dec 08 '16 at 15:34
  • check the edit. this would be the most viable solution for you. – BradleyIW Dec 08 '16 at 15:46
  • Wow, this is what I was looking for exactly. Thanks a lot. I really really appreciate it. I ended up removing the onclick elements from my xml and programmatically adding them on the RecyclerViewHolder's constructor. If it is not much to ask, can you explain to me how your solution works, exactly. I am new to the android programming. Thanks again. Edit: I saw the last part and used it instead of the messy Layout clicks handling. – John Anderson Dec 08 '16 at 16:52
  • let me know if it makes sense. – BradleyIW Dec 08 '16 at 16:57
  • 2
    Thanks for the explaination. It did not make sense at first. So, I had to take a break from all the suffering. I cannot believe I have spent days on an interface issue. I hope I do not have any similar problems with the rest of controls. It is really a brilliant solution. You are using the MainActivity to call the adapter with an instance of the custom clicking method. Then, you capture the clicks at the ViewHolder level and redirect them to clicking custom interface method. Thanks again for the fantastic solution. – John Anderson Dec 08 '16 at 18:43
  • What should I do if I want to implement and OnKeyPressed action in one editText of an item from the list? – Marcos Guimaraes Sep 01 '17 at 16:26
1

I think you are stuck with handling multiple clicks on ReceylerView if that is the case then let me share you the a code snippet from my project. That's how i handle the click in RecyclerView

public class ExploreItemAdapter extends RecyclerView.Adapter<ExploreItemAdapter.ViewHolder> {
private WSResponseDTO<Data> wsResponseDTO;

public ExploreItemAdapter(WSResponseDTO<Data> wsResponseDTO) {
    this.wsResponseDTO = wsResponseDTO;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.consumer_dashboard_item, parent, false);
    return new ViewHolder(view);
}

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    holder.tvCompanyName.setText(wsResponseDTO.getData().getPosts().get(position).getStoreInfo().getStoreName());
    holder.tvBranchName.setText("(" + wsResponseDTO.getData().getPosts().get(position).getStoreInfo().getBranchName() + ")");
    holder.tvLikes.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getFavoriteCount() + "");
    holder.tvShares.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getShareCount() + "");
    holder.validity.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getValidFrom() + "-" +
            wsResponseDTO.getData().getPosts().get(position).getPost().getValidTo());
    holder.sTime.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getTime());
    holder.tvTitle.setText(wsResponseDTO.getData().getPosts().get(position).getPost().getHeading());
    holder.cardView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(view.getContext(), "card is touched", Toast.LENGTH_SHORT).show();
            view.getContext().startActivity(new Intent(view.getContext(), ConsumerDetailOfferActivity.class).putExtra("post", wsResponseDTO.getData().getPosts().get(position)));
        }
    });
}

@Override
public int getItemCount() {
    return wsResponseDTO.getData().getPosts().size();
}

public class ViewHolder extends RecyclerView.ViewHolder {
    @BindView(R.id.card_explore)
    CardView cardView;
    @BindView(R.id.tv_company_name)
    TextView tvCompanyName;
    @BindView(R.id.tv_branch_name)
    TextView tvBranchName;
    @BindView(R.id.tv_title)
    TextView tvTitle;
    @BindView(R.id.tv_like_count)
    TextView tvLikes;
    @BindView(R.id.tv_share_count)
    TextView tvShares;
    @BindView(R.id.tv_validity)
    TextView validity;
    @BindView(R.id.tv_sTime)
    TextView sTime;


    public ViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}
}

you can set click listener to any item in bindViewHolder() like i did for cardView I hope this will help some.

Zeeshan Shabbir
  • 6,704
  • 4
  • 38
  • 74
  • I am not stuck with the multiple click handling per se. What I need is to find a simple way to present the RecyclerView clicked position to my MainActivity while knowing what control caused it. I can implement the click handling for all my controls and do it within the adapter and I would not have to deal with any issues if it was not for me needing to use a resource that is only available within the MainActivity. Thanks for the feedback. Much Appreciated. – John Anderson Dec 08 '16 at 15:41
  • consider passing that resource into adapter. – Zeeshan Shabbir Dec 08 '16 at 15:49