preface

In the last project, there were many, many, many recyclerViews, and THEN I had to write many, many, many adapters and Viewholder — that’s fine, but there was a lot of duplicate code in it. Each Adapter and ViewHolder actually do the same things: view binding, data binding, click event distribution. What else? If they’re all doing the same thing, why do we keep writing the same code?

The body of the

BaseAdapter

How to create a RecyclerView Adapter?

  • Receive a list of data
  • Rewrite the getItemCount() method to determine the number of items
  • Rewrite onCreateViewHolder() and bind Layout to create our own recyclerView.viewholder
  • Override the onBindViewHolder() method for data and view binding
  • Because RecyclerView didn’t write click events, it distributed click events

It’s basically the same thing, or add a refreshData() method — pass in the new data and notifyDataSetChanged(). Based on these points, I wrote a BaseAdapter base class:

/** Created by lypeer on 16-5-24. */ public Abstract class BaseAdapter Extends recyclerView. Adapter {/** * private List mValueList; Private OnItemClickListener mOnItemClickListener; private OnItemClickListener mOnItemClickListener; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return createViewHolder(parent.getContext(), parent); } @Override @SuppressWarnings("unchecked") public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((BaseViewHolder) holder).setData(mValueList.get(position), position, mOnItemClickListener); Public void setOnClickListener(OnItemClickListener) {public void setOnClickListener(OnItemClickListener) { this.mOnItemClickListener = listener; } public void refreshData(List valueList) {this.mvaluelist = valueList; notifyDataSetChanged(); } @Override public int getItemCount() { return mValueList == null ? 0 : mValueList.size(); } / / generate ViewHolder * @param context * @param parent * @return */ protected BaseViewHolder createViewHolder(Context context, ViewGroup parent); }Copy the code

Its subclasses need to specify the specific type of the generic when inheriting it, since different items may have different data types, thus accommodating more items. In addition, there is an interface OnItemClickListener mentioned, which is very simple:

/** * Created by lypeer on 16-5-24. */ public interface OnItemClickListener {/** * Event distribution when an item is clicked ** @param viewID @param position position of the clicked item */ void onItemClick(V itemValue, int viewID, int position); }Copy the code

You need to use generics when you use it — for the same reason.

Using the BaseAdapter above, we encapsulate many of the common operations in the base class, and its subclasses just need to create a different ViewHolder as needed — of course, this ViewHolder must be inherited from the BaseViewHolder. And what a BaseViewHolder is will be explained in more detail below. Here is an example. Suppose we now have a RecyclerView in an interface, and the data of each Item is a String value. Then how to use our BaseAdapter to simplify the development process?

public class SampleAdapter extends BaseAdapter { @Override protected BaseViewHolder createViewHolder(Context context, ViewGroup parent) { return new SampleViewHolder(context, parent); }}Copy the code

Yes, you read that right! There are only a few lines of code! 5 seconds! Surprise? !

All you need to do is create a new SampleAdapter that inherits from the BaseAdapter, specify its generic type as String, and return new SampleViewHolder(context, parent).

However, some readers may be wondering: maybe we need to do a lot of work inside the SampleViewHolder? Isn’t that just shifting the code in one place, not really optimizing it?

But it’s not.

BaseViewHolder

I have always thought it unwise to write the ViewHolder inside the Adapter. The Adapter class is doing too much. It does everything from data reception to interface binding, control initialization to click event distribution. And that’s not good. Very easily, a complex Adapter of RecyclerView can have hundreds or thousands of lines of code, which is scary, and means that the class will be jumbled and difficult to maintain. So how do you avoid that? I chose to separate the Viewholers and increase the viewholers’ responsibilities so that they are balanced.

Look directly at the code for BaseViewHolder:

