0 x00 overview

Core idea: It’s all about a series of encapsulation around how to solve the viewType, view, data and view binding, and different libraries have their own approach

Adapter can be in the data change, the internal implementation logic will not change, only add new functions in the external, then requires Adapter to the data layer is decoupled, can not display the external data, the core points to grasp the three core API RecyclerView

  • GetItemViewType: An int value returned based on position representing the ViewHolder type of the position. This method is usually overwritten in multiple holder types. 0 is returned by default
  • OnCreateViewHolder: Creates a Viewholder corresponding to the ViewType
  • OnBinderViewHolder: Bind data to the corresponding ViewHolder

How to extract reusable code to reduce ease of use by developers.

The bottom line

  1. Base scenarios reduce redundant code
  2. Multiple types of processing
  3. Pull-down refresh, pull-up load, and animation customization

The extension point

  1. Item animation, item horizontal sliding gesture processing
  2. Multistage folding

This paper is mainly through the analysis of two open source libraries to complete the above learning experience, the full text of the catalog as follows

0x01 AdapterDelegates

  • Project address github.com/sockeqwe/Ad…

  • Solve the problem: “Favor Composition over Inheritance” for RecyclerView Adapters “, solve a variety of types of problems

Design idea

The main APIonBindViewHolder and onCreateViewHolder and getItemViewType methods are hijacked and implemented using a Manager class inside adpter. The name is “Delagates- proxy”, so the key to analyzing this library is to look at the corresponding API behind the proxy

The class diagram

AbsDelegationAdapter: An implementation of the usual API that holds the AdapterDelegatesManager internally to hookRecyclerViewAdapter

AdapterDelegatesManager: Implement binding between RecyclerAdapter and Adapter, especially mapping between viewType and corresponding delegate (core class)

AdapterDelegate: The abstract base classes for each type include UI and business logic (the core classes), which are onCreateViewHolder and onBindViewHolder

AbsListItemAdapterDelegate: reduce redundant code, mainly through the generics to avoid type conversion (extension)

AbsFallbackAdapterDelegate: type the default implementation, to prevent the collapse of unknown type (extension)

Core point analysis

1. How to solve multiple types

And just to illustrate, the three core apis, The AdapterDelegate is responsible for creating the Viewholder and binding data to the Viewholder (the core is onCreateViewHolder and onBindViewHolder). The dispatch of each AdapterDelegate is done by the AdapterDeleg AtesManager getItemViewType to get; The mapping between the business AdapterDelegate and the corresponding viewType is established when the AdapterDelegatesManager#addDelegate is called.

See the demo for a sense of how to use it

public class ReptilesAdapter extends ListDelegationAdapter<List<DisplayableItem>> { public ReptilesAdapter(Activity activity, List<DisplayableItem> items) { // Delegates this.delegatesManager.addDelegate(new GeckoAdapterDelegate(activity)); / / gecko enclosing delegatesManager. AddDelegate (new SnakeListItemAdapterDelegate (activity)); / / snake / / setFallbackDelegate when there is no corresponding type is used to set the default display item enclosing delegatesManager. SetFallbackDelegate (new ReptilesFallbackDelegate(activity)); setItems(items); }}Copy the code

You can see from the AdapterDelegatesManager mentioned above

