background

In a blink of an eye, from the beginning of the release of the article said to take everyone encapsulation Adapter until now, the past half a month, before and after carefully read a lot of Adapter framework source code, on the kaleidoscope of Adapter is a profound understanding, really is to review the old to know the new, take this opportunity, I will also learn these advantages one by one list to tell you, with me to re-understand Adapter

Adapter open source project worth a look

  • mikepenz/FastAdapter
  • DevAhamed/MultiViewAdapter
  • davideas/FlexibleAdapter
  • sockeqwe/AdapterDelegates
  • liangjingkanji/BRV
  • evant/binding-collection-adapter
  • nitrico/LastAdapter
  • The author Adapter

What do these open source projects have in common?

  • The most common feature of these projects is that they all extend DataBinding. It seems that DataBinding is well accepted by everyone
  • There are 6 projects using Kotlin, the highest reached 96%, it seems that we Android friends love learning, Kotlin popularity is very high
  • There is also a nice extension to MultiItemType, which shows that it is indeed standard for an Adapter library
  • There are three projects that extend the Paging library, and I believe there will be more and more opportunities for everyone to use it
  • The head and tail layout has almost become the standard configuration of the frame
  • Kotlin DSL more or less support, writing concise and elegant
  • Most of them have done a better library processing, according to the need to rely on, not muddy water

How do we make an Adapter framework that can be called full and good?

Let me extract some key words to tell you

  • Appropriate subcontracting, as needed, to provide good scalability
  • Kotlin supports the use of a recent old iron saying: if you want to do a good job, you must first use a sharp tool, and Kotlin cliff is a good tool
  • DSL extensions are simple to write and easy to read
  • DataBinding support doesn’t have to be a problem
  • In my opinion Paging uses a lot of new designs that we should learn from
  • Head-to-tail layout, empty layout, pull-up, drag, animation provide specialized packages to extend
  • Don’t forget to use the DiffUtil extension tool, there is always a suitable application scenario
  • Anko extension with Kotlin how can you do without anko Layout, 300% layout loading efficiency is not good?

What was my insistence in designing such a framework?

Have you ever fallen into a mistake? They encapsulate a thing, special full, what all support, a rush of stuff into the inside, this person said I want this, you change to this, that person wants that, you start to change? Do we build things just to be told what to do? The answer must be: no. What aspects should be considered?

  • Extensibility Yes, you have to be extendable, otherwise you will face the dilemma of change east, change west, how to do extensibility? One principle is done: rely on the reverse principle, and try to rely on abstractions rather than implementations, which is not enough
  • Reliability can be understood as, you do not change things often, especially abstract interface, we need to be complete at the beginning, some people say, how can I consider that perfect? It is necessary to consider that you are not walking into a misunderstanding, the abstract interface in such a mature framework, should be very good to determine, the interface should not be large and complete, the interface should be as small as possible, at least the interface can be more inheritance ah, right?
  • The Richter substitution principle: wherever the base class can appear, the subclass must appear. This principle is actually not well understood by many people. In fact, to be frank, with practical examples, I tell you that you inherited a lot of genes from your father, can you change the genes of your father? Certainly not, but the code can be rewritten (abstract method is not well), but the principle is to tell us to avoid rewriting, once the rewritten, parent, there is no meaning, actually in the work, there are a lot of people like to abstract a lot of things in the parent class, love after different subclasses covered in rewriting, these are bad habits, You shouldn’t just use one thing for the third, you should add it to all the people. I feel it’s a drag, don’t you? It is important to follow these good design principles when designing the framework.
  • A class should have one and only one responsibility. Do you have the gall to encapsulate a class with all its functions? Unless it is called a Manager, it can manage only one kind of thing, and a single responsibility is visible and insisted upon everywhere.

Say these nonsense, you are not impatient, the next point dry goods, see Adapter some details are how to package

Adapter How to do ItemViewType automatic adaptation? How do we encapsulate?

ItemViewType affects the reuse of ViewHolder logic. If it’s the same ItemViewType, it triggers reuse. It’s actually caching the ViewHolder and then reuse it, so how do we do that automatically?

Since we’re going to move onCreateViewHolder down, and we’re abstracting the ViewModel layer to organize the View and the Model, then the ViewHolder becomes the carrier of our View, abstracting a ViewHolderFactory for the ViewModel to inherit, Calls to onCreateViewHolder are moved down

