preface

RecyclerView has come out for several years, its importance is self-evident. However, RecyclerView only provides the basic View reuse function, related functions such as refresh, click and so on need to be realized by the developers themselves, each project to achieve a RecyclerView function integration is not necessary, so there are many RecyclerView packaging “wheels”, Github is full of them.

Introduction to the

Although there are many wheels, each has its own characteristics. Sometimes or build your own best fit, OneRecyclerView this wheel features as follows:

  • With very little code (more than 300 lines of main Java code), RecyclerView integration implements most of its functions, including drop-down refresh, load more, multiple viewtypes, multi-column display, custom HeaderView and EmptyView for empty data
  • There’s a neat way to implement multiple viewTypes without complicated mappings, without registering types, without reflection
  • A single line of code can be called, as can multiple viewTypes

rendering


orv_base.gif




orv_types.gif




orv_columns.gif




orv_empty.gif

Method of use

  1. Add OneRecyclerView to the layout file
<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <cc.rome753.demo.onerecycler.OneRecyclerView android:id="@+id/orv" android:layout_width="match_parent" android:layout_height="match_parent"> </cc.rome753.demo.onerecycler.OneRecyclerView> </LinearLayout>Copy the code
  1. Implement a custom ViewHolder
class UserInfoVH extends OneVH<UserInfo> { public UserInfoVH(ViewGroup parent) {//1. Set the item layout file super(parent, r.layout.item_user_simple); } @Override public void bindView(int position, final UserInfo o) {//2. Click event and set data itemView. SetOnClickListener (new View. An OnClickListener () {@ Override public void onClick (View v) { Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show(); }}); TextView tvName = itemView.findViewById(R.id.tv_name); tvName.setText(o.getName()); }}Copy the code

Includes layout files for items, setting data for items, and click events

  1. A line of code using OneRecyclerView
mOneRecyclerView.init( new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { requestData(false); } }, new OneLoadingLayout.OnLoadMoreListener() { @Override public void onLoadMore() { requestData(true); } }, new OnCreateVHListener() { @Override public OneVH onCreateHolder(ViewGroup parent) { return new UserInfoVH(parent);  } @Override public boolean isCreate(int position, Object o) { return position % 3 > 0; }});Copy the code

Call OneRecyclerView’s init() method, pass in the drop-down refresh listener, load more listeners, and create ViewHolder listeners.

The last argument to the init() method of OneRecyclerView is a mutable argument for multiple itemTypes: implement multiple Viewholers wrapped in OnCreateVHListener and passed in

  1. Add custom headers
        View header = View.inflate(this, R.layout.layout_header, null);
        mOneRecyclerView.addHeader(header);
Copy the code

You can add multiple headers by calling the addHader() method multiple times, and the display of the headers is completely self-controlled

  1. Sets the number of columns to display in multiple columns
        mOneRecyclerView.setSpanCount(3);
Copy the code

No default is 1 column; Multi-column display and multiple viewtypes are generally not used at the same time. Choose one based on your specific requirements

The principle of analysis

The drop-down refresh

Use the official SwipeRefreshLayout

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/srl_wrapper"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_wrapper"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
Copy the code

Use SwipeRefreshLayout in the layout file, add RecyclerView inside, and then setOnRefreshListener to set the refresh listener

To load more

Give recyclerView. Adapter ItemCount + 1 and use a separate ViewType. A loading layout is displayed when you swipe to the bottom. Hide the layout after the data is retrieved

<? The XML version = "1.0" encoding = "utf-8"? > <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <ProgressBar android:layout_gravity="center" android:layout_width="30dp" android:layout_height="48dp" /> </FrameLayout>Copy the code

There is only one ProgressBar, which you can customize

ViewHolder encapsulation

For a generic library:

  1. The data type used by the external caller
  2. RecyclerView Item layout
  3. How Item loads data
  4. Item click event handling

The first is the need to use generics, that is, to define data types by the caller. The last three points can all be handled in the ViewHolder. This encapsulates an abstract class OneVH

inherited from recyclerView. ViewHolder, which is implemented by the caller

public abstract class OneVH<T> extends RecyclerView.ViewHolder {

    public OneVH(View itemView) {
        super(itemView);
    }

    public OneVH(ViewGroup parent, int layoutRes) {
        super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
    }

    public abstract void bindView(int position, T t);
}
Copy the code

A simple OneVH

implementation class is as follows

class TextVH extends OneVH<UserInfo> { public TextVH(ViewGroup parent) {//1. Set the item layout file super(parent, Android.r.layout.simple_list_item_1); } @Override public void bindView(int position, final UserInfo o) {//2. Click event and set data itemView. SetOnClickListener (new View. An OnClickListener () {@ Override public void onClick (View v) { Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show(); }}); TextView tvName = itemView.findViewById(android.R.id.text1); tvName.setText(o.getName()); tvName.setBackgroundColor(Color.GREEN); }}Copy the code

OnCreateVHListener encapsulation

Once a ViewHolder is wrapped, the object is not directly created and passed to the Adapter because the onCreateViewHolder method creates a ViewHolder when and how many times. So you need to define an interface OnCreateVHListener

public interface OnCreateVHListener<S extends OneVH, T>{/** * create ViewHolder * @param parent RecyclerView * @return S extends OneVH */ S onCreateHolder(ViewGroup parent); }Copy the code

When the Adapter needs to create a ViewHolder, the onCreateHolder method of OnCreateVHListener is called to return a custom OneVH implementation object

