Square Cycler – RecyclerView API

Square Cycler – RecyclerView API

前言

不用adapter和ViewHolder也可以使用RecycleView,你知道吗?
Cycler,是Square的开源项目,目的是以简洁的方式使用配置Android RecycleView。将Adapter 和ViewHoler,DiffUtil,ItemTouchHelper.Callback等封装起来。使用时,不用创建adapter,viewHoler,方便快捷。Update类维护数据源,且在内部刷新数据时,会去计算数据源的差异,调用不同的adapter 的刷新数据的方法。

首先配上官方使用demo

demo1:

   override fun config(recyclerView: RecyclerView) {
   // 返回的是Recycler
   cycler = Recycler.adopt(recyclerView) {
    //根据StandardRowSpec会创建ViewHolder
    //Item ,Discount,GrandTotal 都是数据Bean
    //ItemView,ConstraintLayout ,TextView 是数据bean 对应的ItemView

     row<Item, ItemView> {
       create { context ->
         view = ItemView(context, showDragHandle = false)
         bind { index, item ->
           view.show(item, index % 2 == 0)
         }
       }
     }

     row<Discount, ConstraintLayout> {
         //自定义的匹配规则
       forItemsWhere { it.isStarred }
       create(R.layout.starred_discount) {
         val amount = view.findViewById<TextView>(R.id.amount)
         bind { discount -> amount.text = discount.amount.format() }
       }
     }

     row<Discount, ConstraintLayout> {
       create(R.layout.discount) {
         val amount = view.findViewById<TextView>(R.id.amount)
         bind { discount -> amount.text = discount.amount.format() }
       }
     }

     extraItem<GrandTotal, TextView> {
       create { context ->
         view = TextView(context)
         view.setTextAppearance(R.style.TextAppearance_Bold)
         bind { total ->
           view.text = "Grand total: ${total.total.format()}"
         }
       }
     }
   }
   update()
 }

 // 更新数据
 private fun update() {

   val total = data
       .sumByFloat { it.amount }
       .coerceAtLeast(0f)

   cycler.update {
       // toDataSource 生成DataSource<T>,复制给Update的data成员变量
     data = this@SimplePage.data.toDataSource()
     extraItem = if (showTotal) GrandTotal(total) else null
   }
 }
}

这样就使用了一个简单的Recycleview,三个不同类型的ItemView和一个扩展的ItemView。

在这里插入图片描述

demo2:

 cycler = Recycler.adopt(recyclerView) {
      row<Item, ItemView> {
        create { context ->
          view = ItemView(context, showDragHandle = true)
          //设定ItemView拖动的View
          dragHandle(view.dragHandle)
          bind { index, item ->
            view.show(item, index % 2 == 0)
          }
        }
      }

      enableMutations {
        // 上下拖动的开关
        dragAndDropEnabled = true
        //左右移除的开关
        swipeToRemoveEnabled = true
        
        onMove { from, to ->
          Toast.makeText(
              recyclerView.context,
              "Moved index $from to index $to",
              Toast.LENGTH_SHORT
          ).show()
        }

        //左右移除的回调函数
        onSwiped { it ->
          Toast.makeText(
              recyclerView.context,
              "Swiped ${it.name} away",
              Toast.LENGTH_SHORT
          ).show()
        }
      }
    }
    update()

这样简单的几句,就创建了Recyclview并且可以左右移除,和上下拖拽

在这里插入图片描述

Recycler类

内部维护adapter和Config等

创建Recycler

创建Recycler有两种方式:create 和 adopt 都是Recycler的静态方法,返回值是 Recycler
  1. 如果项目中不存在RecycleView
    使用create()方法,创建RecycleView,并且Recycler和RecycleView相关联

    @param context 上下文环境
    @param id  为RecyclewView指定id
    @param layoutProvider 为RecyclewView指定布局管理器
    @param block Recycle的一些配置函数
    inline fun <I : Any> create(
      context: Context,
      id: Int = View.NO_ID,
      noinline layoutProvider: (Context) -> LayoutManager = { LinearLayoutManager(it) },
      crossinline block: Config<I>.() -> Unit
    ): Recycler<I> {
      val view = RecyclerView(context)
      view.id = id
      return adopt(view, layoutProvider, block)
    }
    
  2. 如果项目中存在RecycleView
    使用adopt() 将Recycler和RecycleView相关联。并且create最后也调用的是adopt()

    @param view RecycleView
    @param layoutProvider 为RecyclewView指定布局管理器
    @param block Recycle的一些配置函数
    inline fun <I : Any> adopt(
        view: RecyclerView,
        noinline layoutProvider: ((Context) -> LayoutManager)? = null,
        crossinline block: Config<I>.() -> Unit
        ): Recycler<I> {
        layoutProvider?.invoke(view.context)
            .let { view.layoutManager = it }
        requireNotNull(view.layoutManager) {
            "RecyclerView needs a layoutManager assigned. " +
                "Assign one to the view, or pass a layoutProvider argument."
        }
        return Config<I>()
            .apply(block)
            .setUp(view)
        }
    

    注意: 如果在adopt 中不去设置LayoutManager会崩溃。而我们在以前用RecycleView,忘记设置LayoutManager,为空白。

