sandalone
sandalone

Reputation: 41759

How to avoid multiple calls to findViewById() in Adapter class?

I am trying to speed up an app as much as possible by calling findViewById() as little as possible in the Adapter class.

I've heard of a "way" where I can create a HashMap<T> containing View instances and integers as values, and use it along or together with adapter's ViewHolder inner class. As you can see, I heard very little about this and did not have chance to ask more questions about this "way".

I have been searching for the solution on the Internet, but I am either searching using the wrong keywords or I am not recognizing solutions as such.

Can anyone direct me in a right way? Maybe someone writing a small sample on how to accomplish this?

EDIT 1

I don't have any issues with my current ListView as it's already using ViewHolder pattern. The reason for this question was because I heard that you can actually gain more speed using HashMap pattern than using ViewHolder pattern. I know this is vague description, but that's why I came here to ask.

Sharing a code would not help at all, because it's just a regular adapter using ViewHolder pattern, just like the one in the links you shared. There is no any experimenting or something that made the adapter slow.

Saying it again, the main reason to ask this was to find our if there is a pattern faster than the ViewHolder

Upvotes: 3

Views: 1271

Answers (2)

PaulR
PaulR

Reputation: 3293

You should be using the ViewHolder paradigm which is most easily implemented with RecyclerView when Adapters are involved. Online sample code available here and GitHub sample cloneable via AndroidStudio directly or via Github.

The HashMap solution will work, but why incur a HashMap lookup when you can simply hold a reference to the object? Also, RecyclerView natively handles different View TYPEs so you don't have to roll your own solution when you have section header rows which look differently from data rows.

===============================================

A more detailed explanation of RecyclerView follows based on snippets from the sample I linked to.

Have your CustomAdapter extend RecyclerView.

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

Create a Custom ViewHolder class. It should almost always be a static class if embedded within your CustomAdapter.

    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;

        public ViewHolder(View v) {
            super(v);
            // Define click listener for the ViewHolder's View.
            v.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d(TAG, "Element " + getPosition() + " clicked.");
                }
            });
            textView = (TextView) v.findViewById(R.id.textView);

Notice that the ViewHolder finds the views in the constructor since a ViewHolder instance is tied to a View instance. The ViewHolder should have references to any UI elements that you plan to update in an onBind callback. The ViewHolder object is then associated with the View by RecyclerView when you include the following code in your CustomAdapter.

// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    // Create a new view.
    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.text_row_item, viewGroup, false);

    return new ViewHolder(v);
}

Notice how the onCreateViewHolder callback is given a viewType parameter. The current code always returns the same type of custom ViewHolder, but it could be providing different ViewHolder classes based on the type (this is how you support rows that support different views). The following code is then how you update your UI when the view is bound to a dataset.

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
    Log.d(TAG, "Element " + position + " set.");

    // Get element from your dataset at this position and replace the contents of the view
    // with that element
    viewHolder.getTextView().setText(mDataSet[position]);
}

With these pieces in place RecyclerView will create and reuse your Views AND ViewHolders by corresponding type reducing view hierarchy look ups and potentially making your app much less "janky".

Upvotes: 1

Raanan
Raanan

Reputation: 4775

The way to reduce the amount of calls to findViewById is to "recycle" Views with ViewHolder. The idea behind is to keep inflated views and reuse them when needed - and since they already have ViewHolders you don't need to make the findViewById calls.

As @nhaarman described in the last part of his answer the way to do it for listviews is in the getView to reuse the convertView if it's not null and using the referenced view in the ViewHolder to update them.

An approach how to create ViewHolder and store them in the View by setTag is described in the first link @nhaarman provided. Then in the getView function if convertView isn't null call the getTag inorder to get the ViewHolder back.

In another note, you should look into RecyclerView which is a listview that enforces the ViewHolder pattern.

Upvotes: 1

Related Questions