RecyclerView is a list component commonly used in Android development. It is a more advanced and flexible ListView. It is more complex to use, but very flexible. We can package RecyclerViewAdapter and ViewHolder to facilitate the use in the future development process.

Package learning from Mooc courses online

Basic use of RecyclerView

Using a RecyclerView requires the following components:

  • RecyclerView: RecyclerView component as the container of the whole list, used to hold the list content.
  • LayoutManager: a LayoutManager used to manage the arrangement of RecyclerView elements. Different layout managers can be used to achieve different layout effects, such as linear layout, waterfall flow, etc. The Android SDK provides some default layout managers, such asLinearLayoutManager.GridLayoutManagerEtc. We can also customize LayoutManager.
  • ViewHolder: Each ViewHolder manages a View, and the ViewHolder is managed by recyclerView. Adapter. Each RecyclerView does not generate a ViewHolder for each View. There is a binding concept between ViewHolder and View, and when the list slides, the View that has drawn the screen is unbound, binding the newly drawn data to the ViewHolder.
  • Recyclerview. Adapter: Create a ViewHolder and bind the ViewHolder to the data.

When we use RecyclerView, the most key is to inherit recyclerView. Adapter, to achieve their own Adapter. Adapter needs to complete the tasks include:

  • Get the total size of item (getItemCount())
  • According to different data coordinates (position), obtain different viewtypes, that is, a RecyclerView can contain different display forms.
  • Create ViewHolder (implementation) as needed, depending on the viewTypeonCreateViewHolder()Methods)
  • Bind ViewHolder to specific data if needed (onBindView())

A ViewHolder contains a concrete View, and we should inherit RecyclerView.ViewHolder to implement our own ViewHolder.

RecyclerView image description

The above is a very abstract concept description, I try to use a kind of image metaphor to introduce RecyclerView related concepts, so as to understand.

We can imagine a scenario where we have a warehouse that stores a variety of goods, and at the same time, we have a window that displays a certain number of goods at the same time. The person looking at the goods can be manipulated in a way on the window to switch the goods displayed in the window.

To achieve this function, there is a row of ViewHolder behind the window. Each position on the ViewHolder holds the goods to be displayed. When the user operates the window, the belt rotates and switches the goods to be displayed in turn.

, of course, the quantity of the goods in our warehouse is often greater than the window can display the number of goods, in order to achieve the above functions, we don’t need a long the length of the conveyor belt, only need than the window can display the number of goods a little bit longer, image, we can imagine, the conveyor belt is a ring, It’s like a baggage carousel at the airport.

In order to make the user switch operation in the window, the display of goods is changed in a certain order, we need a manager (recyclerView. Adapter) to keep loading and unloading goods, so that the outside users in the window, as if there is an infinite conveyor belt behind the transmission of goods.

The above is according to my understanding of RecyclerView, the construction of an image of the scene description, we will analyze the various operations of RecyclerView.

ViewHolder

A ViewHolder is a module that holds goods on a conveyor belt. When we create a ViewHolder, we determine what type of goods the ViewHolder will hold. Therefore, when each ViewHolder is created, I need to pass in a concrete itemView.

Here the concept can be further subdivided. For example, the data we want to display is a beautiful piece of clothing, and our ViewHolder also contains an itemView for hanging clothes. When we create a ViewHolder, We do not know which clothes to hang (we do not know the specific data, it can be reused), but we know that it must be a dress.

The ViewHolder is the process of attaching a specific item of clothing to the hanger, corresponding to the onBindViewHolder() of the Adapter.

onCreateViewHolder()

At first, our conveyor belt is still empty. When we need to display data, our Warehouse manager (Adapter) will generate the corresponding itemView according to the type of data to display (getItemViewType()). If it’s a hanger, if it’s a flower, if it’s a vase, etc.), but at this point, the itemView doesn’t have any data (no clothes on the hanger, no flowers in the vase), and at the same time, add a section to the conveyor belt and place the itemView.

onBindViewHolder()

When the conveyor belt is about to turn to the window, we need to put the appropriate data on the conveyor belt. The Adapter will pay attention to the current position of the conveyor belt to determine the number of data to be placed on the next ViewHolder. GetItemViewType (int position) getItemViewType(int position) getItemViewType(int position) = 88 Do you have a ViewHolder available? If not, create one. If you have one, take the flowers from the original vase and put new ones in. This is essentially the process of binding a ViewHolder to a specific piece of data, enabling the reuse of the ViewHolder.

Other operating

The Adapter, also known as the warehouse manager, should also manage the goods in the warehouse, such as adding new goods, removing existing goods, empting goods and so on. In the process of completing these operations, it should also ensure that the goods displayed in the window are correct.

After completing the operation, we should call notify_, a series of correct functions, to check the correct data display to ensure that the correct data is displayed even after changing the data set.

BaseRecyclerAdapter encapsulation

Full code click here.

Inheritance and generics

public abstract class BaseRecyclerAdapter<Data>
        extends RecyclerView.Adapter<BaseRecyclerAdapter.BaseViewHolder<Data>>
        implements View.OnClickListener.View.OnLongClickListener.AdapterCallback<Data> {... }Copy the code