typealias GenericViewHolderFactory = ViewHolderFactory<out RecyclerView.ViewHolder>

interface ViewHolderFactory<VH : RecyclerView.ViewHolder> {
    fun getViewHolder(parent: ViewGroup, layoutInflater: LayoutInflater): VH
}
Copy the code

So let’s look at a couple of things, these are the two functions that we’ve implemented, onCreateViewHolder callback only gives us the viewType, so what we’re going to do is we’re going to cache a ViewHolderFactory with a Map, So you can get the corresponding ViewHolderFactory based on the viewType, isn’t that perfect?

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)

override fun getItemViewType(position: Int)
Copy the code

Cache ViewHolderFactory code as follows, the first step I abstract a ViewHolderFactoryCache interface first, ensure future extensibility, and the default implementation of a DefaultViewHolderFactoryCache, provide registration, obtain and check, cleaning methods

interface ViewHolderFactoryCache<VHF : GenericViewHolderFactory> {
    fun register(type: Int, item: VHF): Boolean
    operator fun get(type: Int): VHF
    fun contains(type: Int): Boolean
    fun clear()
}

class DefaultViewHolderFactoryCache<VHF : GenericViewHolderFactory> : ViewHolderFactoryCache<VHF> {
    private val typeInstances = SparseArray<VHF>()
    override fun register(type: Int, item: VHF): Boolean {
        if (typeInstances.indexOfKey(type) < 0) {
            typeInstances.put(type, item)
            return true
        }
        return false
    }

    override fun get(type: Int): VHF {
        return typeInstances.get(type)
    }

    override fun contains(type: Int) = typeInstances.indexOfKey(type) >= 0
    override fun clear() {
        typeInstances.clear()
    }
}
Copy the code

And then I’m going to implement it in Adapter, so I’m going to make the ViewModel implement the ViewHolderFactory, cache it, and then I’m going to take it out of onCreateViewHolder and assign it to it.

private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache<ViewHolderFactory<VH>>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val defaultViewHolder = defaultViewHolderFactoryCache[viewType].getViewHolder(parent, sparseArray.get(0) ? : LayoutInflater.from(parent.context)) defaultViewHolder.itemView.setTag(R.id.list_adapter, this)returndefaultViewHolder } override fun getItemViewType(position: Int): Int { val item = getItem(position) ? :return 0
        val type = item.itemViewType
        if(! defaultViewHolderFactoryCache.contains(type)) {
            item as ViewHolderFactory<VH>
            defaultViewHolderFactoryCache.register(type, item)
        }
        return type
    }
    
    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        defaultViewHolderFactoryCache.clear()
        sparseArray.clear()
    }
Copy the code

Clean up after onDetachedFromRecyclerView cached data. There’s one detail I didn’t see here, so let’s see

interface ViewModel<M, VH : RecyclerView.ViewHolder, Adapter:IAdapter<*>> :
    ViewHolderFactory<VH> {
    var model: M?
    var adapter: Adapter?
    val itemViewType: Int
        get() = layoutRes

    @get:LayoutRes
    val layoutRes: Int
    fun bindVH(
        viewHolder: VH,
        model: M,
        payloads: List<Any>
    )

    fun unBindVH(viewHolder: VH)
}
Copy the code

So here’s the ViewModel, which is actually an interface, but in Kotlin you can implement interfaces, so I’m making the default itemViewType to be layoutRes, and I’m doing it abstractly, which is perfectly reasonable, A different layout is a different ViewHolder, and since it is the same layout, the ViewHolder can be the same. That’s right.

How do you do a generic ViewModel, how do you implement DSLS?

Generic ViewHolder needless to say, in the previous blog has been analyzed, want to see please turn a senior Android should learn to do their own a super recyclerview. Adapter, here is the initial package to achieve, the bottom I did the optimization, Just looking at the ViewHolder for a moment, what should the ViewModel do? Let’s take a look at the ViewModel interface above and analyze its responsibilities as follows

  • Inherits the ViewHolderFactory, which is responsible for getViewHolder
  • BindVH is responsible for binding the data
  • UnBindVH Triggers when unbinding
  • LayoutRes are responsible for layout references
  • By default, itemViewType is the application ID of the layout
  • Holds Model data, which can be of any type
  • The adapter must be inherited from the IAdapter