/** * Created by lypeer on 16-5-27. */ public Class BaseViewHolder extends RecyclerView.ViewHolder { public BaseViewHolder(Context context, ViewGroup root, int layoutRes) { super(LayoutInflater.from(context).inflate(layoutRes, root, false)); ButterKnife.bind(this, itemView); ** @return caller's Context */ public Context getContext() {return itemView.getContext(); } /** * abstract method, * * @param Position Position of the current Item * @param Listener Click event listener */ protected abstract void bindData(V itemValue, int position, OnItemClickListener listener); ** @param itemValue * @param position * @param listener */ public void setData(V itemValue, int position, OnItemClickListener listener) { bindData(itemValue, position, listener); }}Copy the code

BaseViewHolder also uses generics to accommodate different data types. At the same time, I used ButterKnife in BaseViewHolder to simplify the code and eliminate the need for findViewById repetition.

In addition, you can see that the SampleViewHolder constructor is passed three parameters, but in the example of BaseAdapter, the SampleViewHolder constructor is passed only the first two parameters, and the third parameter layoutRes is not passed. What’s going on here? Is it wrong? Of course not. The BaseViewHolder constructor has three passed parameters because it needs three, and its subclass has only two because it can only have two. _BaseViewHolder must satisfy its super construct, so it must have those three arguments, and its subclasses if those three arguments are passed in from the outside, how does it operate specifically on that layout? It has to explicitly specify Layout ID_ in the class — this is something I really want to optimize because subclasses that inherit from BaseViewHolder need to modify their constructors, which is a bit more cumbersome, but there are no good ways to optimize it elegantly.

There are two methods in BaseViewHolder that look very similar: setData() and bindData(), but are in fact completely different except for passing the same parameters. The setData() method is a public method that can be called by an object subclass of BaseViewHolder to pass in data from the outside for initialization by the view held by the ViewHolder. The setData() method acts as a bridge for transmitting information. GetData (), on the other hand, is an abstract method whose concrete implementation is in each BaseViewHolder subclass, which does the binding and initialization of the control, handling of the control’s click events, and so on.

Speaking of handling click events, how should a subclass do that? Through the OnItemClickListener. We can use the OnItemClickListener interface callback to pass click events that need to be processed to the outside world for processing. What if you have multiple control click events to handle? Don’t worry, because in the OnItemClickListener onClick method you need to pass in the id of the clicked control, so that the incoming ID can be checked from the outside and different click events can be executed for different ids.

To see how this can be used, consider SampleViewHolder:

public class SampleViewHolder extends BaseViewHolder { @Bind(R.id.is_tv_content) TextView mIsTvContent; public SampleViewHolder(Context context, ViewGroup root) { super(context, root, R.layout.item_sample); } @Override protected void bindData(final String itemValue, final int position, final OnItemClickListener listener) { if (itemValue ! = null) { mIsTvContent.setText(itemValue); } mIsTvContent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(listener == null){ return; } listener.onItemClick(itemValue , v.getId() , position); }}); }}Copy the code

It’s also simple, easy, quick and clear. But there are a few caveats. First, do not forget to modify the constructor by explicitly specifying the Layout ID in super. Otherwise, you will not be able to do the next step. In addition, the listener must be nulled when the listener.onclick () method is called — because the listener really could be empty! It is very easy to create empty exceptions and crash your program.

Bingo, and so the SampleViewHolder is done, again with almost no effort.

conclusion

The goal of this blog post is to share some of the things I’ve come up with that will hopefully speed up your development just a little bit. Of course, there may be a bug I haven’t found, if you found a problem in the process of using, please don’t hesitate to hit me!

Finally, we will summarize how to solve the RecyclerView process in the case of BaseAdapter and BaseViewHolder:

  • ViewHolder related

    • New XXXViewHolder inherits from BaseViewHolder and specifies the generic type (that is, the data type of the data in Item).
    • Delete the layoutRes parameter from the constructor and specify the Layout ID explicitly in super.
    • Bind the control with ButterKnife.
    • Initialize the control and pass the click event in the bindData() method (don’t forget null check for the listener)
  • Adapter related

    • Create a new XXXAdapter that inherits from the BaseAdapter and specifies the generic type (that is, the data type of the Item data).
    • return new XXXViewHolder(context, parent);
  • The relevant

    • Bind RecyclerView and create XXXAdapter.
    • Call the baseAdapter.refreshData () method to pass in the list of data.
    • If you have to click event handling requirements, call the BaseAdapter. SetOnClickListener () method.

At present, I only made BaseAdapter for a single Item in RecyclerView, but BaseViewHolder can be universal, and it can greatly simplify the volume of Adapter under multiple items.

The source code for BaseAdapter and BaseViewHolder, as well as demo, is here.