  • AddDelegate: Build SparseArrayCompat

0x02 BaseRecyclerViewAdapterHelper

Github.com/CymChad/Bas…

Function is introduced

Introduction to the

BRVAH is a powerful RecyclerAdapter framework that can save developers a lot of development time. It integrates most of the common list requirements solutions (multiple types, pull-up load, pull-down refresh, item drag). It has been more than a year since the first version of the framework was released on April 10, 2016, with more than 800 code submissions, more than 140 version packages, more than 1,000 issues fixed, 10,000 + STAR and a professional website, www.recyclerview.org/

Design idea

Basically, the BaseQuickAdapter and BaseViewHolder are constructed as the base

The BaseViewHolder contains a pre-built view Id, thus introducing a series of set methods such as setText, setImageResource, setBackgroundColor, and setTextColor

BaseQuickAdapter encapsulates three core apis. The core idea is to find duplicate code, extract it to the base class, and replace the non-duplicate code with abstract methods

The class diagram

This is the main class diagram BaseRecyclerViewAdapterHelper library, it can be seen

(1) BaseQuickAdapter realized the universal abstract method of RecyclerView.Adapter

(2) BaseQuickAdapter two generics

Analysis Point 1: Core API encapsulation

A lot of wrapping is done in the recycler. Adapter basequickAdapter. Java class, which is derived from recycler. Adapter, so the three core apis are necessary.