RecyclerAdapter is inherited from RecyclerView.Adapter, we named BaseRecyclerAdapter.

BaseRecyclerAdapter specifies a generic that specifies the type of Data in RecyclerAdapter. The inherited recyclerView. Adapter needs to specify a generic type, which is the concrete type of ViewHolder. Here our ViewHolder is created as an internal RecyclerAdapter class. Therefore designated as < BaseRecyclerAdapter. BaseViewHolder < Data > >

The BaseRecyclerAdapter also implements two interfaces for handling click events, as described later.

In addition, the BaseRecyclerAdapter implements AdapterCallback, which is the interface we defined ViewHolder to handle data updates.

/ * * *@author Dcr
 */
public interface AdapterCallback<Data> {
    /** * Update data callback *@paramData Updated data *@param holder BaseViewHolder
     */
    void update(Data data, BaseRecyclerAdapter.BaseViewHolder<Data> holder);
}
Copy the code

Member variables

private final List<Data> mDataList;
private AdapterListener<Data> mListener;
Copy the code

BaseRecyclerAdapter has two private members:

MDataList is aList type that represents the List of data, i.e. the warehouse specified in our explanation above, and represents all data to be displayed.

Another member variable, mListener, is the Listener we constructed to handle click events, which we’ll describe later.

A constructor

The two private members of the BaseRecyclerAdapter correspond to three constructors.

public BaseRecyclerAdapter(a) {
    / / empty data
    mDataList = new ArrayList<>();
}

public BaseRecyclerAdapter(@NonNull List<Data> dataList) {
    this(dataList, null);
}

public BaseRecyclerAdapter(@NonNull List<Data> dataList, AdapterListener<Data> listener) {
    mDataList = Objects.requireNonNull(dataList);
    mListener = listener;
}
Copy the code

In the constructor, I used the objects.requirenonNULL () method and the @nonNULL annotation, which means that if you use a constructor with arguments, you can’t pass in an empty list. If you don’t have data, you pass in an empty constructor. Let the user specify the constructor explicitly to avoid accidentally passing in a null pointer.

BaseViewHolder

From the above analysis, we know that ViewHolder plays an important role in carrying View, and we need to inherit RecyclerView.ViewHolder to realize our ViewHolder.

/**
 * ViewHolder
 *
 * @param<Data> Generic type */
public static abstract class BaseViewHolder<Data> extends RecyclerView.ViewHolder {

    private Unbinder mUnbinder;
    private AdapterCallback<Data> callback;

    protected Data mData;


    public BaseViewHolder(@NonNull View itemView) {
        super(itemView);
    }

    /** * used to bind data trigger **@paramData Specifies the bound data */
    void bind(Data data) {
        mData = data;
        onBind(mData);
    }

    /** * the subclass must implement the ** callback when binding data is triggered@paramData Specifies the bound data */
    protected abstract void onBind(Data data);

    /** * Holder updates Data **@paramData data data */
    public void updateData(Data data) {
        if(callback ! =null) {
            // Call the update callback
            callback.update(data, this); }}}Copy the code

Member variables

private Unbinder mUnbinder;
private AdapterCallback<Data> callback;

protected Data mData;
Copy the code

Our ViewHolder has three member variables, of which mUnbinder is the object that ButterKnife unbinds and callback is the callback that updates the data.

In turn, our ViewHolder provides external methods to update data

public void updateData(Data data) {
    if(callback ! =null) {
        // Call the update callback
        callback.update(data, this); }}Copy the code

A constructor

public BaseViewHolder(@NonNull View itemView) {
            super(itemView);
        }
Copy the code

Here, the parent class must contain a constructor for a View argument, so a constructor with arguments is implemented. A ViewHolder will host a View passed in from this constructor.

bind()

As mentioned earlier, the ViewHolder needs to bind to the specific data. In our wrapper, we provide a bind() method to bind the View to the data.

/** * used to bind data trigger **@paramData Specifies the bound data */
void bind(Data data) {
    mData = data;
    onBind(mData);
}
Copy the code

First, we ViewHolder holds a reference to the data, which is set when the data is bound. Second, we provide an onBind() interface, which is implemented by subclasses to perform data binding operations on the View, such as updating images and text in the View.

Inherit from RecyclerView.Adapter method

From the above analysis, we know that RecyclerAdapter needs to implement several key methods to fulfill its role of “administrator” of warehouse display:

  • getItemViewType()
  • onCreateViewHolder()
  • onBindViewHolder()
  • getItemCount()

getItemViewType()

We further encapsulate getItemViewType() here, providing a new interface:

/** * returns View type **@paramPosition item coordinates *@returnThe layout resource file id */ is returned
@Override
public int getItemViewType(int position) {
    // Call the interface to get the layout resource ID
    return getItemViewType(position, mDataList.get(position));
}

/** * subclass interface, subclass must implement, return layout resource file id **@paramThe position coordinates *@paramData Indicates the current data *@returnXML file resource ID used to create ViewHolder */
@LayoutRes
protected abstract int getItemViewType(int position, Data data);
Copy the code

