对RecyclerView.Adapter的封装框架解析

  • 概述

    在使用RecyclerView时,我们经常配合着封装的Adapter第三方框架来使用,最经典的就是BaseQuickAdapter系列,它的内部封装了一些关于Adapter的通用逻辑,使得我们可以专注于业务逻辑的开发,极大地提高了工作效率。通过它也可以很方便的结合分页嵌入数据提前加载更多、刷新、多布局等操作。本文通过它的源码,在结合RecyclerView的原理,来研究一下它是怎么封装的。

  • BaseQuickAdapter

    BaseQuickAdapter是这套框架的基础,我们先来看一下他是怎么实现的。

    • onCreateViewHolder方法

      通过RecyclerView的原理我们知道,ViewHolder创建是在onCreateViewHolder方法中完成的,我们继承RecyclerView.Adapter的时候需要重写这个方法,BaseQuickAdapter也实现了这个方法:

      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val baseViewHolder: VH
        when (viewType) {
            LOAD_MORE_VIEW -> {
                val view = mLoadMoreModule!!.loadMoreView.getRootView(parent)
                baseViewHolder = createBaseViewHolder(view)
                mLoadMoreModule!!.setupViewHolder(baseViewHolder)
            }
            HEADER_VIEW -> {
                val headerLayoutVp: ViewParent? = mHeaderLayout.parent
                if (headerLayoutVp is ViewGroup) {
                    headerLayoutVp.removeView(mHeaderLayout)
                }
      
                baseViewHolder = createBaseViewHolder(mHeaderLayout)
            }
            EMPTY_VIEW -> {
                val emptyLayoutVp: ViewParent? = mEmptyLayout.parent
                if (emptyLayoutVp is ViewGroup) {
                    emptyLayoutVp.removeView(mEmptyLayout)
                }
      
                baseViewHolder = createBaseViewHolder(mEmptyLayout)
            }
            FOOTER_VIEW -> {
                val footerLayoutVp: ViewParent? = mFooterLayout.parent
                if (footerLayoutVp is ViewGroup) {
                    footerLayoutVp.removeView(mFooterLayout)
                }
      
                baseViewHolder = createBaseViewHolder(mFooterLayout)
            }
            else -> {
                val viewHolder = onCreateDefViewHolder(parent, viewType)
                bindViewClickListener(viewHolder, viewType)
                mDraggableModule?.initView(viewHolder)
                onItemViewHolderCreated(viewHolder, viewType)
                baseViewHolder = viewHolder
            }
        }
      
        return baseViewHolder
      }
      

      由此可知,BaseQuickAdapter会提供几种类型的View,一般的开发中都是单一布局,所以几乎用不到viewType,而这里,BaseQuickAdapter使用了viewType来创建不同的ViewHolder。

      createBaseViewHolder会使用反射创建对应的ViewHolder:

      protected open fun createBaseViewHolder(view: View): VH {
          var temp: Class<*>? = javaClass
          var z: Class<*>? = null
          while (z == null && null != temp) {
              z = getInstancedGenericKClass(temp)
              temp = temp.superclass
          }
          // 泛型擦除会导致z为null
          val vh: VH? = if (z == null) {
              BaseViewHolder(view) as VH
          } else {
              createBaseGenericKInstance(z, view)
          }
          return vh ?: BaseViewHolder(view) as VH
      }
      
      /**
       * get generic parameter VH
       *
       * @param z
       * @return
       */
      private fun getInstancedGenericKClass(z: Class<*>): Class<*>? {
          try {
              val type = z.genericSuperclass
              if (type is ParameterizedType) {
                  val types = type.actualTypeArguments
                  for (temp in types) {
                      if (temp is Class<*>) {
                          if (BaseViewHolder::class.java.isAssignableFrom(temp)) {
                              return temp
                          }
                      } else if (temp is ParameterizedType) {
                          val rawType = temp.rawType
                          if (rawType is Class<*> && BaseViewHolder::class.java.isAssignableFrom(rawType)) {
                              return rawType
                          }
                      }
                  }
              }
          } catch (e: java.lang.reflect.GenericSignatureFormatError) {
              e.printStackTrace()
          } catch (e: TypeNotPresentException) {
              e.printStackTrace()
          } catch (e: java.lang.reflect.MalformedParameterizedTypeException) {
              e.printStackTrace()
          }
          return null
      }
      
      /**
       * try to create Generic VH instance
       *
       * @param z
       * @param view
       * @return
       */
      @Suppress("UNCHECKED_CAST")
      private fun createBaseGenericKInstance(z: Class<*>, view: View): VH? {
          try {
              val constructor: Constructor<*>
              // inner and unstatic class
              return if (z.isMemberClass && !Modifier.isStatic(z.modifiers)) {
                  constructor = z.getDeclaredConstructor(javaClass, View::class.java)
                  constructor.isAccessible = true
                  constructor.newInstance(this, view) as VH
              } else {
                  constructor = z.getDeclaredConstructor(View::class.java)
                  constructor.isAccessible = true
                  constructor.newInstance(view) as VH
              }
          } catch (e: NoSuchMethodException) {
              e.printStackTrace()
          } catch (e: IllegalAccessException) {
              e.printStackTrace()
          } catch (e: InstantiationException) {
              e.printStackTrace()
          } catch (e: InvocationTargetException) {
              e.printStackTrace()
          }
      
          return null
      }
      
    • onBindViewHolder方法

      根据RecyclerView的原理我们再去onBindViewHolder方法中看看数据是如何绑定的:

      override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position)
            return
        }
        //Add up fetch logic, almost like load more, but simpler.
        mUpFetchModule?.autoUpFetch(position)
        //Do not move position, need to change before LoadMoreView binding
        mLoadMoreModule?.autoLoadMore(position)
        when (holder.itemViewType) {
            LOAD_MORE_VIEW -> {
                mLoadMoreModule?.let {
                    it.loadMoreView.convert(holder, position, it.loadMoreStatus)
                }
            }
            HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return
            else -> convert(holder, getItem(position - headerLayoutCount), payloads)
        }
      }
      

      这个方法会有两个重载方法,多出来的payloads表示额外的数据,两个参数的onBindViewHolder方法基本一样,针对RecyclerView.Adapter的两个重载onBindViewHolder方法,这里调用的是重载的convert方法,所以我们只需要在自定义Adapter中重写convert方法即可完成数据绑定。

    • 设置Item中需要点击的子View Id

      在onCreateViewHolder中else分支中调用了bindViewClickListener方法,这表示为用户Item添加事件监听:

      protected open fun bindViewClickListener(viewHolder: VH, viewType: Int) {
          mOnItemClickListener?.let {
              viewHolder.itemView.setOnClickListener { v ->
                  var position = viewHolder.adapterPosition
                  if (position == RecyclerView.NO_POSITION) {
                      return@setOnClickListener
                  }
                  position -= headerLayoutCount
                  setOnItemClick(v, position)
              }
          }
          mOnItemLongClickListener?.let {
              viewHolder.itemView.setOnLongClickListener { v ->
                  var position = viewHolder.adapterPosition
                  if (position == RecyclerView.NO_POSITION) {
                      return@setOnLongClickListener false
                  }
                  position -= headerLayoutCount
                  setOnItemLongClick(v, position)
              }
          }
              //下面是给Item中的子View添加事件监听(如果需要的话)
          mOnItemChildClickListener?.let {
              for (id in getChildClickViewIds()) {
                  viewHolder.itemView.findViewById<View>(id)?.let { childView ->
                      if (!childView.isClickable) {
                          childView.isClickable = true
                      }
                      childView.setOnClickListener { v ->
                          var position = viewHolder.adapterPosition
                          if (position == RecyclerView.NO_POSITION) {
                              return@setOnClickListener
                          }
                          position -= headerLayoutCount
                          setOnItemChildClick(v, position)
                      }
                  }
              }
          }
          mOnItemChildLongClickListener?.let {
              for (id in getChildLongClickViewIds()) {
                  viewHolder.itemView.findViewById<View>(id)?.let { childView ->
                      if (!childView.isLongClickable) {
                          childView.isLongClickable = true
                      }
                      childView.setOnLongClickListener { v ->
                          var position = viewHolder.adapterPosition
                          if (position == RecyclerView.NO_POSITION) {
                              return@setOnLongClickListener false
                          }
                          position -= headerLayoutCount
                          setOnItemChildLongClick(v, position)
                      }
                  }
              }
          }
      }
      

      这里特别的一点是可以给Item中的子View添加事件监听,不过要事先通过addChildClickViewIds方法把需要监听的子View的id添加进去:

      fun addChildClickViewIds(@IdRes vararg viewIds: Int) {
          for (viewId in viewIds) {
              childClickViewIds.add(viewId)
          }
      }
      
    • Header和Footer

      我们知道,RecyclerView是没有自带header和footer的相关API的,但是这一切完全都可以通过viewType和position来完成,看一下这里是怎么做的,我们以header为例(footer的原理一样)。

      BaseQuickAdapter中有一个mHeaderLayout来盛放所有的header view,通过addHeaderView添加、通过setHeaderView替换,setHeaderView中如果mHeaderLayout之前为空(即之前没有Header)的话也会走addHeaderView方法添加header view,所以我们来看一下addHeaderView方法:

      @JvmOverloads
      fun addHeaderView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {
          if (!this::mHeaderLayout.isInitialized) {
              mHeaderLayout = LinearLayout(view.context)
              mHeaderLayout.orientation = orientation
              mHeaderLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {
                  RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
              } else {
                  RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
              }
          }
      
          val childCount = mHeaderLayout.childCount
          var mIndex = index
          if (index < 0 || index > childCount) {
              mIndex = childCount
          }
          mHeaderLayout.addView(view, mIndex)
          if (mHeaderLayout.childCount == 1) {
              val position = headerViewPosition
              if (position != -1) {
                  notifyItemInserted(position)
              }
          }
          return mIndex
      }
      

      值得注意的一点是,mHeaderLayout.childCount == 1说明是第一次添加header view,此时mHeaderLayout还并没有attach到RecyclerView中,所以这里会调用notifyItemInserted方法插入mHeaderLayout,也就是说mHeaderLayout是作为某一项插入到RecyclerView中的,只是它的viewType是HEADER_VIEW,看一下它重写的getItemViewType方法:

      companion object {
          const val HEADER_VIEW = 0x10000111
          const val LOAD_MORE_VIEW = 0x10000222
          const val FOOTER_VIEW = 0x10000333
          const val EMPTY_VIEW = 0x10000555
      }
      
      override fun getItemViewType(position: Int): Int {
          if (hasEmptyView()) {
              val header = headerWithEmptyEnable && hasHeaderLayout()
              return when (position) {
                  0 -> if (header) {
                      HEADER_VIEW
                  } else {
                      EMPTY_VIEW
                  }
                  1 -> if (header) {
                      EMPTY_VIEW
                  } else {
                      FOOTER_VIEW
                  }
                  2 -> FOOTER_VIEW
                  else -> EMPTY_VIEW
              }
          }
      
          val hasHeader = hasHeaderLayout()
          if (hasHeader && position == 0) {
              return HEADER_VIEW
          } else {
              var adjPosition = if (hasHeader) {
                  position - 1
              } else {
                  position
              }
              val dataSize = data.size
              return if (adjPosition < dataSize) {
                  getDefItemViewType(adjPosition)
              } else {
                  adjPosition -= dataSize
                  val numFooters = if (hasFooterLayout()) {
                      1
                  } else {
                      0
                  }
                  if (adjPosition < numFooters) {
                      FOOTER_VIEW
                  } else {
                      LOAD_MORE_VIEW
                  }
              }
          }
      }
      

      这里的getDefItemViewType方法返回的就是普通Item的view类型,这个方法默认是一样的,可以重写返回自己的ViewType。

      总之,Header和Footer的原理就是通过设置不同的ViewType来达到区别的目的的。

    • 设置spanSize

      在GridLayoutManager中会有行列的占比设置,这部分封装BaseQuickAdapter是通过重写onViewAttachedToWindow方法和onAttachedToRecyclerView方法实现的:

      override fun onViewAttachedToWindow(holder: VH) {
          super.onViewAttachedToWindow(holder)
          val type = holder.itemViewType
          if (isFixedViewType(type)) {
              setFullSpan(holder)
          } else {
              addAnimation(holder)
          }
      }
      
      override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
          super.onAttachedToRecyclerView(recyclerView)
          weakRecyclerView = WeakReference(recyclerView)
          mRecyclerView = recyclerView
      
          this.context = recyclerView.context
          mDraggableModule?.attachToRecyclerView(recyclerView)
      
          val manager = recyclerView.layoutManager
          if (manager is GridLayoutManager) {
              val defSpanSizeLookup = manager.spanSizeLookup
              manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                  override fun getSpanSize(position: Int): Int {
                      val type = getItemViewType(position)
                      if (type == HEADER_VIEW && headerViewAsFlow) {
                          return 1
                      }
                      if (type == FOOTER_VIEW && footerViewAsFlow) {
                          return 1
                      }
                      return if (mSpanSizeLookup == null) {
                          if (isFixedViewType(type)) manager.spanCount else defSpanSizeLookup.getSpanSize(position)
                      } else {
                          if (isFixedViewType(type))
                              manager.spanCount
                          else
                              mSpanSizeLookup!!.getSpanSize(manager, type, position - headerLayoutCount)
                      }
                  }
      
              }
          }
      }
      

      这里还调用了addAnimation方法设置item初始显示动画:

      private fun addAnimation(holder: RecyclerView.ViewHolder) {
          if (animationEnable) {
              if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) {
                  val animation: BaseAnimation = adapterAnimation ?: AlphaInAnimation()
                  animation.animators(holder.itemView).forEach {
                      startAnim(it, holder.layoutPosition)
                  }
                  mLastPosition = holder.layoutPosition
              }
          }
      }
      
      class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : BaseAnimation {
          override fun animators(view: View): Array<Animator> {
              val animator = ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)
              animator.duration = 300L
              animator.interpolator = LinearInterpolator()
              return arrayOf(animator)
          }
      
          companion object {
              private const val DEFAULT_ALPHA_FROM = 0f
          }
      
      }
      

      可见默认显示动画是300毫秒内渐显,可以通过setAdapterAnimation方法设置别的动画。

    • 上滑加载、下滑加载和拖动事务

      前面我们看到,在onCreateViewHolder和onBindViewHolder方法中用到了三个“Module”:mLoadMoreModule、mUpFetchModule、mDraggableModule,它们在BaseQuickAdapter构造方法中会尝试初始化:

      init {
          checkModule()
      }
      
      /**
       * 检查模块
       */
      private fun checkModule() {
          if (this is LoadMoreModule) {
              mLoadMoreModule = this.addLoadMoreModule(this)
          }
          if (this is UpFetchModule) {
              mUpFetchModule = this.addUpFetchModule(this)
          }
          if (this is DraggableModule) {
              mDraggableModule = this.addDraggableModule(this)
          }
      }
      

      可见,如果要使用LoadMoreModule需要自定义的Adapter继承这个接口才行,同样其他的两种也是,后面讲到其他两种时,初始化部分就不再赘述了,具体用来干什么的先不用管,现在只需要知道,要使用这三个Module,自定义的Adapter必须继承对应的接口,它们都没有需要实现的方法,只是一个空接口

      addLoadMoreModule是BaseQuickAdapterModuleImp中的方法,因为BaseQuickAdapter继承了它,所以这里使用this调用,看一下他的addLoadMoreModule方法:

      fun addLoadMoreModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseLoadMoreModule {
          return BaseLoadMoreModule(baseQuickAdapter)
      }
      

      在这里,BaseLoadMoreModule实例会赋给mLoadMoreModule,你可以在Adapter中重写addLoadMoreModule方法来返回自己的LoadMoreModule实例,同样,其他两种Module也是

      下面我们分别来看看它们是用来干啥的。

    • LoadMoreModule

      mLoadMoreModule类型是BaseLoadMoreModule,先看一下它的setupViewHolder方法:

      internal fun setupViewHolder(viewHolder: BaseViewHolder) {
          viewHolder.itemView.setOnClickListener {
              if (loadMoreStatus == LoadMoreStatus.Fail) {
                  loadMoreToLoading()
              } else if (loadMoreStatus == LoadMoreStatus.Complete) {
                  loadMoreToLoading()
              } else if (enableLoadMoreEndClick && loadMoreStatus == LoadMoreStatus.End) {
                  loadMoreToLoading()
              }
          }
      }
      

      失败、完成或者数据全部加载完的状态时调用loadMoreToLoading方法:

      fun loadMoreToLoading() {
          if (loadMoreStatus == LoadMoreStatus.Loading) {
              return
          }
          loadMoreStatus = LoadMoreStatus.Loading
          baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
          invokeLoadMoreListener()
      }
      

      这里会把loadMoreStatus修改为LoadMoreStatus.Loading,然后调用BaseQuickAdapter的notifyItemChanged方法更改loadMoreView的UI。

      既然调用notifyItemChanged方法更改UI,说明loadMoreView之前已被添加,那是在什么时候添加的呢?

      var isEnableLoadMore = false
          set(value) {
              val oldHasLoadMore = hasLoadMoreView()
              field = value
              val newHasLoadMore = hasLoadMoreView()
      
              if (oldHasLoadMore) {
                  if (!newHasLoadMore) {
                      baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)
                  }
              } else {
                  if (newHasLoadMore) {
                      loadMoreStatus = LoadMoreStatus.Complete
                      baseQuickAdapter.notifyItemInserted(loadMoreViewPosition)
                  }
              }
          }
      

      可见,是在给isEnableLoadMore赋值的时候插入的,在这里调用notifyItemInserted方法后会走到onCreateViewHolder方法,然后就会调用上面的setupViewHolder方法,onCreateViewHolder方法中判断LOAD_MORE_VIEW类型的逻辑时有一句代码:

      val view = mLoadMoreModule!!.loadMoreView.getRootView(parent)
      

      默认的loadMoreView是:

      var loadMoreView = LoadMoreModuleConfig.defLoadMoreView
      @JvmStatic
      var defLoadMoreView: BaseLoadMoreView = SimpleLoadMoreView()
      

      SimpleLoadMoreView中的getRootView方法返回View:

      override fun getRootView(parent: ViewGroup): View =
              parent.getItemView(R.layout.brvah_quick_view_load_more)
      

      getItemView方法是添加的ViewGroup扩展函数:

      fun ViewGroup.getItemView(@LayoutRes layoutResId: Int): View {
          return LayoutInflater.from(this.context).inflate(layoutResId, this, false)
      }
      

      然后onBindViewHolder方法中会调用autoLoadMore方法:

      internal fun autoLoadMore(position: Int) {
          if (!isAutoLoadMore) {
              //如果不需要自动加载更多,直接返回
              return
          }
          if (!hasLoadMoreView()) {
              return
          }
          if (position < baseQuickAdapter.itemCount - preLoadNumber) {
              return
          }
          if (loadMoreStatus != LoadMoreStatus.Complete) {
              return
          }
          if (loadMoreStatus == LoadMoreStatus.Loading) {
              return
          }
          if (!mNextLoadEnable) {
              return
          }
      
          invokeLoadMoreListener()
      }
      

      这里会调用invokeLoadMoreListener方法:

      private fun invokeLoadMoreListener() {
          loadMoreStatus = LoadMoreStatus.Loading
          baseQuickAdapter.mRecyclerView?.let {
              it.post { mLoadMoreListener?.onLoadMore() }
          } ?: mLoadMoreListener?.onLoadMore()
      }
      

      调用自添加的加载更多监听回调去加载更多数据。

      数据获取后判断是否还有更多数据,如果没有了则调用mAdapter.loadMoreModule?.loadMoreEnd(true)方法隐藏加载更多view:

      fun loadMoreEnd(gone: Boolean = false) {
              if (!hasLoadMoreView()) {
                  return
              }
      //        mNextLoadEnable = false
              isLoadEndMoreGone = gone
      
              loadMoreStatus = LoadMoreStatus.End
      
              if (gone) {
                  baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition)
              } else {
                  baseQuickAdapter.notifyItemChanged(loadMoreViewPosition)
              }
          }
      
    • UpFetchModule

      和LoadMoreModule原理类似,UpFetchModule是负责向上加载的,但是它比LoadMoreModule要简单多了,它没有布局,他只是负责向上加载的回调。注意,不要使用它来实现刷新操作,它并不适合。

      什么情况下需要它呢,比如,要显示一些类目,默认显示当天的,向上滑动的时候我想要看见今天之前的类目的时候就用到它了。

      BaseQuickAdapter中每次调用onBindViewHolder方法时都会调用autoUpFetch方法尝试向上加载:

      internal fun autoUpFetch(position: Int) {
          if (!isUpFetchEnable || isUpFetching) {
              return
          }
          if (position <= startUpFetchPosition) {
              mUpFetchListener?.onUpFetch()
          }
      }
      

      可见是根据isUpFetchEnable和isUpFetching来决定的,前者是决定是否开启自动向上加载,后者是避免重复加载。

      开启自动向上加载(默认是关闭的):

      private fun setUpFetch() {
          mAdapter.upFetchModule?.isUpFetchEnable = true
          mAdapter.upFetchModule?.setOnUpFetchListener {
              mAdapter.upFetchModule?.isUpFetching = true
              Thread {
                  Thread.sleep(2000)
                  //获取数据
                  model.upFetchData()
              }.start()
          }
      }
      

      回调:

      model.upFetchData.observe(this, {
          mAdapter.upFetchModule?.isUpFetching = false
          mAdapter.addData(0, it)
      })
      

      注意这里是把数据放在首位,从而达到向上加载的目的,所以作者只是提供了思路,而实际上向上加载的效果是由我们决定的。你可以在这里决定是否需要关闭向上加载,比如你只需要向上加载一次,则可在第一次后把isUpFetchEnable置为false。

    • DraggableModule

      DraggableModule里面默认有两种操作,一个是拖拽移动Item,一个是右滑删除Item:

      //开启拖拽移动
      mAdapter.draggableModule?.isDragEnabled = true
      //开启右滑删除
      mAdapter.draggableModule?.isSwipeEnabled = true
      

      初始化mDraggableModule之后,在BaseQuickAdapter的onAttachedToRecyclerView方法中会调用:

      mDraggableModule?.attachToRecyclerView(recyclerView)
      
      fun attachToRecyclerView(recyclerView: RecyclerView) {
          itemTouchHelper.attachToRecyclerView(recyclerView)
      }
      
      private fun initItemTouch() {
          itemTouchHelperCallback = DragAndSwipeCallback(this)
          itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
      }
      

      ItemTouchHelper的attachToRecyclerView方法如下:

      public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
          if (mRecyclerView == recyclerView) {
              return; // nothing to do
          }
          if (mRecyclerView != null) {
              destroyCallbacks();
          }
          mRecyclerView = recyclerView;
          if (recyclerView != null) {
              final Resources resources = recyclerView.getResources();
              mSwipeEscapeVelocity = resources
                      .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
              mMaxSwipeVelocity = resources
                      .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
              setupCallbacks();
          }
      }
      

      这里调用了setupCallbacks方法:

      private void setupCallbacks() {
          ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
          mSlop = vc.getScaledTouchSlop();
          mRecyclerView.addItemDecoration(this);
          mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
          mRecyclerView.addOnChildAttachStateChangeListener(this);
          startGestureDetection();
      }
      

      可以看到,这里调用了RecyclerView的addOnItemTouchListener方法:

      public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) {
          mOnItemTouchListeners.add(listener);
      }
      

      mOnItemTouchListeners在findInterceptingOnItemTouchListener方法中使用:

      private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
          int action = e.getAction();
          final int listenerCount = mOnItemTouchListeners.size();
          for (int i = 0; i < listenerCount; i++) {
              final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
              if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
                  mInterceptingOnItemTouchListener = listener;
                  return true;
              }
          }
          return false;
      }
      

      findInterceptingOnItemTouchListener方法又在dispatchToOnItemTouchListeners中调用,dispatchToOnItemTouchListeners在onTouchEvent中调用:

      @Override
      public boolean onTouchEvent(MotionEvent e) {
              ...
          if (dispatchToOnItemTouchListeners(e)) {
              cancelScroll();
              return true;
          }
          ...
      }
      

      可以看到,这里如果Item消费了事件则直接return。

      注意dispatchToOnItemTouchListeners方法中如果是Item第一次捕获事件的话会走findInterceptingOnItemTouchListener方法,也就是最终会调用mOnItemTouchListener的onInterceptTouchEvent方法;之后再次响应事件则直接调用mOnItemTouchListener的onTouchEvent方法。

      不管是onInterceptTouchEvent还是onTouchEvent,都会调用select(vh, ACTION_STATE_SWIPE)方法:

      void select(@Nullable ViewHolder selected, int actionState) {
          if (selected == mSelected && actionState == mActionState) {
              return;
          }
          mDragScrollStartTimeInMs = Long.MIN_VALUE;
          final int prevActionState = mActionState;
          // prevent duplicate animations
          endRecoverAnimation(selected, true);
          mActionState = actionState;
          if (actionState == ACTION_STATE_DRAG) {
              if (selected == null) {
                  throw new IllegalArgumentException("Must pass a ViewHolder when dragging");
              }
      
              // we remove after animation is complete. this means we only elevate the last drag
              // child but that should perform good enough as it is very hard to start dragging a
              // new child before the previous one settles.
              mOverdrawChild = selected.itemView;
              addChildDrawingOrderCallback();
          }
          int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
                  - 1;
          boolean preventLayout = false;
      
          if (mSelected != null) {
              final ViewHolder prevSelected = mSelected;
              if (prevSelected.itemView.getParent() != null) {
                  final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
                          : swipeIfNecessary(prevSelected);
                  releaseVelocityTracker();
                  // find where we should animate to
                  final float targetTranslateX, targetTranslateY;
                  int animationType;
                  switch (swipeDir) {
                      case LEFT:
                      case RIGHT:
                      case START:
                      case END:
                          targetTranslateY = 0;
                          targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
                          break;
                      case UP:
                      case DOWN:
                          targetTranslateX = 0;
                          targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
                          break;
                      default:
                          targetTranslateX = 0;
                          targetTranslateY = 0;
                  }
                  if (prevActionState == ACTION_STATE_DRAG) {
                      animationType = ANIMATION_TYPE_DRAG;
                  } else if (swipeDir > 0) {
                      animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
                  } else {
                      animationType = ANIMATION_TYPE_SWIPE_CANCEL;
                  }
                  getSelectedDxDy(mTmpPosition);
                  final float currentTranslateX = mTmpPosition[0];
                  final float currentTranslateY = mTmpPosition[1];
                  final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
                          prevActionState, currentTranslateX, currentTranslateY,
                          targetTranslateX, targetTranslateY) {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          super.onAnimationEnd(animation);
                          if (this.mOverridden) {
                              return;
                          }
                          if (swipeDir <= 0) {
                              // this is a drag or failed swipe. recover immediately
                              mCallback.clearView(mRecyclerView, prevSelected);
                              // full cleanup will happen on onDrawOver
                          } else {
                              // wait until remove animation is complete.
                              mPendingCleanup.add(prevSelected.itemView);
                              mIsPendingCleanup = true;
                              if (swipeDir > 0) {
                                  // Animation might be ended by other animators during a layout.
                                  // We defer callback to avoid editing adapter during a layout.
                                  postDispatchSwipe(this, swipeDir);
                              }
                          }
                          // removed from the list after it is drawn for the last time
                          if (mOverdrawChild == prevSelected.itemView) {
                              removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
                          }
                      }
                  };
                  final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
                          targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
                  rv.setDuration(duration);
                  mRecoverAnimations.add(rv);
                  rv.start();
                  preventLayout = true;
              } else {
                  removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
                  mCallback.clearView(mRecyclerView, prevSelected);
              }
              mSelected = null;
          }
          if (selected != null) {
              mSelectedFlags =
                      (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
                              >> (mActionState * DIRECTION_FLAG_COUNT);
              mSelectedStartX = selected.itemView.getLeft();
              mSelectedStartY = selected.itemView.getTop();
              mSelected = selected;
      
              if (actionState == ACTION_STATE_DRAG) {
                  mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
              }
          }
          final ViewParent rvParent = mRecyclerView.getParent();
          if (rvParent != null) {
              rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
          }
          if (!preventLayout) {
              mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
          }
          mCallback.onSelectedChanged(mSelected, mActionState);
          mRecyclerView.invalidate();
      }
      

      可以看到,如果actionState不是ACTION_STATE_DRAG的话就调用swipeIfNecessary方法,拿到返回值swipeDir,根据它来判断是横向swipe还是纵向swipe,在swipeIfNecessary方法中是根据RecyclerView布局方向计算的,如果是横向swipe操作则把transitionY置为0,否则置transitionX为0。

      然后根据actionState来判断使用哪种动画,总共有三种,一种是拖拽动画,一种是删除成功动画(就是Item移出RecyclerView的动画),还有一种是删除失败动画(即由于滑动距离或者手势不符合移除条件时Item复原位置时的动画)。

      在动画结束监听onAnimationEnd里会调用postDispatchSwipe方法:

      void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
          // wait until animations are complete.
          mRecyclerView.post(new Runnable() {
              @Override
              public void run() {
                  if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
                          && !anim.mOverridden
                          && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
                      final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
                      // if animator is running or we have other active recover animations, we try
                      // not to call onSwiped because DefaultItemAnimator is not good at merging
                      // animations. Instead, we wait and batch.
                      if ((animator == null || !animator.isRunning(null))
                              && !hasRunningRecoverAnim()) {
                          mCallback.onSwiped(anim.mViewHolder, swipeDir);
                      } else {
                          mRecyclerView.post(this);
                      }
                  }
              }
          });
      }
      

      DragAndSwipeCallback的onSwiped方法中:

      @Override
      public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
          if (!isViewCreateByAdapter(viewHolder)) {
              if (mDraggableModule != null) {
                  mDraggableModule.onItemSwiped(viewHolder);
              }
          }
      }
      

      BaseDraggableModule中onItemSwiped方法如下:

      open fun onItemSwiped(viewHolder: RecyclerView.ViewHolder) {
          val pos = getViewHolderPosition(viewHolder)
          if (inRange(pos)) {
              baseQuickAdapter.data.removeAt(pos)
              baseQuickAdapter.notifyItemRemoved(viewHolder.adapterPosition)
              if (isSwipeEnabled) {
                  mOnItemSwipeListener?.onItemSwiped(viewHolder, pos)
              }
          }
      }
      

      可以看到,在动画结束后会通知RecyclerView响应数据变化。和postDispatchSwipe方法相对的是clearView方法:

      @Override
      public void clearView(View view) {
          if (Build.VERSION.SDK_INT >= 21) {
              final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation);
              if (tag instanceof Float) {
                  ViewCompat.setElevation(view, (Float) tag);
              }
              view.setTag(R.id.item_touch_helper_previous_elevation, null);
          }
      
          view.setTranslationX(0f);
          view.setTranslationY(0f);
      }
      

      在不符合变动条件(移动Item或者移除Item)时调用它回到原位置。

      那么是在哪里响应手指拖动的呢?

      在select方法的最后会调用mRecyclerView.invalidate()方法,在RecyclerView的onDraw方法中:

      @Override
      public void onDraw(Canvas c) {
          super.onDraw(c);
      
          final int count = mItemDecorations.size();
          for (int i = 0; i < count; i++) {
              mItemDecorations.get(i).onDraw(c, this, mState);
          }
      }
      

      因为ItemTouchHelper继承自RecyclerView.ItemDecoration:

      public class ItemTouchHelper extends RecyclerView.ItemDecoration
              implements RecyclerView.OnChildAttachStateChangeListener {
      

      而在BaseDraggableModule的attachToRecyclerView方法中调用了ItemTouchHelper的attachToRecyclerView方法,在随后的setupCallbacks方法中调用了mRecyclerView.addItemDecoration(this)方法将ItemTouchHelper添加到了mItemDecorations中。

      所以这里也会调用ItemTouchHelper的onDraw方法:

      void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
              List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
              int actionState, float dX, float dY) {
          final int recoverAnimSize = recoverAnimationList.size();
          for (int i = 0; i < recoverAnimSize; i++) {
              final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
              anim.update();
              final int count = c.save();
              onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
                      false);
              c.restoreToCount(count);
          }
          if (selected != null) {
              final int count = c.save();
              onChildDraw(c, parent, selected, dX, dY, actionState, true);
              c.restoreToCount(count);
          }
      }
      

      可以看到,这里会通过animation的update方法(比如阻尼等拖动效果的实现)计算出最新的Item View的坐标信息,然后调用ItemTouchUIUtilImpl的onDraw方法:

      @Override
      public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
              int actionState, boolean isCurrentlyActive) {
          if (Build.VERSION.SDK_INT >= 21) {
              if (isCurrentlyActive) {
                  Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
                  if (originalElevation == null) {
                      originalElevation = ViewCompat.getElevation(view);
                      float newElevation = 1f + findMaxElevation(recyclerView, view);
                      ViewCompat.setElevation(view, newElevation);
                      view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
                  }
              }
          }
              
          //产生移动效果
          view.setTranslationX(dX);
          view.setTranslationY(dY);
      }
      

      在这里产生随手指移动的效果。

    • setDiffNewData方法

      你还可以通过setDiffNewData方法来设置新的数据集,它只会更新和旧数据集相比不同的项:

      open fun setDiffNewData(list: MutableList<T>?) {
          if (hasEmptyView()) {
              // If the current view is an empty view, set the new data directly without diff
              setNewInstance(list)
              return
          }
          mDiffHelper?.submitList(list)
      }
      

      当然使用这个方法的前提需要调用setDiffConfig方法设置mDiffHelper:

      fun setDiffConfig(config: BrvahAsyncDifferConfig<T>) {
          mDiffHelper = BrvahAsyncDiffer(this, config)
      }
      

      主要是提供一个BrvahAsyncDifferConfig:

      class BrvahAsyncDifferConfig<T>(
              @RestrictTo(RestrictTo.Scope.LIBRARY)
              val mainThreadExecutor: Executor?,
              val backgroundThreadExecutor: Executor,
              val diffCallback: DiffUtil.ItemCallback<T>)
      

      关键是提供一个DiffUtil.ItemCallback接口,这个接口就是比较数据项的回调接口。

      在mDiffHelper调用submitList方法时:

      fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) {
          // incrementing generation means any currently-running diffs are discarded when they finish
          val runGeneration: Int = ++mMaxScheduledGeneration
          if (newList === adapter.data) {
              // nothing to do (Note - still had to inc generation, since may have ongoing work)
              commitCallback?.run()
              return
          }
          val oldList: List<T> = adapter.data
          // fast simple remove all
          if (newList == null) {
              val countRemoved: Int = adapter.data.size
              adapter.data = arrayListOf()
              // notify last, after list is updated
              mUpdateCallback.onRemoved(0, countRemoved)
              onCurrentListChanged(oldList, commitCallback)
              return
          }
          // fast simple first insert
          if (adapter.data.isEmpty()) {
              adapter.data = newList
              // notify last, after list is updated
              mUpdateCallback.onInserted(0, newList.size)
              onCurrentListChanged(oldList, commitCallback)
              return
          }
      
          config.backgroundThreadExecutor.execute {
              val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                  override fun getOldListSize(): Int {
                      return oldList.size
                  }
      
                  override fun getNewListSize(): Int {
                      return newList.size
                  }
      
                  override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                      val oldItem: T? = oldList[oldItemPosition]
                      val newItem: T? = newList[newItemPosition]
                      return if (oldItem != null && newItem != null) {
                          config.diffCallback.areItemsTheSame(oldItem, newItem)
                      } else oldItem == null && newItem == null
                      // If both items are null we consider them the same.
                  }
      
                  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                      val oldItem: T? = oldList[oldItemPosition]
                      val newItem: T? = newList[newItemPosition]
                      if (oldItem != null && newItem != null) {
                          return config.diffCallback.areContentsTheSame(oldItem, newItem)
                      }
                      if (oldItem == null && newItem == null) {
                          return true
                      }
                      throw AssertionError()
                  }
      
                  override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
                      val oldItem: T? = oldList[oldItemPosition]
                      val newItem: T? = newList[newItemPosition]
                      if (oldItem != null && newItem != null) {
                          return config.diffCallback.getChangePayload(oldItem, newItem)
                      }
                      throw AssertionError()
                  }
              })
              mMainThreadExecutor.execute {
                  if (mMaxScheduledGeneration == runGeneration) {
                      latchList(newList, result, commitCallback)
                  }
              }
          }
      }
      

      可以看到,在后台线程中比较数据,在主线程中调用latchList方法更新数据:

      private fun latchList(
              newList: MutableList<T>,
              diffResult: DiffResult,
              commitCallback: Runnable?) {
          val previousList: List<T> = adapter.data
          adapter.data = newList
      
          diffResult.dispatchUpdatesTo(mUpdateCallback)
          onCurrentListChanged(previousList, commitCallback)
      }
      

      在DiffResult的构造方法中已经调用findMatchingItems方法,在里面会通过前面DiffUtil.Callback的相关方法产生mOldItemStatuses和mNewItemStatuses的内容,它们保存的分别是旧新数据集的下标,最后,调用diffResult.dispatchUpdatesTo方法传入的mUpdateCallback的相关方法进行数据更新:

      class BrvahListUpdateCallback(private val mAdapter: BaseQuickAdapter<*, *>) : ListUpdateCallback {
      
          override fun onInserted(position: Int, count: Int) {
              mAdapter.notifyItemRangeInserted(position + mAdapter.headerLayoutCount, count)
          }
      
          override fun onRemoved(position: Int, count: Int) {
              if (mAdapter.mLoadMoreModule?.hasLoadMoreView() == true && mAdapter.itemCount == 0) {
                  // 如果注册了加载更多,并且当前itemCount为0,则需要加上loadMore所占用的一行
                  mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count + 1)
              } else {
                  mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count)
              }
          }
      
          override fun onMoved(fromPosition: Int, toPosition: Int) {
              mAdapter.notifyItemMoved(fromPosition + mAdapter.headerLayoutCount, toPosition + mAdapter.headerLayoutCount)
          }
      
          override fun onChanged(position: Int, count: Int, payload: Any?) {
              mAdapter.notifyItemRangeChanged(position + mAdapter.headerLayoutCount, count, payload)
          }
      
      }
      

      如果使用默认的Executor的话,你也可以直接调用setDiffCallback方法设置DiffUtil.ItemCallback即可。

    • 默认加载动画

      当Item View添加到window时的显示动画是通过addAnimation方法添加的:

      private fun addAnimation(holder: RecyclerView.ViewHolder) {
          if (animationEnable) {
              if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) {
                  val animation: BaseAnimation = adapterAnimation ?: AlphaInAnimation()
                  animation.animators(holder.itemView).forEach {
                      startAnim(it, holder.layoutPosition)
                  }
                  mLastPosition = holder.layoutPosition
              }
          }
      }
      

      可以看到,默认是AlphaInAnimation。

      adapterAnimation可以自己设置,也可以调用setAnimationWithDefault方法设置默认动画:

      fun setAnimationWithDefault(animationType: AnimationType) {
          adapterAnimation = when (animationType) {
              AnimationType.AlphaIn -> AlphaInAnimation()
              AnimationType.ScaleIn -> ScaleInAnimation()
              AnimationType.SlideInBottom -> SlideInBottomAnimation()
              AnimationType.SlideInLeft -> SlideInLeftAnimation()
              AnimationType.SlideInRight -> SlideInRightAnimation()
          }
      }
      

      addAnimation方法是在onViewAttachedToWindow方法中调用的:

      override fun onViewAttachedToWindow(holder: VH) {
          super.onViewAttachedToWindow(holder)
          val type = holder.itemViewType
          if (isFixedViewType(type)) {
              setFullSpan(holder)
          } else {
              addAnimation(holder)
          }
      }
      

      isFixedViewType方法会保证是数据Item才会响应动画,头布局、加载更多等Item不会响应动画。

      而onViewAttachedToWindow方法又是在RecyclerView的addView流程中调用的,所以在Item添加到RecyclerView时会触发动画。

  • BaseMultiItemQuickAdapter

    在开发中,我们有时会遇到多布局的情况,就是列表中存在不同样式的布局,这种情况下我们可以使用BaseMultiItemQuickAdapter。

    BaseMultiItemQuickAdapter继承自BaseQuickAdapter,所以它的原理完全一样,我们这里只看不同的地方。

    abstract class BaseMultiItemQuickAdapter<T : MultiItemEntity, VH : BaseViewHolder>(data: MutableList<T>? = null)
        : BaseQuickAdapter<T, VH>(0, data) {
    
        private val layouts: SparseIntArray by lazy(LazyThreadSafetyMode.NONE) { SparseIntArray() }
    
        override fun getDefItemViewType(position: Int): Int {
            return data[position].itemType
        }
    
        override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {
            val layoutResId = layouts.get(viewType)
            require(layoutResId != 0) { "ViewType: $viewType found layoutResId,please use addItemType() first!" }
            return createBaseViewHolder(parent, layoutResId)
        }
    
        /**
         * 调用此方法,设置多布局
         * @param type Int
         * @param layoutResId Int
         */
        protected fun addItemType(type: Int, @LayoutRes layoutResId: Int) {
            layouts.put(type, layoutResId)
        }
    }
    

    可以看到很少的代码,重写了getDefItemViewType和onCreateDefViewHolder方法。

    我们知道,在上面的BaseQuickAdapter原理分析中,onCreateViewHolder中会调用onCreateDefViewHolder方法创建ViewHolder,在这里会通过一个layouts集合保存所有的布局id,在调用onCreateDefViewHolder的时候从中取出,从而达到显示多种布局的效果。

    通过调用adapter的addItemType来根据type添加多个布局id,比如:

    init {
        addItemType(FlightListAdapterModel.TUIJIAN, R.layout.layout_001)
        addItemType(FlightListAdapterModel.FLIGHT, R.layout.layout_002)
    }
    

    此外,注意,每一个Item Data都必须继承自MultiItemEntity。

    然后convert方法中根据itemType来实现不同操作,比如:

    override fun convert(helper: BaseViewHolder, item: FlightlistHandleDataBean.Flight) {
        when (helper.itemViewType) {
            FlightListAdapterModel.TUIJIAN -> onBindHolderTuijian(helper, item)
            FlightListAdapterModel.FLIGHT -> onBindHolderFlight(helper, item)
        }
    }
    
  • 其他

    此外,还有基于BaseQuickAdapter的几种适用不同情况的Adapter,下面给出注释,在对应情况下可以使用。

    /**
     * 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 ItemProvider 中。
     * 适用于以下情况:
     * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[getItemType]中返回对应类型
     * 2、item 类型较多,在convert()中管理起来复杂
     *
     * ViewHolder 由 [BaseItemProvider] 实现,并且每个[BaseItemProvider]可以拥有自己类型的ViewHolder类型。
     *
     * @param T data 数据类型
     * @constructor
     */
    abstract class BaseProviderMultiAdapter<T>(data: MutableList<T>? = null) :
            BaseQuickAdapter<T, BaseViewHolder>(0, data) {
    
    /**
     * 快速实现带头部的 Adapter,由于本质属于多布局,所以继承自[BaseMultiItemQuickAdapter]
     * @param T : SectionEntity
     * @param VH : BaseViewHolder
     * @property sectionHeadResId Int
     * @constructor
     */
    abstract class BaseSectionQuickAdapter<T : SectionEntity, VH : BaseViewHolder>
    @JvmOverloads constructor(@LayoutRes private val sectionHeadResId: Int,
                              data: MutableList<T>? = null)
        : BaseMultiItemQuickAdapter<T, VH>(data) {
    
    /**
     * 多类型布局,通过代理类的方式,返回布局 id 和 item 类型;
     * 适用于:
     * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[BaseMultiTypeDelegate.getItemType]中返回对应类型
     * 2、item 类型较少
     * 如果类型较多,为了方便隔离各类型的业务逻辑,推荐使用[BaseBinderAdapter]
     *
     * @param T
     * @param VH : BaseViewHolder
     * @property mMultiTypeDelegate BaseMultiTypeDelegate<T>?
     * @constructor
     */
    abstract class BaseDelegateMultiAdapter<T, VH : BaseViewHolder>(data: MutableList<T>? = null) :
            BaseQuickAdapter<T, VH>(0, data) {
    
    /**
     * 使用 Binder 来实现adapter,既可以实现单布局,也能实现多布局
     * 数据实体类也不存继承问题
     *
     * 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 BaseItemBinder 中。
     * 适用于以下情况:
     * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,默认情况下不需要实现 getItemType
     * 2、item 类型较多,在convert()中管理起来复杂
     *
     * ViewHolder 由 [BaseItemBinder] 实现,并且每个[BaseItemBinder]可以拥有自己类型的ViewHolder类型。
     *
     * 数据类型为Any
     */
    open class BaseBinderAdapter(list: MutableList<Any>? = null) : BaseQuickAdapter<Any, BaseViewHolder>(0, list) {
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容