Config 配置块

config 配置块将具有一些常规配置,例如数据比较器,每种itemView类型的定义。以及用来将数据bean和itemview 相关联.(核心)
class Config<I : Any> @PublishedApi internal constructor() {

   // rowSpecs 储存在config中创建的StandardRowSpec,后面会根据这个生成ViewHolder
   @PublishedApi internal val rowSpecs = mutableListOf<RowSpec<I, *, *>>()
   // extraItemSpecs 格外的ItemView
   @PublishedApi internal val extraItemSpecs = mutableListOf<RowSpec<Any, *, *>>()
   internal val extensionSpecs = mutableListOf<ExtensionSpec<I>>()

   
   internal var hasStableIdProvider = false
   // 提供一个id比较函数
   internal var stableIdProvider: StableIdProvider<I> = {
     throw java.lang.IllegalStateException("stableIdProvider not set.")
   }
   // 提供内容比较器。stableIdProvider和contentComparator都定义,将会生成一个默认的比较器
   internal var contentComparator: ContentComparator<I>? = null

   /**
    * 更新数据必须在创建ViewRootImpl的线程中(通常是UI线程)
    */
   var mainDispatcher: CoroutineDispatcher = Dispatchers.Main

   /**
    * 设定比较数据的线程,默认是IO线程
    * 
    */
   var backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO

   /**
    * 提供stableIdProvider和contentComparator就会生成itemComparator比较器,用于比较数据
    * 
    */
   var itemComparator: ItemComparator<I>? = null

   /** 
    * 提供stableIdProvider 自定义,为每个item数据提供一个识别号(一般是id)
    */
   fun stableId(block: StableIdProvider<I>) {
     stableIdProvider = block
     hasStableIdProvider = true
   }

   /** 
    * 提供 内容比较 lambda 
    * stableId 和compareItemsContent 必须都调用,才能生成比较器
    */
   fun compareItemsContent(block: ContentComparator<I>) {
     contentComparator = block
   }

   /**
    * 
    * 生成StandardRowSpec ,并且添加到rowSpecs
    *  生成ItemView (函数重载)
    */
   inline fun <reified S : I, reified V : View> row(
     crossinline specBlock: StandardRowSpec<I, S, V>.() -> Unit
   ) {
     row(
         StandardRowSpec<I, S, V> { it is S }.apply {
           specBlock()
         }
     )
   }

   /**
    * 生成ItemView (函数重载)
    */
   inline fun <reified S : I, reified V : View> row(
     noinline creatorBlock: (CreatorContext) -> V,
     noinline bindBlock: (Int, S, V) -> Unit
   ) {
     // Converts the bindBlock in a BinderRowSpec block.
     row<S, V>(creatorBlock) { -> bind(bindBlock) }
   }

   /**
    * 生成ItemView (函数重载)
    */
   inline fun <reified S : I, reified V : View> row(
     noinline creatorBlock: (CreatorContext) -> V,
     crossinline specBlock: BinderRowSpec<I, S, V>.() -> Unit
   ) {
     row(
         BinderRowSpec<I, S, V>(
             typeMatchBlock = { it is S },
             creatorBlock = creatorBlock
         ).apply { specBlock() }
     )
   }

   /**
    * 定义格外项
    */
   inline fun <reified S : Any, reified V : View> extraItem(
     crossinline specBlock: StandardRowSpec<Any, S, V>.() -> Unit
   ) {
     extraItem(
         StandardRowSpec<Any, S, V> { it is S }.apply {
           specBlock()
         }
     )
   }