In this case, itemViewType specifies the type of the View, and for simplicity, the encapsulation convention is that our ViewType is the ID of our XML layout file.

onCreateViewHolder()

As we know from the previous analysis, this step is how we create a ViewHolder.

/** * To create a BaseViewHolder, you can create a different BaseViewHolder depending on the ViewType@param parent   RecyclerView
 * @paramViewType The View type to be generated *@returnReturn a BaseViewHolder */
@NonNull
@Override
public BaseViewHolder<Data> onCreateViewHolder(@NonNull ViewGroup parent, @LayoutRes int viewType) {
    // Get LayoutInflater, Context passed to parent.getContext()
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    // Generate a View with a layout ID of viewType
    View root = inflater.inflate(viewType, parent, false);
    // Call the subclass interface generation, complete the user onCreatedViewHolder operation, create ViewHolder
    BaseViewHolder<Data> holder = onCreateViewHolder(root, viewType);

    // Bind the view to BaseViewHolder
    root.setTag(R.id.tag_recycler_holder, holder);

    / / bind ButterKnife
    holder.mUnbinder = ButterKnife.bind(holder, root);
    / / bind callback
    holder.callback = this;

    // Set click event listening
    root.setOnClickListener(this);
    root.setOnLongClickListener(this);

    return holder;
}

/** * is used to generate BaseViewHolder. Subclasses must implement BaseViewHolder@paramRoot parent is RecyclerView@paramViewType Specifies the viewType as resource ID *@returnReturns the BaseViewHolder */ created
protected abstract BaseViewHolder<Data> onCreateViewHolder(View root, @LayoutRes int viewType);
Copy the code

Our ViewHolder needs to hold a View, so we generate a View via a LayoutInflater. According to the convention above, the layout file of the View comes from the viewType.

We then call the interface we built onCreateViewHolder() to generate the ViewHolder.

We then bind the view to the ViewHolder using a Tag, allowing us to retrieve the ViewHolder using view.getTag().

The ButterKnife is then bound.

Then bind the holder’s callback as the current Adapter, so that we only need to inherit the BaseRecyclerAdapter and implement the corresponding method of the callback.

Then we set the view’s click event. For the click event, the view first gets the click event, and then passes it to the Adapter, which processes the click event.

/** * overrides onClick, passing the item onClick event to subclass ** via the AdapterListener interface@param view
 */
@Override
public void onClick(View view) {
    // This is the function of the previous tag, which is used to fetch the ViewHolder from the view
    BaseViewHolder viewHolder = (BaseViewHolder) view.getTag(R.id.tag_recycler_holder);
    if (this.mListener ! =null) {
        intpos = viewHolder.getAdapterPosition(); mListener.onItemClick(viewHolder, mDataList.get(pos)); }}/** * overwrites onLongClick, passing the item onLongClick event to subclass ** via the AdapterListener interface@param view
 */
@Override
public boolean onLongClick(View view) {
    BaseViewHolder viewHolder = (BaseViewHolder) view.getTag(R.id.tag_recycler_holder);
    if (this.mListener ! =null) {
        int pos = viewHolder.getAdapterPosition();
        mListener.onItemLongClick(viewHolder, mDataList.get(pos));
        return true;
    }
    return false;
}
Copy the code

Increases the deletion

For a list, the Adapter is its administrator, and we should use the Adapter to manage changes to our data, so we can encapsulate methods that add, delete, and change.

/** * Add data **@paramThe element added to data */
public void add(Data data) {
    mDataList.add(data);
    // Notify data update
    notifyItemInserted(mDataList.size() - 1);
}

/** * Insert multiple data and update **@paramDataList insert data */
public void add(Data... dataList) {
    if (Objects.requireNonNull(dataList, DATA_LIST_ERROR_MESSAGE).length > 0) {
        int start = mDataList.size();
        Collections.addAll(mDataList, dataList);
        // Notifies updates with the start position and number of insertsnotifyItemRangeChanged(start, dataList.length); }}/** * Insert multiple data and update **@paramDataList insert data */
public void add(Collection<Data> dataList) {
    if (Objects.requireNonNull(dataList, DATA_LIST_ERROR_MESSAGE).size() > 0) {
        intstart = mDataList.size(); mDataList.addAll(dataList); notifyItemRangeChanged(start, dataList.size()); }}/** * empty */
public void clear(a) {
    mDataList.clear();
    notifyDataSetChanged();
}

/** * replace the original collection with the new collection **@paramDataList replaces the collection */
public void replace(Collection<Data> dataList) {
    mDataList.clear();
    mDataList.addAll(Objects.requireNonNull(dataList, DATA_LIST_ERROR_MESSAGE));
    notifyDataSetChanged();
}
Copy the code

We will not go into details here. It should be noted that after updating the data, we need to call the corresponding notify method to notify the Adapter that the corresponding data has been updated, so as to update our interface display in time.

At this point, the packaging of RecyclerView.Adapter is introduced. If there is an update in the future, this article will be updated in time.