The most important thing for the consumer to do is to bind the business Model to the ViewModel and accept the bindVH callback to implement the binding. After configuring a layoutRes, let’s look directly at the inheritance implementation

typealias  DefaultViewModelType <M, Adapter> = ViewModel<M, DefaultViewHolder, Adapter>

abstract class DefaultItemViewModel<M, A : IAdapter<*>> : DefaultViewModelType<M, A> {

    override var adapter: A? = null
    override var model: M? = null
    private var bindView: BindView? = null
    private var bindViewPayload: BindViewPayload? = null
    private var itemClick: ItemClick<M>? = null

    open fun onBindViewHolder(f: (DefaultViewHolder) -> Unit) {
        bindView = f
    }

    open fun onBindViewHolder(f: (DefaultViewHolder, Any) -> Unit) {
        bindViewPayload = f
    }

    open fun onItemClick(f: (viewModel: ArrayItemViewModel<M>, viewHolder: DefaultViewHolder) -> Unit) {
        itemClick = f
    }

    override fun getViewHolder(
        parent: ViewGroup,
        layoutInflater: LayoutInflater
    ): DefaultViewHolder {
        return DefaultViewHolder(layoutInflater.inflate(layoutRes, parent, false)).apply { itemView.setOnClickListener { itemClick? .invoke( adapter? .getItem(adapterPosition) as @ParameterName(name ="viewModel") ArrayItemViewModel<M>,
                    this
                )
            }
        }
    }

    override fun bindVH(viewHolder: DefaultViewHolder, model: M, payloads: List<Any>) {
        if (payloads.isNotEmpty()) {
            this.model =  payloads[0] as M
            bindViewPayload? .invoke(viewHolder, payloads[0]) }else{
            bindView? .invoke(viewHolder) } } override fun unBindVH(viewHolder: DefaultViewHolder) {} } typealias ArrayViewModelType <M> = DefaultItemViewModel<M, ArrayListAdapter> open class ArrayItemViewModel<M>(override val layoutRes: Int) : ArrayViewModelType<M>()Copy the code

In the case of payloads, we call bindViewPayload, otherwise we call bindView, so that we can refresh the ItemView locally. The business layer calls onBindViewHolder and passes in a higher-level function. This advanced function assigns a value to the bindView and then receives a callback. This uses the generic DefaultViewHolder and initializes it directly to setOnClickListener. DefaultItemViewModel<M, A: ArrayItemViewModel is an extension of ArrayListAdapter. Please see the code below to do a DSL support

fun <M> arrayItemViewModelDsl(
    layoutRes: Int,
    init: ArrayItemViewModel<M>.() -> Unit
): ArrayItemViewModel<M> {
    return ArrayItemViewModel<M>(layoutRes).apply {
        init()
    }
}

fun arrayListAdapter(block: ArrayListAdapter.() -> Unit): ArrayListAdapter {
    return ArrayListAdapter().apply {
        block()
    }
}

fun ListAdapter<*, *>.into(
    recyclerView: RecyclerView,
    layoutManager: RecyclerView.LayoutManager? = null
) = apply {
    recyclerView.layoutManager = layoutManager ?: LinearLayoutManager(recyclerView.context)
    recyclerView.adapter = this
}

Copy the code

The first step extends ArrayItemViewModel, the second step extends ArrayListAdapter, the third extension Adapter abstract class, bound to RecyclerView, see the effect of using

