赞
踩
背景
一转眼,从一开始发布文章说带大家
封装Adapter
直到现在,过去半个月了吧,前后又仔细阅读了很多Adapter的框架源码,对Adapter的千变万化算是有了深刻的认识,真的是温故而知新,借着这次机会,我也将学到的这些优点一一的列出来告诉你,跟我一起重新认识Adapter
值得一看的Adapter开源项目
这些开源项目都有哪些相同点?
这几个项目最大的共同点是都对DataBinding做了扩展,看来DataBinding大家都很认可
有6个项目都用到了Kotlin,最高达到了96%,看来我们android小伙伴都爱学习,kotlin的普及率好高
还有一个点都对MultiItemType做了良好的扩展,说明这确实是一个Adapter库的标配
有三个项目对Paging库做了扩展,相信大家用它的机会越来越多
头尾布局几乎成了框架的标配
Kotlin DSL或多或少的支持,写法上简洁而优美
大多数都做了比较好的分库处理,按需依赖,不拖泥带水
我们如何做一个可以称之为全而好的Adapter框架呢?
我抽取几个关键词来告诉你
适当分包,按需依赖,提供良好的扩展性
kotlin支持 利用最近一个老铁的话:工欲善其事必先利其器,kotlin绝壁是一个好利器
DSL扩展 简洁的写法,利于阅读
DataBinding支持 不用质疑,有就对了
Paging扩展 在我看来Paging用了很多新的设计,值得我们去学习
头尾布局、空布局、上拉加载,拖动,动画 提供专门的包来扩展
DiffUtil扩展 好的工具不要忘记使用,总会有合适的应用场景
anko扩展 用了kotlin怎么能不用一下anko layout,300%的布局加载效率提高不香吗?
设计这么一个框架我的坚持是什么?
你有没有陷入过一个误区?自己封装了一个东西,特别的全,什么都支持,一股脑的往里面塞东西,这个人说我想这样,你就改成这样,那个人想那样,你又开始改?难道我们做个东西就是为了让别人指挥吗?答案肯定是:不。那应该从哪几个方面考虑呢?
扩展性 对的,你一定要做到可扩展,要不然你就要面临东改改,西改改的困境,如何做到可扩展呢?一条原则搞定:依赖倒转原则,尽量依赖于抽象,而不是依赖于实现,做到这一点其实还不够
可靠性 可以理解为,你做的东西不要经常变,特别是抽象的接口,在一开始我们就要做到完整,有的人说,我怎么肯能考虑那么完美呢?那就要考虑一下,你是不是走到了一个误区,接口的抽象在这样一个成熟的框架中,应该很好确定的,接口不要大而全,接口也要做到尽量的小,起码接口可以多继承啊,对吧。
里氏替换原则: 任何基类可以出现的地方,子类一定可以出现,这个原则其实好多人都不太理解,其实说白一点,用现实的例子告诉你,你继承了父亲的很多基因,那你能改变父亲的基因吗?肯定不能,可代码里是可以重写的(抽象方法不算哦),但这个原则就是告诉我们避免重写,一旦重写,父类就没有了意义,其实在工作中,有很多人喜欢往父类里抽象很多东西,还特别喜欢在不同的子类中覆盖后重写,这些都是不好的习惯,你不应该只是老三用到了一个东西,就要给所有的人加上这个东西,我感觉是个累赘,你觉得呢?我们在设计框架的时候,一定要遵循这些好的设计原则。
单一职责 一个类应该有且仅有一个职责,你还好意思封装一个类,啥功能都有吗?除非它叫Manager,但也只能管理一类东西,职责的单一处处可见,且要处处坚持。
讲了这些废话,你是不是不耐烦了,下面来点干货吧,看看Adapter的一些细节是如何封装的
Adapter 如何做到ItemViewType自动适配?我们是如何封装的?
看源码前,我想说的是,你要明白一个原理,其实ItemViewType它影响的就是ViewHolder的复用逻辑,只要是一样的ItemViewType,它就会触发复用,所以说ItemViewType封装的目的,其实是缓存ViewHolder然后复用,那我们如何做到自动处理加缓存呢?
由于我们要做到onCreateViewHolder的下移,而我们抽象了ViewModel层来负责组织View和Model,那么ViewHolder就成了我们View的载体,抽象一个ViewHolderFactory让ViewModel继承,达到onCreateViewHolder的调用下移
typealias GenericViewHolderFactory = ViewHolderFactory
interface ViewHolderFactory {
fun getViewHolder(parent: ViewGroup, layoutInflater: LayoutInflater): VH
}
再看两处代码,这是我们实现的两个函数,onCreateViewHolder回调的时候只给了viewType,那么我们就只能想办法,用Map缓存一个起来上面的ViewHolderFactory,这样就可以根据viewType拿到对应的ViewHolderFactory,是不是就完美了?
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
override fun getItemViewType(position: Int)
缓存ViewHolderFactory代码如下,第一步我先抽象一个ViewHolderFactoryCache接口,保证以后的扩展性,并默认实现一个DefaultViewHolderFactoryCache,提供注册、获取、检查、清理的方法
interface ViewHolderFactoryCache {
fun register(type: Int, item: VHF): Boolean
operator fun get(type: Int): VHF
fun contains(type: Int): Boolean
fun clear()
}
class DefaultViewHolderFactoryCache : ViewHolderFactoryCache {
private val typeInstances = SparseArray()
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()
}
}
然后再去Adapter里实现, 我选择让ViewModel实现ViewHolderFactory,缓存起来后,在onCreateViewHolder中再取出来然后赋值。
private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache>()
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 getItemViewType(position: Int): Int {
val item = getItem(position) ?: return 0
val type = item.itemViewType
if (!defaultViewHolderFactoryCache.contains(type)) {
item as ViewHolderFactory
defaultViewHolderFactoryCache.register(type, item)
}
return type
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
defaultViewHolderFactoryCache.clear()
sparseArray.clear()
}
在onDetachedFromRecyclerView后清理掉缓存数据。有个细节这里没看到,来看下
interface ViewModel> :
ViewHolderFactory {
var model: M?
var adapter: Adapter?
val itemViewType: Int
get() = layoutRes
@get:LayoutRes
val layoutRes: Int
fun bindVH(
viewHolder: VH,
model: M,
payloads: List
)
fun unBindVH(viewHolder: VH)
}
这是ViewModel,它其实也是个接口,但在Kotlin里接口是可以有实现的(kotlin的优势就在这),所以我让itemViewType默认就是layoutRes,我人为这种抽象的实现,完全合理,不同的layout布局就是不同的ViewHolder,而既然都是一样的layout,ViewHolder也完全可以一样对吗?对的。
如何做一个通用的ViewModel,如何实现DSL呢?
通用的ViewHolder就不用说了,在之前的博客里已经分析了,想看的请转一个资深的Android是不是应该学会自己做一个超级的RecyclerView.Adapter,这里面的是初期封装实现的,底层我做了优化,暂且只看ViewHolder,那ViewModel应该如何做呢?我们来看下上面的ViewModel接口,分析一下它的职责,如下
继承ViewHolderFactory,负责getViewHolder
bindVH 负责绑定数据
unBindVH 解绑时触发
layoutRes 负责布局的引用
itemViewType 默认就是布局的应用id
持有Model数据,可以是任何类型
持有adapter 需要继承自IAdapter
其实对于使用者来说,最需要做的就是,将业务的Model绑定到ViewModel上,并接受bindVH回调来实现绑定,配置一个layoutRes就完事儿了,下面直接看下继承的实现
typealias DefaultViewModelType = ViewModel
abstract class DefaultItemViewModel> : DefaultViewModelType {
override var adapter: A? = null
override var model: M? = null
private var bindView: BindView? = null
private var bindViewPayload: BindViewPayload? = null
private var itemClick: ItemClick? = null
open fun onBindViewHolder(f: (DefaultViewHolder) -> Unit) {
bindView = f
}
open fun onBindViewHolder(f: (DefaultViewHolder, Any) -> Unit) {
bindViewPayload = f
}
open fun onItemClick(f: (viewModel: ArrayItemViewModel, 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,
this
)
}
}
}
override fun bindVH(viewHolder: DefaultViewHolder, model: M, payloads: List) {
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 = DefaultItemViewModel
open class ArrayItemViewModel(override val layoutRes: Int) : ArrayViewModelType()
实现绑定有两个部分,一个是有payloads情况,我们回调函数bindViewPayload,否则回调bindView函数,这样可以实现ItemView的局部刷新,业务层调用onBindViewHolder传入一个高级函数,这个高级函数最终赋值给bindView,然后接收回调就可以,这里用了通用的DefaultViewHolder,初始化后直接setOnClickListener,为什么这么做:就是为了防止业务端出现多次的setOnClickListener,一次设置终身收益,这里DefaultItemViewModel>特意给Adapter留了扩展的入口,因为我们的Adapter会有好几个,但都可以复用这块的逻辑,ArrayItemViewModel就是针对ArrayListAdapter的一个扩展,下面做一个DSL支持,请看代码
fun arrayItemViewModelDsl(
layoutRes: Int,
init: ArrayItemViewModel.() -> Unit
): ArrayItemViewModel {
return ArrayItemViewModel(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
}
第一步扩展ArrayItemViewModel,第二步扩展ArrayListAdapter,第三扩展Adapter抽象类,绑定到RecyclerView,来看下使用的效果
arrayListAdapter {
//循环添加ItemViewModel
(0..10).map {
add(
// ItemViewModel 对象 函数中传入布局IdRes
arrayItemViewModelDsl(if (it % 2 == 0) R.layout.item_test else R.layout.item_test_2) {
// Model 数据模型
model = ModelTest("title$it", "subTitle$it")
// 绑定数据
onBindViewHolder { viewHolder ->
viewHolder.getView(R.id.tv_title)?.text = model?.title
viewHolder.getView(R.id.tv_subTitle)?.text = model?.subTitle
}
// 点击处理
onItemClick { vm, vh ->
//这里需要注意,为什么直接从该对象获取的Model是不正确的?因为ViewHolder的复用
//导致click事件其实是在另外一个VM里触发的
Log.d("arrayItemViewModel", "不正确的model${model}")
Log.d("arrayItemViewModel", "正确的model${vm.model}")
Log.d("arrayItemViewModel", "adapter$adapter")
Log.d("arrayItemViewModel", "viewHolder${vh.adapterPosition}")
//修改Model数据
vm.model?.title = "测试更新"
//用Adapter更新数据
adapter?.set(vh.adapterPosition, vm)
}
}
)
}
// 绑定 RecyclerView
into(rv_list_dsl)
}
看完例子,有没有觉得还行?我倒是觉得还Ok吧。
如何扩展成Anko Layout呢?
我们都知道Anko layout可以提升UI的加载效率,减少cpu的使用,这里引用一个大佬的总结,来看下
从xml 到 运行 再到 读取xml文件 最终到生成UI元素,最主要的就是经历了文件流的读取
而DSL直接编辑代码测量布局然后绘制,省了这么多的步骤,能不快吗?到底快多少呢?来看另一张图,一个大佬的测试
最低的手机型号都快了近359%,这是什么操作?流不流逼?我们先不说DSL的写法极大的高了加载效率,它还有其他优势,DSL写起来也比XML简洁易懂,来看个例子
class AnkoLayoutComponent(private val ankoListAdapter: ArrayListAdapter) : AnkoComponent {
override fun createView(ui: AnkoContext) = with(ui) {
verticalLayout {
recyclerView {
bindListAdapter(ankoListAdapter)
}.lparams(matchParent) {
weight = 1F
}
// Anko 兼容 xml布局的加载
include(R.layout.include_button_bottom)
}
}
}
这个例子,我想告诉你的是,anko layout 不光是它的独有的写法,还完全兼容XML,这也给那些想慢慢过渡的朋友,一个好的方式。那么主题来了,如何扩展Adapter 也用上这个呢?其实关键点就是View,来看下AnkoComponent接口
interface AnkoComponent {
fun createView(ui: AnkoContext): View
}
再看下ViewHolder构造函数,这不是正好吗?AnkoComponent生成的View,直接给ViewHolder不就可以直接跳过XML吗?
public ViewHolder(@NonNull View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
}
this.itemView = itemView;
}
实现代码如下,直接继承ArrayItemViewModel,然后实现getViewHolder函数,最终通过AnkoComponent的实例对象createView,就是这么简单。
public abstract class AnkoItemViewModel>
extends ArrayItemViewModel {
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);
}
}
用法示例
class AnkoViewModelTest : AnkoItemViewModel() {
init {
onBindViewHolder { viewHolder ->
getAnkoView(viewHolder).tvTitle?.text = model?.title
getAnkoView(viewHolder).tvSubTitle?.text = model?.subTitle
getAnkoView(viewHolder).itemClick = {
Log.d("AnkoViewModelTest", "正确的model${model}")
Log.d("AnkoViewModelTest", "正确的model${model}")
Log.d("AnkoViewModelTest", "adapter$adapter")
Log.d("AnkoViewModelTest", "viewHolder${viewHolder.adapterPosition}")
model?.title = "点击更新"
adapter?.set(viewHolder.adapterPosition, this)
}
}
}
override fun onCreateView(): AnkoItemView {
return AnkoItemView()
}
}
class AnkoItemView : AnkoComponent {
...
}
继承AnkoItemViewModel,配置一下Model和AnkoView就行了,就是这么的简单,然后在Adapter里这么使用,添加一个对应的实例就行了
listAdapter.add(AnkoViewModelTest().apply {
model = ModelTest("标题${++index}", "副标题")
})
扩展了这么多,总体上是如何设计的?
interface IAdapter {
fun getItem(position: Int): VM?
}
最上层IAdapter接口,只有一个函数通过positon 获取ViewModel,中间层 ListAdapter
abstract class ListAdapter, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter(), IAdapter {
private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache>()
private val sparseArray = SparseArray(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) {
if(position != RecyclerView.NO_POSITION){
// Do your binding here
holder.itemView.setTag(R.id.list_adapter, this)
val item = getItem(position) as? ViewModel>
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
defaultViewHolderFactoryCache.register(type, item)
}
return type
}
override fun onViewRecycled(holder: VH) {
(holder.itemView.getTag(R.id.list_adapter_item) as ViewModel).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()
}
}
为什么这么设计,我说几个理由
第一 在getItemViewType的时候,参数就是position,这样我就可以通过postion拿到ViewModel,通过ViewModel就可以实现ViewHolderFactory,然后通过DefaultViewHolderFactoryCache缓存ViewHolder就行了。
第二 ListAdapter只干了很简单的事情,缓存ViewHolder,并在onBindViewHolder的时候,触发ViewModel的bindVH。职责上尽量简单,才好复用。
第三 集成它以后只需要扩展对应的数据结构,如ArrayListAdapter的ObservableArrayList,SortedListAdapter的SortedList,还有PagingListAdapter的AsyncPagingDataDiffer,这样面临不同的数据接口,不断扩展就行。
这样设计好吗?是否可以将不同的数据结构抽象成一种呢?不都是列表吗?还能咋地?答案是可以的,但我为什么没有这么做呢?第一个理由,分包处理,按需依赖,保持架构的足够清晰简洁,易懂易维护,还有个理由这三个数据结构说白了都是对数据列表做了不同程度的封装,ObservableArrayList主要就是扩展了ArrayList实现数据更新回调,然后触发Adapter更新,SortedList就是个工具类,没有扩展List,它一直保持着一个有序的列表,而且用到了二分查找算法来实现快速的定位更新,AsyncPagingDataDiffer就更多了,线程、分页、状态、比较等,更复杂的功能封装。三个Adapter的代码就不贴了,可以去看源码哦?
通过封装PagingListAdapter,发现了新大陆,想知道吗?
接下来就揭晓,为什么我不着急封装空布局、头尾布局、上啦加载等常用业务组件?
第一 看了Paging 3 版本的PagingDataAdapter后,我发现一个更好的头尾布局实现方案
第二 这些组件的封装,其实无形中加大了框架的复杂度,而更重要的原因是,不同的App,这些都是需要自己实现的,那么如何提供这样一个扩展入口才是最主要的,而不是实现这些功能
基于这些,我更想提供一些例子来让你更加容易实现想要的,而不是按照我的接口规范来,如果我的接口规范不好不完整,你还不得不换其他的框架,这岂不是让你很尴尬,你们有没有遇到一个项目引用好几个Adapter框架?肯定有吧。
下面我们看下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)
}
这是PagingDataAdapter 提供的三个类函数,发现了什么?ConcatAdapter,对的就这个东西。再往里看下
public final class ConcatAdapter extends Adapter
就是对RecyclerView.Adapter 的一个扩展,官方解释ConcatAdapter允许我们按顺序显示多个适配器的内容,也就是将原先绑定一个Adapter,现在变成了可以按顺序绑定多个,这样组合实现,是不是很新颖,我之前接触过一个WrapperAdapter,也是类似的设计,但我那个是这样
这种方案也是大多数热门Adapter框架解耦的方法,利用WrapperAdapter可以实现头尾布局,空布局等等吧。可当我接触到ConcatAdapter后发现还有这种设计
这种方式更适合做头尾布局的扩展,完全的解耦,也不需要WrapperAdapter来实现方法的回调。最开心的是ConcatAdapter在RecyclerView库的1.2.0版本就有了,以后我们可以基于这个做很多事情,包括上啦加载,下拉加载等,Paging的头尾状态如何控制的呢?
abstract class LoadStateAdapter : RecyclerView.Adapter() {
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
}
}
同样实现RV的Adapter,抽象一个LoadState状态,然后根据它的状态,notifyItemRemoved(0)或者notifyItemInserted(0)或者notifyItemChanged(0),那我们未来是不是也可以这样做呢?完全可以,而且我建议这样做,组合实现,跟插件一样,随用随插。不浪费感情。
讲了这么多源码在哪?
如何引用呢?
allprojects {
repositories {
// 首先项目根目录的build.gradle文件中加入这一行
maven { url 'https://jitpack.io' }
}
}
//核心库
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-core:V1.0.0
//下面都是可选项
//anko layout 扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-anko:V1.0.0
//diffutil 扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-diff:V1.0.0
//data binding扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-binding:V1.0.0
// paging3 扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-paging:V1.0.0
// sortedlist 扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-sorted:V1.0.0
// flexbox 扩展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-flex:V1.0.0
未来还有什么计划吗?
有,肯定有,第一不断的完善设计和实现,第二不断的聆听你们的建议或者批评,哪里设计的不好的请您大胆的告诉我,正所谓知错能改善莫大焉,我不入地狱,谁入地狱?
好了这次就到这了,拜拜
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。