    @Override
    public S onCreateViewHolder(ViewGroup parent, int viewType) {
        ...
        return onCreateVHListener.onCreateHolder(parent);
    }
Copy the code

Because OnCreateVHListener can return a specific OneVH<T> implementation object, Adapter only relies on OnCreateVHListener, not OneVH. So the external caller just needs to pass an implementation class OneRecyclerView to the Adapter.


After the above steps, has been very convenient to use a single ViewType of RecyclerView. The caller simply implements his own ViewHolder and cares nothing more. The following describes the implementation of various viewTypes based on this

Implement multiple viewTypes

First, let’s look at how viewTypes work. The main Adapter methods associated with viewtypes are getItemViewType() and onCreateViewHolder()

    @Override
    public int getItemViewType(int position) {
    }

    @Override
    public S onCreateViewHolder(ViewGroup parent, int viewType) {
    }
Copy the code

The getItemViewType() method is used to determine the type of the item in the current position. The onCreateViewHolder() method returns an int representing the type viewType

For general thinking, two corresponding relationships need to be recorded here:

  1. Position Mapping between position and viewType
  2. Mapping between viewType and ViewHolder

That would require two maps to hold them, and maybe a class to manage them. Can I merge these two maps into one? Or no Map at all?

Consider the second correspondence. Since the ViewHolder type was previously bound to the OnCreateVHListener interface, different viewTypes correspond to different OnCreateVHListener objects. For example, if there are three Item types, then there are three OnCreateVHListener objects, and the three viewTypes correspond to three different int values.

In fact, Map is a kind of redundancy, 3 or more int values can be used 0,1,2… OnCreateVHListener

save OnCreateVHListener

save OnCreateVHListener


Then consider the first correspondence, obtain the viewType value according to position, based on the List

, viewType is the sequence number of OnCreateVHListener in the List. It is not easy to get this sequence number directly. Can we get OnCreateVHListener first and then iterate through List

to get its location?

It is also inconvenient to obtain OnCreateVHListener based on position. This correspondence is defined by the caller and needs to provide a natural way for outsiders to define it, rather than registering a type or defining a Manager class. Each OnCreateVHListener only needs to know if the corresponding position is itself.

This adds an isCreate(int Position, T T) method to the OnCreateVHListener interface. The isCreate(int Position, T T) method takes position and the corresponding position data. The caller uses these parameters to determine whether the position is the corresponding ViewHolder

public interface OnCreateVHListener<S extends OneVH, T>{/** * create ViewHolder * @param parent RecyclerView * @return S extends OneVH */ S onCreateHolder(ViewGroup parent); /** * ViewHolder * @param position * @param t * @return */ Boolean isCreate(int position, t t); }Copy the code

The Adapter getItemViewType method iterates through the List

and calls isCreate(). If true, the current sequence number is returned, which is the viewType


    @Override
    public int getItemViewType(int position) {
        ...
        int pos = position - headerVHList.size();
        T t = data.get(pos);

        for(int i = 0; i < listeners.size(); i++){
            OnCreateVHListener<S,T> listener = listeners.get(i);
            if(listener.isCreate(pos, t)){
                return i;
            }
        }
        return TYPE_NORMAL_MIN;
    }
Copy the code

Finally, position, viewType, ViewHolder, and OnCreateVHListener are all related. The cost is simply to add a List

to the Adapter (passed in at initialization) and a method to the OnCreateVHListener interface.

Although traversal is performed in the getItemViewType method, the performance impact is negligible considering that 99% of the list Item types are bits and determining the type is not a time-consuming operation

The two methods defined in OnCreateVHListener are associated not only with ViewHolder but also with position. External callers use several Item types and pass in a few OnCreateVHListener implementation classes. The actual use is as follows

mOneRecyclerView.init( new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { requestData(false); } }, new OneLoadingLayout.OnLoadMoreListener() { @Override public void onLoadMore() { requestData(true); } }, new OnCreateVHListener() { @Override public OneVH onCreateHolder(ViewGroup parent) { return new UserInfoVH(parent);  } @Override public boolean isCreate(int position, Object o) { return position % 3 > 0; } }, new OnCreateVHListener() { @Override public OneVH onCreateHolder(ViewGroup parent) { return new TextVH(parent); } @Override public boolean isCreate(int position, Object o) { return position % 3 == 0; }});Copy the code

Onerecyclerview.init () passes two oncreatevHListeners to the initialization method of OneRecyclerView, corresponding to two OneVH subclasses: UserInfoVH and TextVH. IsCreate () of the former returns true when position % 3 > 0, and isCreate() of the latter returns true when position % 3 == 0. So position 0,3,6,9… Display TextVH layout, position 1,2,4,5,7,8… Display the layout for UserInfoVH. This is an alternate display effect (as shown in figure 2 above, orv_types.gif).

conclusion

Having said that, the implementation code is not complicated, using only common inheritance, encapsulation, polymorphism, interfaces, abstract classes, generics, and data structures only for lists. On the one hand, the advanced technology of software design needs to be learned. On the other hand, yes, it is enough to create a simple RecyclerView framework with common functions. The realization of their own or study a code will RecyclerView principle and Java basic technology have a better understanding.

Welcome fork and Star to Github

Github.com/rome753/One…