   /**
    * 生成比较器
    * 提供stableIdProvider和contentComparator 则生成,否则为null
    */
   internal fun createItemComparator(): ItemComparator<I>? {
     return itemComparator ?: stableIdProvider.let { ids ->
       contentComparator?.let { contentComp ->
         DefaultItemComparator(ids, contentComp)
       }
     }
   }

   fun row(rowSpec: RowSpec<I, *, *>) {
     rowSpecs.add(rowSpec)
   }

   @PublishedApi internal fun extraItem(rowSpec: RowSpec<Any, *, *>) {
     extraItemSpecs.add(rowSpec)
   }

   /**
    * 添加回调函数,(比如:左右移除ItemView,上下移动ItemView)
    * 不能直接调用
    */
   fun extension(spec: ExtensionSpec<I>) {
     extensionSpecs.add(spec)
   }

   /**
    * 
    * 生成Recycler
    */
   @PublishedApi internal fun setUp(view: RecyclerView): Recycler<I> {
     return Recycler(view, this)
   }
 }
用例代码如下:
    val block: Recycler.Config<BaseItem>.() -> Unit = {

        //生成ItemView ,将ItemView和数据源进行绑定
       row<Item, ItemView> {
           create { context ->
               view = ItemView(context, showDragHandle = false)
               bind { index, item ->
               view.show(item, index % 2 == 0)
               }
           }
       }

       //定义不同的 ItemView
       row<Discount, ConstraintLayout> {
           //生成ItemView
           create(R.layout.discount) {
               val amount = view.findViewById<TextView>(R.id.amount)
               // 数据进行绑定
               bind { discount -> amount.text = discount.amount.format() }
           }
       }

        //定义不同的 ItemView
       row<Discount, ConstraintLayout> {
           // 自定义的匹配规则
           forItemsWhere { true }
           create(R.layout.starred_discount) {
               val amount = view.findViewById<TextView>(R.id.amount)
               bind { discount -> amount.text = discount.amount.format() }
           }
       }

       // 格外的ItemView  只可以定义一个
       extraItem<GrandTotal, TextView> {
       create { context ->
           view = TextView(context)
           view.setTextAppearance(R.style.TextAppearance_Bold)
           bind { total ->
           view.text = "Grand total: ${total.total.format()}"
           }
       }
       }
   }
Config.row()中会生成StandardRowSpec,并且调用rowSpecs.add()
StandardRowSpec.create() 生成Creator(内部维护View 和bind 函数);

Item ,Discount,GrandTotal 都是数据Bean
ItemView,ConstraintLayout ,TextView 是数据bean 对应的ItemView

StandardRowSpec extends RowSpec ,内部有ViewHolder.我们在config内部定义的行。
在Adapter的onCreateViewHolder 会去调用StandardRowSpec.onCreateViewHolder 
生成一个Viewholder。每一个行都会对应生成一个Viewholder。

在这里插入图片描述

数据更新

Update 类

数据更新的类
维护两个数据 一个是data ,ItemView 的数据源
另一个是extraItem ,扩展ItemView 的数据源
toDataSource() 是一个扩展函数,生成DataSource<T> DataSoure 用来判断数据源是否有效 
示例用法:
        // 将数据赋值给Update 类
        cycler.update {
        data = this@SimplePage.data.toDataSource()
        extraItem = GrandTotal
        }

RecyclerDiff 类

/** 
 * 实现自Android 的DiffUtil.Callback()
 */
class DataSourceDiff<T>(
  private val helper: ItemComparator<T>,
  private val oldList: DataSource<T>,
  private val newList: DataSource<T>
) : DiffUtil.Callback() {

  // 得到就旧数据大小
  override fun getOldListSize(): Int = oldList.size
  // 得到新数据大小
  override fun getNewListSize(): Int = newList.size
  // 新老数据集Item是否是同一个 (一般比较的id)
  override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
    helper.areSameIdentity(oldList[oldItemPosition], newList[newItemPosition])
  //比较同一个item 的内容是否相同(上面方法返回true 时,会调用此方法)
  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
    helper.areSameContent(oldList[oldItemPosition], newList[newItemPosition])

  //Object就是表示Item改变了哪些内容(上面方法返回false 时,会调用此方法)
  override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) = null
}

/**
 * 如果我们定义了 idsProvider & contentComparator 函数,Adapter在init时候就会去创建这个Item比较器
 *
 */