ArrayListAdapter {// loop to add ItemViewModel (0.. 10).map {add(// arrayItemViewModelDsl<ModelTest>(arrayItemViewModelDsl<ModelTest>)if (it % 2 == 0) R.layout.item_test elseR.layout.item_test_2) {// Model = ModelTest();"title$it"."subTitle$it"OnBindViewHolder {viewHolder -> viewholder.getView <TextView>(R.i.D.tv_title)? .text = model? .title viewHolder.getView<TextView>(R.id.tv_subTitle)? .text = model? OnItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick {vm, vh -> onItemClick; The click event is actually triggered in a different VM by the ViewHolder reuse."arrayItemViewModel"."Incorrect model${model}")
                            Log.d("arrayItemViewModel"."Correct model${vm.model}")
                            Log.d("arrayItemViewModel"."adapter$adapter")
                            Log.d("arrayItemViewModel"."viewHolder${vh.adapterPosition}"// Modify the Model data vm. Model? .title ="Test Updates"// Update the data Adapter? .set(vh.adapterPosition, VM)}})} // Bind RecyclerView into(rv_list_DSL)}Copy the code

After reading the example, is that ok? I think it’s Ok.

How to expand to Anko Layout?

We all know that Anko Layout can improve the loading efficiency of UI and reduce CPU usage. Here is a summary of Anko layout

From THE XML to the run, to the reading of the XML file, to the generation of UI elements, the main thing is to go through the reading of the file stream

The lowest phone models are nearly 359% faster. What is this operation? Does it suck? Aside from the fact that DSLS are extremely loading efficient, there are other advantages to writing DSLS that are simpler and easier to understand than XML, here’s an example

class AnkoLayoutComponent(private val ankoListAdapter: ArrayListAdapter) : AnkoComponent<AnkoLayoutActivity> {

    override fun createView(ui: AnkoContext<AnkoLayoutActivity>) = with(ui) {

        verticalLayout {

            recyclerView {
                bindListAdapter(ankoListAdapter)}.lParams (matchParent) {weight = 1F} // Anko compatible XML layout loading include<View>(R.layout.include_button_bottom) } } }Copy the code

In this example, I want to tell you that anko Layout is not only unique to its writing method, but also fully compatible with XML, which is a good way for those who want to transition slowly. So here comes the theme, how can we extend Adapter to use this as well? The key is the View. Let’s look at the AnkoComponent interface

interface AnkoComponent<in T> {
    fun createView(ui: AnkoContext<T>): View
}
Copy the code

If you look at the ViewHolder constructor, isn’t that just right? If the AnkoComponent generates a View and gives it to the ViewHolder, you can skip the XML.

  public ViewHolder(@NonNull View itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }
Copy the code

ArrayItemViewModel (); getViewHolder (); createView ();

public abstract class AnkoItemViewModel<M, AnkoView extends AnkoComponent<ViewGroup>>
        extends ArrayItemViewModel<M> {

    public AnkoItemViewModel() {
        super(0);
    }

    public abstract AnkoView onCreateView();

    @NotNull
    @Override
    public DefaultViewHolder getViewHolder(@NotNull ViewGroup parent, @NotNull LayoutInflater layoutInflater) {
        AnkoView ankoView = onCreateView();
        View view = ankoView.createView(AnkoContext.Companion.create(parent.getContext(), parent, false));
        view.setTag(R.id.list_adapter_anko_view, ankoView);
        return new DefaultViewHolder(view);
    }

    @Override
    public int getItemViewType() {
        return this.hashCode();
    }

    public AnkoView getAnkoView(RecyclerView.ViewHolder viewHolder) {
        return(AnkoView) viewHolder.itemView.getTag(R.id.list_adapter_anko_view); }}Copy the code

Usage examples

class AnkoViewModelTest : AnkoItemViewModel<ModelTest, AnkoItemView>() { init { onBindViewHolder { viewHolder -> getAnkoView(viewHolder).tvTitle? .text = model? .title getAnkoView(viewHolder).tvSubTitle? .text = model? .subTitle getAnkoView(viewHolder).itemClick = { Log.d("AnkoViewModelTest"."Correct model${model}")
                Log.d("AnkoViewModelTest"."Correct model${model}")

                Log.d("AnkoViewModelTest"."adapter$adapter")
                Log.d("AnkoViewModelTest"."viewHolder${viewHolder.adapterPosition}") model? .title ="Click to update"adapter? .set(viewHolder.adapterPosition, this) } } } override fun onCreateView(): AnkoItemView {return AnkoItemView()
    }
}

class AnkoItemView : AnkoComponent<ViewGroup> {
    ...
}
Copy the code

Inherit AnkoItemViewModel, configure the Model and AnkoView on the line, is so simple, and then in the Adapter so used, add a corresponding instance on the line

  listAdapter.add(AnkoViewModelTest().apply {
                    model = ModelTest("Title${++index}"."Subtitle")})Copy the code

How is it designed to expand so much?

Adapter inheritance. JPG

interface IAdapter<VM> {
    fun getItem(position: Int): VM?
}

Copy the code

At the top of the IAdapter interface, there is only one function to retrieve the ViewModel via positon, and in the middle layer, ViewHolderCacheAdapter

abstract class ViewHolderCacheAdapter<VM : ViewModel<*,*,*>, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>(), IAdapter<VM> { private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache<ViewHolderFactory<VH>>() private val sparseArray = SparseArray<LayoutInflater>(1) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val defaultViewHolder = defaultViewHolderFactoryCache[viewType].getViewHolder(parent, sparseArray.get(0) ? : LayoutInflater.from(parent.context)) defaultViewHolder.itemView.setTag(R.id.list_adapter, this)return defaultViewHolder
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        onBindViewHolder(holder, position, Collections.emptyList())
    }

    override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
        if(position ! = RecyclerView.NO_POSITION){ // Do your binding here holder.itemView.setTag(R.id.list_adapter, this) val item = getItem(position) as? ViewModel<Any, RecyclerView.ViewHolder, IAdapter<*>> item? .let { item.adapter = this item.model? .let { it1 -> item.bindVH(holder, it1, payloads) } holder.itemView.setTag(R.id.list_adapter_item, item) } } } override fun getItemViewType(position: Int): Int { val item = getItem(position) ? :return 0
        val type = item.itemViewType
        if(! defaultViewHolderFactoryCache.contains(type)) {
            item as ViewHolderFactory<VH>
            defaultViewHolderFactoryCache.register(type, item)
        }
        return type
    }

    override fun onViewRecycled(holder: VH) {
        (holder.itemView.getTag(R.id.list_adapter_item) as ViewModel<*, VH, *>).apply {
            unBindVH(holder)
        }
        holder.itemView.setTag(R.id.list_adapter_item, null)
        holder.itemView.setTag(R.id.list_adapter, null)
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        val context = recyclerView.context
        sparseArray.append(0, LayoutInflater.from(context))
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        defaultViewHolderFactoryCache.clear()
        sparseArray.clear()
    }
}
Copy the code

Why would you do that? Can I give you some reasons

  • First, when I say getItemViewType, the argument is position, so I can use postion to get the ViewModel, and using the ViewModel I can implement the ViewHolderFactory, Then through the ViewHolder DefaultViewHolderFactoryCache cache line.
  • The second ViewHolderCacheAdapter does the simple thing of caching the ViewHolder and firing the bindVH of the ViewModel when onBindViewHolder is used. Keep your responsibilities simple so you can reuse them.
  • Third, after integrating it, we only need to extend the corresponding data structure, such as The ObservableArrayList of ArrayListAdapter, SortedListAdapter SortedList, And PagingListAdapter AsyncPagingDataDiffer, so that faced with different data interfaces, constantly expand the line.

Is this a good design? Can different data structures be abstracted into one? Aren’t they all lists? What else can I do? The answer is yes, but why didn’t I? The first reason, subcontract processing, on-demand dependency, is to keep the architecture clean and simple enough to understand and maintain. The third reason is that all three data structures encapsulate lists of data to varying degrees. The ObservableArrayList basically extends ArrayList to implement data update callback. SortedList is a tool class that does not extend the List. It always maintains an ordered List, and uses binary lookup algorithm to achieve fast location update. AsyncPagingDataDiffer is much more important. Threading, paging, state, comparison, etc., more complex functionality encapsulation. Three Adapter code is not posted, can go to see the source oh?

By encapsulating the PagingListAdapter, you’ve discovered a new world. Want to know?

Let’s see why I didn’t rush to encapsulate common business components like empty layouts, head-to-tail layouts, and up-load.

  • After first looking at PagingDataAdapter for Paging 3 version, I found a better implementation scheme for end-to-end layout
  • Second, the encapsulation of these components actually increases the complexity of the framework inconspicuously. More importantly, different apps need to implement these by themselves, so how to provide such an extension entry is the most important, rather than implementing these functions

With that in mind, I’d like to provide some examples to make it easier for you to implement what you want, rather than following my interface specification. If my interface specification is incomplete and you have to switch to another framework, it would be embarrassing for you. Have you ever encountered a project that referenced several Adapter frameworks? There must be.

Let’s see how elegant encapsulation can be achieved by Paging?

 fun withLoadStateHeader(
        header: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            header.loadState = loadStates.prepend
        }
        return ConcatAdapter(header, this)
    }

    fun withLoadStateFooter(
        footer: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            footer.loadState = loadStates.append
        }
        return ConcatAdapter(this, footer)
    }

    fun withLoadStateHeaderAndFooter(
        header: LoadStateAdapter<*>,
        footer: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            header.loadState = loadStates.prepend
            footer.loadState = loadStates.append
        }
        return ConcatAdapter(header, this, footer)
    }
Copy the code

So these are the three class functions that PagingDataAdapter provides, and what do we find? ConcatAdapter, that’s the thing. Look inside

public final class ConcatAdapter extends Adapter<ViewHolder> 
Copy the code

ConcatAdapter allows us to display the contents of multiple adapters in order, which is the original binding of a Adapter, now becomes a sequence of binding multiple, such a combination of implementation, is not very novel, ConcatAdapter I have previously encountered a WrapperAdapter with a similar design, but mine is like this

This solution is also the most popular Adapter framework decoupling method, using WrapperAdapter can be implemented head to tail layout, empty layout and so on. But when I came into contact with ConcatAdapter found that there is such a design

abstract class LoadStateAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
  
    var loadState: LoadState = LoadState.NotLoading(endOfPaginationReached = false)
        set(loadState) {
            if(field ! = loadState) { val oldItem = displayLoadStateAsItem(field) val newItem = displayLoadStateAsItem(loadState)if(oldItem && ! newItem) { notifyItemRemoved(0) }else if(newItem && ! oldItem) { notifyItemInserted(0) }else if (oldItem && newItem) {
                    notifyItemChanged(0)
                }
                field = loadState
            }
        }

    final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        return onCreateViewHolder(parent, loadState)
    }

    final override fun onBindViewHolder(holder: VH, position: Int) {
        onBindViewHolder(holder, loadState)
    }

    final override fun getItemViewType(position: Int): Int = getStateViewType(loadState)

    final override fun getItemCount(): Int = if (displayLoadStateAsItem(loadState)) 1 else 0

    abstract fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): VH

    abstract fun onBindViewHolder(holder: VH, loadState: LoadState)

    open fun displayLoadStateAsItem(loadState: LoadState): Boolean {
        return loadState is LoadState.Loading || loadState is LoadState.Error
    }
}