  • GetItemViewType: An int value returned based on position, representing the ViewHolder type of the position
  • OnCreateViewHolder: Creates a Viewholder corresponding to the ViewType
  • OnBinderViewHolder: Bind data to the corresponding ViewHolder

What does the core Adapter do with these three apis from which the rest of the work starts

BaseQuickAdapter#getItemViewType

Gets the Type used to create the View

@override public int getItemViewType(int position) {if (getEmptyViewCount() == 1) { } int numHeaders = getHeaderLayoutCount(); if (position < numHeaders) { return HEADER_VIEW; } else { int adjPosition = position - numHeaders; int adapterCount = mData.size(); if (adjPosition < adapterCount) { return getDefItemViewType(adjPosition); // create viewType}... }}Copy the code

Further down the core processing, create the viewType

protected int getDefItemViewType(int position) { if (mMultiTypeDelegate ! = null) { return mMultiTypeDelegate.getDefItemViewType(mData, position); } return super.getitemViewType (position); // Return 0, that is, normal single type return 0}Copy the code

From here we can see what type of viewHolder should be returned in onCreateViewHolder based on data’s index value and whether or not we have an empty view open or not.

GetDefItemViewType is called when there is neither a header view, a tail view, an empty view, or an in-load view.

Core function call chain getItemViewType->getDefItemViewType

Now that we have the viewType the next step is how do we create the corresponding viewHolder

BaseQuickAdapter#onCreateViewHolde

// The core is in default, the other several cases are to create several special views, @override public K onCreateViewHolder(ViewGroup parent, int viewType) {K baseViewHolder = null; this.mContext = parent.getContext(); this.mLayoutInflater = LayoutInflater.from(mContext); switch (viewType) { case LOADING_VIEW: baseViewHolder = getLoadingView(parent); break; case HEADER_VIEW: baseViewHolder = createBaseViewHolder(mHeaderLayout); break; case EMPTY_VIEW: baseViewHolder = createBaseViewHolder(mEmptyLayout); break; case FOOTER_VIEW: baseViewHolder = createBaseViewHolder(mFooterLayout); break; Default: // Note that viewType = 0 baseViewHolder = onCreateDefViewHolder(parent, viewType); // Create viewHolder bindViewClickListener(baseViewHolder) for the content Item; / / encapsulation listening, mainly is the click and longClick} baseViewHolder setAdapter (this); //1. Get the location of the click, where the location is after removing the headLayoutCount, need Adapter 2. ChildView: baseViewHolder; ChildView: baseViewHolder }Copy the code

You can see that the viewHolder that creates the Item is actually handed over to the function onCreateDefViewHolder as follows

onCreateDefViewHolder->createBaseViewHolder

protected K onCreateDefViewHolder(ViewGroup parent, int viewType) { int layoutId = mLayoutResId; // The constructor passes BaseQuickAdapter(@layoutres int layoutResId, @Nullable List<T> data) if (mMultiTypeDelegate! = null) {/ / used when multiple types type layoutId = mMultiTypeDelegate. GetLayoutId (viewType); } return createBaseViewHolder(parent, layoutId); } protected K createBaseViewHolder(ViewGroup parent, int layoutResId) { return createBaseViewHolder(getItemView(layoutResId, parent)); } protected K createBaseViewHolder(View view) { Class temp = getClass(); Class z = null; while (z == null && null ! = temp) {/ / check whether generic use the generic z = getInstancedGenericKClass adapter (temp); temp = temp.getSuperclass(); } K k; If (z == null) {if (z == null) {// Create a basic return k = (k) new BaseViewHolder(view); } else {// Derive ViewHolder k = createGenericKInstance(z, view); } return k ! = null ? k : (K) new BaseViewHolder(view); }Copy the code

At this point, you can see that reflection + reflection is actually left to create an instance of viewholder. Following up on how generics + reflection is created, it feels like a good example code to learn about generics + reflection.

@SuppressWarnings("unchecked") private K createGenericKInstance(Class z, View view) { try { Constructor constructor; // inner and unstatic class if (z.isMemberClass() && ! Modifier.isStatic(z.getModifiers())) { constructor = z.getDeclaredConstructor(getClass(), View.class); constructor.setAccessible(true); return (K) constructor.newInstance(this, view); } else { constructor = z.getDeclaredConstructor(View.class); constructor.setAccessible(true); return (K) constructor.newInstance(view); }}... return null; } private Class getInstancedGenericKClass(Class z) { Type type = z.getGenericSuperclass(); If (type instanceof ParameterizedType) {ParameterizedType = ParameterizedType; [] types = ((ParameterizedType) Type).getActualTypeArguments(); // If generics are supported, return an array of Type objects indicating that the Type is a Type parameter (get the actual parameter Type in the parameterized Type) for (Type temp: types) { if (temp instanceof Class) { Class tempClass = (Class) temp; / / BaseViewHolder. Class. TempClass parent or both if same (BaseViewHolder. Class. IsAssignableFrom (tempClass)) {/ / validation is BaseViewHodler  return tempClass; } } } } return null; }Copy the code

To recap the process of creating a viewholder:

Create a viewHolder using generic reflection. Create a viewHolder to determine if the hodler is derived from the BaseViewHodler. Then extract the type of the specific holder and create it.

Function call chain onCreateViewHolder->onCreateDefViewHolder->createGenericKInstance

Now that the viewHolder is in place, here’s the binding data

BaseQuickAdapter#onBindViewHolder

@Override public void onBindViewHolder(K holder, int position) { //Add up fetch logic, almost like load more, but simpler. autoUpFetch(position); //Do not move position, need to change before LoadMoreView binding autoLoadMore(position); int viewType = holder.getItemViewType(); Switch (viewType) {//viewType = 0 is the data we normally use, see onCreateViewHolder default case 0: convert(holder, getItem(position - getHeaderLayoutCount())); break; case LOADING_VIEW: mLoadMoreView.convert(holder); break; ... default: convert(holder, getItem(position - getHeaderLayoutCount())); break; Public T getItem(@intrange (from = 0) int position) {if (position < mdata.size ()) return mData.get(position); else return null; } // API protected abstract void convert(K helper, T item);Copy the code

Function call chain onBindViewHolder -> convert

ViewType = 0 is the viewholder for normal data, and the holder and its bound data (mdata.get (position)) are passed into the convert function so that the holder and data are available for the user to unbind.

  • getItemViewType -> getDefItemViewType
  • onCreateViewHolder -> onCreateDefViewHolder->createGenericKInstance
  • onBindViewHolder -> convert
Analysis point 2: Multi-layout MultiType handling

Let me remind you of the core API

  • getItemViewType

Also can see a thing or two from the above analysis, in getViewType call getDefItemViewType, involves mMultiTypeDelegate this concept, focus on BaseMultiItemQuickAdapter, realize the Type, Using the Adapter is no longer directly inherited from BaseQuickAdapter class, it inherits from its subclasses BaseMultiItemQuickAdapter, and data sources to achieve MultiItemEntity interface

public interface MultiItemEntity {
    int getItemType();
}
Copy the code

Intention is very simple, yes every itemd corresponding data source bring own ViewType, look at the below BaseMultiItemQuickAdapter,

public abstract class BaseMultiItemQuickAdapter<T extends MultiItemEntity, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {... public BaseMultiItemQuickAdapter(List<T> data) { super(data); } // Copy the core API, Override protected int getDefItemViewType(int position) {Object item = mdata.get (position); if (item instanceof MultiItemEntity) { return ((MultiItemEntity) item).getItemType(); } return DEFAULT_VIEW_TYPE; }... @Override protected K onCreateDefViewHolder(ViewGroup parent, Int viewType) {// Core API create corresponding ViewHodlder return createBaseViewHolder(parent, getLayoutId(viewType)); }... // Add the layout file of ViewType to the collection. @LayoutRes int layoutResId) { if (layouts == null) { layouts = new SparseIntArray(); } layouts.put(type, layoutResId); }}Copy the code

ViewType returns, viewHolder rendering, and finally the convert to a user to perform data binding, use derived from BaseMultiItemQuickAdapter.

Analysis point 3: pull-up loading & preloading

BaseQuickAdapter.java

To load more

Think about:

  • Load more customizable
  • A bunch of callbacks, how do you call them?
  • How to implement load more (UI updates, load more Item inserts)?

Load more layouts can be customized, injected, otherwise use the default configuration. LoadMoreView is an abstract class that supports derived customization

The implementation first loads more listening interfaces and then injects them into the BaseQuickAdapter

public interface RequestLoadMoreListener {

    void onLoadMoreRequested();

}

public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener, RecyclerView recyclerView) {
    openLoadMore(requestLoadMoreListener);
    if (getRecyclerView() == null) {
        setRecyclerView(recyclerView);
    }
}

private void openLoadMore(RequestLoadMoreListener requestLoadMoreListener) {
    this.mRequestLoadMoreListener = requestLoadMoreListener;
    mNextLoadEnable = true;
    mLoadMoreEnable = true;
    mLoading = false;
}
Copy the code

The function chain is called in the framework layer BaseQuickAdapter as follows

OnBindViewHolder – > autoLoadMore – > (the callback) mRequestLoadMoreListener. OnLoadMoreRequested (), so as to complete closed loop, the corresponding source code is as follows

@Override public void onBindViewHolder(K holder, int position) { //Add up fetch logic, almost like load more, but simpler. autoUpFetch(position); //Do not move position, need to change before LoadMoreView binding autoLoadMore(position); int viewType = holder.getItemViewType(); ... } private void autoLoadMore(int position) {...... mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING); if (! mLoading) { mLoading = true; if (getRecyclerView() ! = null) { getRecyclerView().post(new Runnable() { @Override public void run() { mRequestLoadMoreListener.onLoadMoreRequested(); }}); } else { mRequestLoadMoreListener.onLoadMoreRequested(); }}}Copy the code

Then, while the business call implements this callback, the UI updates the data using different function functions provided by different BasequickAdapters depending on the load

  • Baseadapter. loadMoreEnd(Boolean gone) // No more is displayed

  • BaseAdapter. LoadMoreComplete () / / dropdown refresh request successfully closed loading

  • Baseadapter.loadmorefail () // The drop-down refresh request failed to load

Set loadMoreView update status to refresh the UI

public void loadMoreEnd(boolean gone) { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; mNextLoadEnable = false; mLoadMoreView.setLoadMoreEndGone(gone); if (gone) { notifyItemRemoved(getLoadMoreViewPosition()); } else { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END); notifyItemChanged(getLoadMoreViewPosition()); }}... }Copy the code

The overall idea is to have, let’s look at the specific operation

The core of LoadMoreView is to write all 3 states in XML file according to Status Visible/Gone.

So after we’ve looked at the core functionality of the load section, and then this load more when did this Item get inserted into the Item?

When using load more there is a switch that controls whether to use load more,

public void setEnableLoadMore(boolean enable) { int oldLoadMoreCount = getLoadMoreViewCount(); mLoadMoreEnable = enable; int newLoadMoreCount = getLoadMoreViewCount(); If (oldLoadMoreCount == 1) {if (newLoadMoreCount == 0) {// Remove load more notifyItemRemoved(getLoadMoreViewPosition()); }} else {the if (newLoadMoreCount = = 1) {/ / need to load more mLoadMoreView. SetLoadMoreStatus (LoadMoreView. STATUS_DEFAULT); notifyItemInserted(getLoadMoreViewPosition()); Public int getLoadMoreViewPosition() {return getHeaderLayoutCount() + mdata.size () + getFooterLayoutCount(); }Copy the code

You can see the mystery in this switch ~

Analysis Point 4: Pull down refresh

thinking

  • Integration into libraries or support for pull-down live

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </android.support.v4.widget.SwipeRefreshLayout>

Copy the code

Cons: Loses some of the custom animations, but still conforms to Google’s specifications

Analysis Point 5: Multi-level folding

Think about:

  • How to fold?
  • Folding hierarchy?

In fact, we can use recyclerView. Adapter to provide us with the following notification data source update method to achieve our dynamic extension and folding function.

  @see #notifyItemChanged(int)
  @see #notifyItemInserted(int)
  @see #notifyItemRemoved(int)
  @see #notifyItemRangeChanged(int, int)
  @see #notifyItemRangeInserted(int, int)
  @see #notifyItemRangeRemoved(int, int)
Copy the code

When stretching, we dynamically add the next level of item’s data to the data set bound to the Adapter, and then tell layoutManager to update the data source. When it is time to shrink, the data source for the next level of item is removed from the data set bound to adapter and then refreshed.

Ideas:

  1. Data beans should have fields that store their own data
  2. The data bean should have collection-type fields that store the next-level LITEM list
  3. The data bean should have a field that identifies the current item state (scale or expand)
  4. Only top-level items are rendered when the Adapter is initialized
  5. Support scaling: Click current status byExpand -> CollapseInsert the secondary list into the data collection bound by adapter and refresh the data. The current state is determined byFold -> Expand(Remove the secondary list from the data set bound to adapter and refresh the data)
  6. The position of insertion or removal is confirmed according to the clicked item, and the amount of insertion and removal is determined according to the number of items at the next level
  7. The insert and remove process can be animated

Implement Exandable and Collapse effect we are still use BaseMultiItemQuickAdapter implementation, because its essence is a type of adapter, then we need to look at two related classes

  • IExapandable interface
  • The AbstractExpandableItem abstract class implements the IExpandable interface: a re-encapsulation of a data bean

    public interface IExpandable<T> { boolean isExpanded(); // Whether the current bean expands void setExpanded(Boolean expanded); List<T> getSubItems(); Int getLevel(); // Return the level of the current item, the first level}Copy the code

The Item to be expanded inherits AbstractExpandableItem< generics of next-level Item >, and the last level does not need to specify the generics of next-level Item

public class Level0Item extends AbstractExpandableItem<Level1Item> implements MultiItemEntity { public String title; public String subTitle; public Level0Item( String title, String subTitle) { this.subTitle = subTitle; this.title = title; } @Override public int getItemType() { return ExpandableItemAdapter.TYPE_LEVEL_0; } @Override public int getLevel() { return 0; }}Copy the code

Look at an example of building data to help you understand

private ArrayList<MultiItemEntity> generateData() {
    int lv0Count = 9;
    int lv1Count = 3;
    int personCount = 5;

    String[] nameList = {"Bob", "Andy", "Lily", "Brown", "Bruce"};
    Random random = new Random();

    ArrayList<MultiItemEntity> res = new ArrayList<>();
    for (int i = 0; i < lv0Count; i++) {
        Level0Item lv0 = new Level0Item("This is " + i + "th item in Level 0", "subtitle of " + i);
        for (int j = 0; j < lv1Count; j++) {
            Level1Item lv1 = new Level1Item("Level 1 item: " + j, "(no animation)");
            for (int k = 0; k < personCount; k++) {
                lv1.addSubItem(new Person(nameList[k], random.nextInt(40)));
            }
            lv0.addSubItem(lv1);
        }
        res.add(lv0);
    }
    return res;
}
Copy the code

Now that we have the data ready, let’s go into the details how do we unfold and fold

The first step in the ExandableItemAdapter#convert method is to set the click-collapse logic in addition to completing the data binding

BaseQuickAdapter#collapse BaseQuickAdapter#expand

Collapse ->recursiveCollapse,expand->recursiveExpand

Public int expand(@intrange (from = 0) int position, Boolean animate, boolean shouldNotify) { position -= getHeaderLayoutCount(); IExpandable expandable = getExpandableItem(position); If (expandable == null) {return 0; } if (! hasSubItems(expandable)) { expandable.setExpanded(false); return 0; } int subItemCount = 0; if (! expandable.isExpanded()) { List list = expandable.getSubItems(); mData.addAll(position + 1, list); subItemCount += recursiveExpand(position + 1, list); // Get the number to expand expandable.setExpanded(true); // subItemCount += list.size(); }... return subItemCount; } private int recursiveExpand(int position, @NonNull List list) { int count = 0; int pos = position + list.size() - 1; for (int i = list.size() - 1; i >= 0; i--, pos--) { if (list.get(i) instanceof IExpandable) { IExpandable item = (IExpandable) list.get(i); if (item.isExpanded() && hasSubItems(item)) { List subList = item.getSubItems(); // Get subitem mdata. addAll(pos + 1, subList); Int subItemCount = recursiveExpand(pos + 1, subList); Count += subItemCount; } } } return count; // Finally returns the number to expand}Copy the code

Summary: Collapse -> Expand: mData adds the data set to be expanded and updates the data source; Expand -> Collapse: mData removes the dataset to be collapsed and updates the data source.

Analysis point 6: Click Related

Think about:

  • Where is it implemented, Holder?
  • Click handling of item and its internal child controls

This point is actually consistent with the pull-load analysis, listening for callbacks

In addition to the normal item click and tap, there’s childItem tap and tap,

Introducing the concept of childItem. For example, the entire click on each Item is the usual ItemClick, and the click on the component inside, such as each button or inner component, is called childItem.

Let’s look at it at the code level

First look at the click interface in the BaseQuickAdapter

Public interface OnItemClickListener {void onItemClick(BaseQuickAdapter adapter, View View, int position); } public interface OnItemLongClickListener {Boolean onItemLongClick(BaseQuickAdapter adapter View View, int position); Public interface OnItemChildClickListener {void onItemChildClick(BaseQuickAdapter adapter View View) int position); } / / ChildItem long press public interface OnItemChildLongClickListener {Boolean onItemChildLongClick (BaseQuickAdapter adapter, View view, int position); }Copy the code

The old routine was for the business side to implement the four interfaces as needed and then call them in the framework BaseQuickAdapter and BaseViewHolder

Create an onCreateViewHolder holder of the corresponding type in the BaseQuickAdapter. When a normal business holder is created, the Hodler is created and the click event for that holder is immediately processed

@Override public K onCreateViewHolder(ViewGroup parent, int viewType) { K baseViewHolder = null; this.mContext = parent.getContext(); this.mLayoutInflater = LayoutInflater.from(mContext); The switch (viewType) {... default: baseViewHolder = onCreateDefViewHolder(parent, viewType); bindViewClickListener(baseViewHolder); }... } private void bindViewClickListener(final BaseViewHolder BaseViewHolder) {...... If (getOnItemClickListener()!) if (getOnItemClickListener()! = null) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getOnItemClickListener().onItemClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount()); }}); } / / connect the bridge ItemLongClick if (getOnItemLongClickListener ()! = null) { view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { return getOnItemLongClickListener().onItemLongClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount()); }}); }}Copy the code

Okay, it handles the click and hold on item, it doesn’t handle the click on ItemChild, because it can’t handle that either, because it doesn’t know which child control needs to respond to the click, so let’s move on, how does it inject in the business class

@override protected void onCreate(Bundle savedInstanceState) {... adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemClick: "); Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show(); }}); adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() { @Override public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemLongClick: "); Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show(); return true; }}); adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { @Override public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemChildClick: "); Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show(); }}); adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() { @Override public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemChildLongClick: "); Toast.makeText(ItemClickActivity.this, "onItemChildLongClick" + position, Toast.LENGTH_SHORT).show(); return true; }});Copy the code

Inject the specific response callbacks for the four click events we need. Now look at ItemClickAdapter#convert, bind the specific Id, and go to action

@Override
protected void convert(final BaseViewHolder helper, final ClickEntity item) {
    switch (helper.getItemViewType()) {
        case ClickEntity.CLICK_ITEM_VIEW:
            helper.addOnClickListener(R.id.btn);
            break;
        case ClickEntity.CLICK_ITEM_CHILD_VIEW:
            helper.addOnClickListener(R.id.iv_num_reduce).addOnClickListener(R.id.iv_num_add)
                    .addOnLongClickListener(R.id.iv_num_reduce).addOnLongClickListener(R.id.iv_num_add);
            // set img data
            break;
        case ClickEntity.LONG_CLICK_ITEM_VIEW:
            helper.addOnLongClickListener(R.id.btn);
            break;
        case ClickEntity.LONG_CLICK_ITEM_CHILD_VIEW:
            helper.addOnLongClickListener(R.id.iv_num_reduce).addOnLongClickListener(R.id.iv_num_add)
                    .addOnClickListener(R.id.iv_num_reduce).addOnClickListener(R.id.iv_num_add);
            break;
        ……
    }
}
Copy the code

Focus on BaseViewHodler#addOnClickListener and BaseViewHodler#addOnLongClickListener. Here, addOnLongClickListener is used as an example

public BaseViewHolder addOnClickListener(@IdRes final int viewId) { childClickViewIds.add(viewId); final View view = getView(viewId); if (view ! = null) { if (! view.isClickable()) { view.setClickable(true); } @override public void setOnClickListener(new view.onClickListener () {Override public void setOnClickListener() onClick(View v) { if (adapter.getOnItemChildClickListener() ! = null) { adapter.getOnItemChildClickListener().onItemChildClick(adapter, v, getClickPosition()); }}}); } return this; }Copy the code

The previous createViewholder had bound a normal callback, and the View added a ChildItem callback in the binderViewHodler->convert phase.

You can see the whole idea of handling the response in the click

  • You need to set the specifics of the click response from the action, and that goes to the business level, the Actvity
  • The wrapper layer provides the user to click inside an Item, the itemChild layer. The framework layer only clicks and presses the item
  • Calling addxxxListener not only binds the click of the control at the framework level, but also binds the response of childView

0 x03 summary

AdapterDelegate of checking

The custom Adapter hooks the original Adapter API, but the hook idea behind it is more commonly used, for example

  • Vlayout (the basic part of Alibaba open source Tangram, applied to tmall Taobao page dynamic) deeply Hook recyclerView layout calculation, creation and a large number of system level core API
  • Peels Hook AssetManager to load the resource path
  • Plug-in Hook system API, “cheat the top and hide the bottom, steal the pillar”

, etc.

BaseRecyclerViewHelper of checking

  • Application of SOLID principle
  • Good encapsulation
  • A combination of generics and reflection
  • Loadmoreview, click processing, collapse expansion processing ideas