internal class DefaultItemComparator<I>(
  val idsProvider: StableIdProvider<I>,
  val contentComparator: ContentComparator<I>
) : ItemComparator<I> {
  override fun areSameIdentity(
    oldItem: I,
    newItem: I
  ): Boolean {
    return idsProvider(oldItem) == idsProvider(newItem)
  }

  override fun areSameContent(
    oldItem: I,
    newItem: I
  ): Boolean {
    return contentComparator(oldItem, newItem)
  }
}

在这里插入图片描述

  1. currentUpdate = new Update()
  2. 调用更新上的函数
  3. 异步计算 currentData 和 CurrentUpdate 差异 (利用DiffUtil 传入数据,根据比较结果的不同,调用不同的更新函数)

ItemView的回调函数

为itemView设置上下拖动,左右移除的回调函数

示例用法:
    /**
    * 
    * 在Config 中进行配置  使用比较方便
    */
  enableMutations {
        // 拖拽开关
        dragAndDropEnabled = true
        // 左右移除开关
        swipeToRemoveEnabled = true
        // 长按开关 if(没有设置draghandle ,打开这个开关,长按上下拖动才有效。反之,如果设置了dranghangle ,则不需要打开这个开关,点击设置的view ,就可以上下拖动)
        longPressDragEnabled = true

        // 上下拖拽 回调的函数
        onDragDrop{originalIndex, dropIndex ->
          Toast.makeText(
              recyclerView.context,
              "onDragDrop ${originalIndex} away",
              Toast.LENGTH_SHORT
          ).show()
        }

       // 左右移除 回调的函数
        onSwiped { it ->
          Toast.makeText(
              recyclerView.context,
              "Swiped ${it.name} away",
              Toast.LENGTH_SHORT
          ).show()
        }

在这里插入图片描述

MutationExtension 中成员变量 MyItemTouchCallback
MyItemTouchCallback 继承 ItemTouchHelper.Callback,当onMove,onSwiped 回调MyItemTouchCallback.函数,在MyItemTouchCallback中调用MutationExtension中定义的函数

Java调用

Recycler 底层是kotlin写的
调用时用kotlin比较方便,用Java涉及到大量的Fuction 函数( lambda),代码不优雅美观,缺乏可读性。

  // 第一个行的定义
  StandardRowSpec<BaseItem, BaseItem.Discount, ItemView> rowSpec1 = new StandardRowSpec<BaseItem, BaseItem.Discount, ItemView>(baseItem -> true);
  rowSpec1.forItemsWhere(discount -> !discount.isStarred());
  rowSpec1.create(R.layout.discount, creator -> {
      TextView textView = ((StandardRowSpec.Creator) creator).getView().findViewById(R.id.amount);
      ((StandardRowSpec.Creator) creator).bind(o -> {
          if (o != null && o instanceof BaseItem.Discount) {
              textView.setText(String.valueOf(((BaseItem.Discount) o).getAmount()));
          }
          return null;
      });

      return null;
  });

  // 第二个行的定义
  StandardRowSpec<BaseItem, BaseItem.Discount, ConstraintLayout> rowSpec2 = new StandardRowSpec<BaseItem, BaseItem.Discount, ConstraintLayout>(baseItem ->
          true
  );

  rowSpec2.forItemsWhere(discount -> discount.isStarred());
  rowSpec2.create(R.layout.starred_discount, creator -> {
      if (creator instanceof StandardRowSpec.Creator) {
          TextView textView = creator.getView().findViewById(R.id.amount);
          ((StandardRowSpec.Creator) creator).bind(o -> {
              if (o instanceof BaseItem.Discount) {
                  textView.setText(String.valueOf(((BaseItem.Discount) o).getAmount()));
              }
              return null;
          });
      }
      return null;
  });


  Recycler<BaseItem> recycler = Recycler.Companion.adopt(recyclerView,
          context -> new LinearLayoutManager(context, RecyclerView.VERTICAL, false),
          config -> {
              config.row(rowSpec1);
              config.row(rowSpec2);
              return null;
          });

  recycler.update(update -> {
              if (update instanceof Update) {
                  ((Update) update).setData(new DataSource() {
                      @Override
                      public boolean isEmpty() {
                          return list.isEmpty();
                      }

                      @Override
                      public Object get(int i) {
                          return list.get(i);
                      }

                      @Override
                      public int getSize() {
                          return list.size();
                      }
                  });
              }
              return null;
          }
  );
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值