Copy the code

Also implements the RV Adapter, abstracts a LoadState state, and then, depending on its state, notifyItemRemoved(0) or notifyItemInserted(0) or notifyItemChanged(0), Can we do the same in the future? Absolutely, and I recommend doing so. Composite implementations, like plug-ins, plug in as you go. Don’t waste emotion.

So where’s the source code?

Github.com/ibaozi-cn/R…

How do you quote it?

Allprojects {repositories {/ / project root directory of the build. The first gradle file add the line maven {url'https://jitpack.io'}}} / / core library implementation. Com. Making ibaozi - cn. RecyclerViewAdapter: adapter - core: V1.0.0 / / / / the following are alternatives anko layout extension Implementation of com. Making. Ibaozi - cn. RecyclerViewAdapter: adapter - anko: V1.0.0 / / diffutil extension implementation Com. Making. Ibaozi -cn. RecyclerViewAdapter: adapter - diff: V1.0.0 / / data binding extension implementation Com. Making. Ibaozi -cn. RecyclerViewAdapter: adapter - binding: V1.0.0 / / paging3 extension implementation Com. Making. Ibaozi -cn. RecyclerViewAdapter: adapter - the paging: V1.0.0 / / sortedlist extension implementation Com. Making. Ibaozi -cn. RecyclerViewAdapter: adapter - sorted: V1.0.0 / / flexbox extension implementation Com. Making. Ibaozi -cn. RecyclerViewAdapter: adapter - flex: V1.0.0Copy the code

Any future plans?

Yes, there must be, the first is to constantly improve the design and implementation, the second is to constantly listen to your suggestions or criticism, where the design is not good, please dare to tell me, as the so-called error can be changed, I do not go to hell, who will go to hell? All right, that’s it for this time. Bye