├── .gitignore ├── README.md ├── adapters ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wang │ │ └── adapters │ │ ├── adapter │ │ ├── BaseAdapter.kt │ │ ├── BaseExpandableAdapter.kt │ │ ├── BaseFragmentPager2Adapter.kt │ │ ├── BaseListAdapter.kt │ │ ├── BaseListCycleAdapter.kt │ │ └── BaseMultiItemAdapter.kt │ │ ├── container │ │ ├── BaseContainerAdapter.kt │ │ ├── bean │ │ │ ├── IContainerBean.kt │ │ │ └── ItemAdapterPositionInfo.kt │ │ ├── item │ │ │ ├── BaseContainerItemAdapter.kt │ │ │ └── OneContainerItemAdapter.kt │ │ └── observer │ │ │ └── IContainerObserver.kt │ │ ├── helper │ │ └── ListAdapterHelper.kt │ │ ├── holder │ │ └── BaseViewHolder.kt │ │ ├── interfaces │ │ ├── IAdapter.kt │ │ ├── IHeaderFooterListAdapter.kt │ │ └── IListAdapter.kt │ │ └── utils │ │ ├── BaseAdapterExt.kt │ │ ├── Exts.kt │ │ ├── ViewBindingHelper.kt │ │ ├── ViewExt.kt │ │ ├── ViewGroupWrapUtils.kt │ │ └── ViewHolderExt.kt │ └── res │ └── values │ └── ids.xml ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wang │ │ └── example │ │ └── MainActivity.kt │ └── res │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── adapter_main_header.xml │ ├── adapter_main_list.xml │ ├── adapter_main_multiple_0.xml │ ├── adapter_main_multiple_1.xml │ ├── adapter_main_multiple_def.xml │ ├── adapter_main_nes.xml │ └── adapter_main_nes_item.xml │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.apk 3 | *.zip 4 | local.properties 5 | *.hprof 6 | /captures 7 | ~$* 8 | .DS_Store 9 | .idea 10 | .gradle 11 | build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 代码非常简单,基于dataBinding 2 | 3 | ### 概览 4 | 5 | 本项目主要做了RecyclerView adapter、ListView adapter、ViewPager adapter fragment的封装,旨在减少大量模板代码。 6 | 7 | 也加了和这些相关连带问题的解决方法:adapter套RecyclerView回调繁琐、动态fragment刷新白屏、ViewPager和ViewPager2高度不能wrap等问题的解决 8 | 9 | ### 背景介绍 10 | 11 | Adapter属于View层,Activity也是View层,在当前各种新框架下两个几乎没有什么代码,然而它们之间交互起来特别繁琐,如果我们直接将Adapter合并到Activity里,会产生什么意想不到的结果呢? 12 | 13 | ### 详细介绍 14 | 15 | 一个简单的listAdapter只需要如下一行(没看错,总共就一行),基于ViewBinding 16 | 17 | ``` 18 | class MainActivity : AppCompatActivity() { 19 | //lazyNone就是lazy(LazyThreadSafetyMode.NONE, initializer)的拓展 20 | private val listAdapter by lazyNone { 21 | createListVbAdapter { holder, vb, bean -> } 22 | } 23 | } 24 | ``` 25 | 26 | 点击事件什么的都不需要额外操作,没有回调也不需要额外传参,直接在Adapter写就行了 27 | 28 | ``` 29 | class MainActivity : AppCompatActivity() { 30 | private val listAdapter by lazyNone { 31 | createListVbAdapter { holder, vb, bean -> 32 | holder.itemView.setBackgroundColor(if (bean.contains("10")) 0xff999999.toInt() else 0xffffffff.toInt()) 33 | vb.tvText.text = bean 34 | vb.btButton.setOnFastClickListener { 35 | toast("点击了Button") 36 | } 37 | holder.setOnFastClickListener { 38 | toast("点击item了${holder.listPosition}") 39 | } 40 | }.apply { 41 | //自带header、footer 42 | headerView = AppCompatTextView(this@MainActivity).apply { 43 | text = "这是header" 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ### 更精简的条目写法 51 | 52 | 只有2个方法:addMultipleItem、addDefaultMultipleItem 53 | 54 | addMultipleItem的isThisTypeCallback返回一个布尔值表示是否是当前条目,不需要额外其他逻辑 55 | 56 | 小提示:对于addMultipleItem这种多参数idea无法自动快速提示出来,可以使用“Ctrl+空格”来主动弹出。但是由于Windows占用了这个快捷键,请打开“设置> 57 | Keymap>Main Menu>Code>Code Completion>Basic”来重新设置,比如:“Ctrl+;”、“Ctrl+,” 58 | 59 | ``` 60 | private val multiAdapter by lazyNone { 61 | createMultiAdapter().apply { 62 | addMultipleItem(isThisTypeCallback = { listPosition, _ -> listPosition % 3 == 0 }) { holder, vb, bean -> 63 | vb.tvText.setTextColor(0xff00ff00.toInt()) 64 | vb.tvText.text = "多条目0:${bean.text}" 65 | holder.setOnFastClickListener { 66 | toast("点击item了${holder.listPosition}") 67 | } 68 | } 69 | addMultipleItem(isThisTypeCallback = { listPosition, _ -> listPosition % 3 == 1 }) { holder, vb, bean -> 70 | vb.tvText2.text = "多条目1:${bean.text}" 71 | } 72 | //多条目兜底,上面判断完后的剩余情况防止出错,如:后端新增了新的数据类型 73 | addDefaultMultipleItem() 74 | 75 | headerView = AppCompatTextView(this@MainActivity).apply { text = "这是header" } 76 | footerView = AppCompatTextView(this@MainActivity).apply { text = "这是footer" } 77 | } 78 | } 79 | ``` 80 | 81 | ### 嵌套?直接正常写就行了 82 | 83 | 注意:嵌套效率问题暂且不在当前讨论范围内 84 | 85 | ``` 86 | private val nesAdapter by lazyNone { 87 | createListVbAdapter { holder, vb, bean -> 88 | vb.tvNesText.text = "这是嵌套外层:${bean.text},${holder.listPosition},${holder.adapterLayoutPosition}" 89 | val itemAdapter = vb.rvItemList.getAdapterOrCreate { 90 | createListVbAdapter { holder, vb, bean -> 91 | vb.tvItem.text = "这是内层$bean,${holder.listPosition},${holder.adapterLayoutPosition}" 92 | }.apply { 93 | headerView = AppCompatTextView(this@MainActivity).apply { 94 | text = "这是内层header" 95 | setOnFastClickListener { 96 | toast("你点击了内层header") 97 | } 98 | } 99 | footerView = AppCompatTextView(this@MainActivity).apply { text = "这是内层footer" } 100 | vb.rvItemList.setGridLayoutManagerSpanSizeLookup { 101 | when (it) { 102 | 0, (itemCount - 1) -> 2 103 | else -> 1 104 | } 105 | } 106 | } 107 | } 108 | itemAdapter.notifyDataSetChanged(bean.itemTextList) 109 | }.apply { 110 | headerView = AppCompatTextView(this@MainActivity).apply { 111 | text = "这是外层header" 112 | setTextColor(0xff00ff00.toInt()) 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ### 当然也可以使用原始继承方式(不推荐) 119 | 120 | 不推荐这么写,因为需要来回处理页面交互 121 | 122 | ``` 123 | class MyAdapter: BaseListAdapter(/*可选传list*/) { 124 | override fun onBindListViewHolder(holder: BaseViewHolder, bean: String){ 125 | //bind 126 | } 127 | 128 | // 按混淆配置好,这里就不需要重写 129 | // override fun onCreateListViewHolder(parent: ViewGroup): BaseViewHolder = 130 | // BaseViewHolder(ViewBindingHelper.getViewBindingInstanceByClass(parent.layoutInflater, parent)) 131 | } 132 | ``` 133 | 134 | ### ViewPager的Fragment更简单 135 | 136 | ``` 137 | mVp.setAdapter(new BaseFragmentPagerAdapter(getSupportFragmentManager(), mFrags)); 138 | //或 139 | mVp.setAdapter(new BaseFragmentPagerAdapter(getSupportFragmentManager(), frag1,frag2...)); 140 | //动态修改frag 141 | mAdapter = new BaseFragmentStatePagerAdapter(getSupportFragmentManager(), mFrags); 142 | mVp.setAdapter(mAdapter); 143 | ... 144 | mAdapter.getFragments().add(xxx);//由于内部有新的list,所以并不能用自己的mFrags 145 | mAdapter.getFragments().remove(yyy); 146 | mAdapter.notifyDataSetChanged(); 147 | //解决动态修改刷新白屏的问题 148 | BaseFragmentNotifyAdapter adapter = new BaseFragmentNotifyAdapter(getSupportFragmentManager(), mFrags); 149 | mVp.setAdapter(adapter); 150 | ... 151 | adapter.notifyAllItem(1);//保留展示的frag这样就不会白屏了,想要刷新这个frag当然需要自己frag内部刷新了,详见app下的示例 152 | ``` 153 | 154 | **多条目太复杂,无法满足?** 155 | 156 | 当然还有适用于各种复杂样式的adapter容器(如:聊天列表,首页、今日头条的列表等): 157 | 158 | 本项目已内置为kotlin版(待后续继续简化),直接使用即可: [一个通过add其他adapter的超级容器,无论多么复杂的列表样式均可解耦成一个一个的adapter](https://github.com/weimingjue/BaseContainerAdapter) 159 | 160 | 简单示例(具体请看详情介绍): 161 | 162 | ``` 163 | mRv.setLayoutManager(LinearLayoutManager(this))//如果是GridLayoutManager需要提前设置好,Linear随意 164 | val baseAdapter = createContainerAdapter() 165 | mRv.setAdapter(baseAdapter.addAdapter(TextAdapter(),ImageAdapter())) 166 | //... 167 | baseAdapter.setListAndNotifyDataSetChanged(list) 168 | ``` 169 | 170 | ### ViewPager、RecyclerView、ViewPager2高度自适应第一个child 171 | 172 | ``` 173 | //随便写一个高 177 | 178 | //初始化时即可调用(如果child高度因数据再次变化,这里不会更新,请在合适时机再次调用,后续可能会进行优化) 179 | ViewGroupWrapUtils.wrap(vp, false); 180 | ``` 181 | 182 | ## 使用小贴士 183 | 184 | 本项目所有的adapter都会内部维护一个List,所以修改数据请一定要使用adapter.getList(),然后notify... 185 | 186 | 刷新list数据建议调用notifyListItem...方法,这样就不用处理header、footer数量了 187 | 188 | 关于增加多个header、footer:个人认为多个header、footer场景少并且双方都难以管理,所以请自行将header、footer设置成LinearLayout,然后自行添加维护 189 | 190 | 关于空状态:个人认为这不在adapter范畴(对上拉下拉、notifyItem等都会有交互问题实际上并不友好),自行写个空状态工具类反而更方便和简单(如有需要后续开放) 191 | 192 | ## 导入方式 193 | 194 | 你的build.gradle要有jitpack.io,大致如下 195 | 196 | ``` 197 | allprojects { 198 | repositories { 199 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } 200 | maven { url 'https://jitpack.io' } 201 | google() 202 | jcenter() 203 | } 204 | } 205 | ``` 206 | 207 | 然后: 208 | `(api或)implementation 'com.github.weimingjue:BaseAdapter:5.0.0'` 209 | 210 | 混淆要求: 211 | #请保留ViewBinding里的两个inflate方法,不然会反射不到 212 | -keepclassmembers class * extends androidx.viewbinding.ViewBinding{ 213 | public static ** inflate(...); 214 | } 215 | #如果还是使用旧的那种继承方式请再保留ViewBinding类(createXxx不需要加) 216 | -keep class * extends androidx.viewbinding.ViewBinding -------------------------------------------------------------------------------- /adapters/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | namespace 'com.wang.adapters' 6 | compileSdkVersion COMPILE_SDK_VERSION 7 | 8 | defaultConfig { 9 | minSdkVersion MIN_SDK_VERSION 10 | targetSdkVersion TARGET_SDK_VERSION 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_17 17 | targetCompatibility JavaVersion.VERSION_17 18 | } 19 | kotlinOptions { 20 | jvmTarget = '17' 21 | } 22 | buildFeatures { 23 | viewBinding = true 24 | } 25 | } 26 | 27 | dependencies { 28 | api fileTree(dir: 'libs', include: ['*.jar']) 29 | api 'androidx.recyclerview:recyclerview:1.3.1' 30 | api 'com.google.android.material:material:1.9.0' 31 | //谷歌更强大的浮动布局 32 | api 'com.google.android:flexbox:2.0.1' 33 | 34 | api "androidx.core:core-ktx:1.10.1" 35 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | } -------------------------------------------------------------------------------- /adapters/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import com.wang.adapters.holder.BaseViewHolder 5 | import com.wang.adapters.interfaces.IAdapter 6 | import com.wang.adapters.utils.createBaseAdapter 7 | import com.wang.adapters.utils.createListAdapter 8 | 9 | /** 10 | * 适用于rv 11 | * 创建方法见[createBaseAdapter]、[createListAdapter]等 12 | */ 13 | abstract class BaseAdapter : RecyclerView.Adapter>(), IAdapter { 14 | /////////////////////////////////////////////////////////////////////////// 15 | // 以下是可能用到的父类方法 16 | /////////////////////////////////////////////////////////////////////////// 17 | 18 | // @Override 19 | // public int getItemViewType(int position) { 20 | // return 0; 21 | // } 22 | 23 | abstract override fun getItemCount(): Int 24 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseExpandableAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import androidx.annotation.IntRange 4 | import androidx.viewbinding.ViewBinding 5 | import com.wang.adapters.holder.BaseViewHolder 6 | import com.wang.adapters.utils.listPosition 7 | 8 | /** 9 | * 轻量级可展开折叠的adapter 10 | * 11 | * 点击事件等想得到position见[getPositionInfo] 12 | * 13 | * 注意:目前仅为普通的2级数据展示 14 | * 暂未实现展开折叠(后续需要再添加) 15 | * 暂不支持header、footer、empty等功能(后续需要再添加) 16 | */ 17 | abstract class BaseExpandableAdapter(vbc1: Class, vbc2: Class) : 18 | BaseMultiItemAdapter() { 19 | private val listRange get() = 0 until list.size 20 | 21 | init { 22 | addMultipleItem(object : OnMultipleListListener { 23 | override fun isThisType(adapter: BaseMultiItemAdapter, listPosition: Int, bean: BEAN) = getPositionInfo(listPosition).isParent 24 | override fun getViewBindingClass() = vbc1 25 | override fun onBindListViewHolder(adapter: BaseMultiItemAdapter, holder: BaseViewHolder, bean: BEAN) = 26 | onBindParent(holder, bean, list.indexOf(bean)) 27 | }) 28 | addDefaultMultipleItem( 29 | object : OnMultipleListListener { 30 | override fun isThisType(adapter: BaseMultiItemAdapter, listPosition: Int, bean: BEAN) = true 31 | override fun getViewBindingClass(): Class = vbc2 32 | override fun onBindListViewHolder(adapter: BaseMultiItemAdapter, holder: BaseViewHolder, bean: BEAN) { 33 | val listPosition = holder.listPosition 34 | val info = getPositionInfo(listPosition) 35 | onBindChild(holder, bean, info.childPosition) 36 | } 37 | }) 38 | } 39 | 40 | final override fun getItemCount(): Int { 41 | var count = list.size 42 | (0 until list.size).forEach { parentPosition -> 43 | count += getChildCount(list[parentPosition], parentPosition) 44 | } 45 | return count 46 | } 47 | 48 | /** 49 | * 根据holder的position获取child真正的position 50 | * @param listPosition holder的listPosition 51 | */ 52 | fun getPositionInfo(listPosition: Int): ExpandablePositionInfo { 53 | var itemStartPosition = 0 54 | listRange.forEach { parentPosition -> 55 | val childCount = getChildCount(list[parentPosition], parentPosition) 56 | val nextStartPosition = itemStartPosition + childCount + 1 57 | if (nextStartPosition > listPosition) { 58 | return ExpandablePositionInfo( 59 | parentPosition, 60 | listPosition - (itemStartPosition + 1), 61 | ) 62 | } else { 63 | itemStartPosition = nextStartPosition 64 | } 65 | } 66 | throw IllegalArgumentException("没有找到对应的child你可能没有notify") 67 | } 68 | 69 | /** 70 | * 仅parent的 71 | */ 72 | fun getAbsPosition(parentPosition: Int) = getAbsPosition(parentPosition, -1) 73 | 74 | /** 75 | * 根据相对position获取绝对position 76 | * @return adapter的position,可用于notify等操作 77 | */ 78 | fun getAbsPosition(parentPosition: Int, childPosition: Int): Int { 79 | var position = 0 80 | (0..parentPosition).forEach { parentIndex -> 81 | position++ 82 | position += if (parentIndex == parentPosition) 83 | childPosition 84 | else 85 | getChildCount(list[parentIndex], parentIndex) 86 | } 87 | return position 88 | } 89 | 90 | abstract fun getChildCount(parentItem: BEAN, parentPosition: Int): Int 91 | 92 | abstract fun onBindParent( 93 | holder: BaseViewHolder, 94 | parentItem: BEAN, 95 | parentPosition: Int 96 | ) 97 | 98 | abstract fun onBindChild( 99 | holder: BaseViewHolder, 100 | parentItem: BEAN, 101 | childPosition: Int 102 | ) 103 | 104 | class ExpandablePositionInfo( 105 | val parentPosition: Int, 106 | @IntRange(from = -1) 107 | val childPosition: Int, 108 | ) { 109 | /** 110 | * true:当前是父item,false:子item 111 | */ 112 | val isParent = childPosition < 0 113 | } 114 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseFragmentPager2Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import androidx.fragment.app.Fragment 5 | import androidx.fragment.app.FragmentManager 6 | import androidx.lifecycle.Lifecycle 7 | import androidx.viewpager2.adapter.FragmentStateAdapter 8 | import androidx.viewpager2.widget.ViewPager2 9 | 10 | /** 11 | * [ViewPager2] fragment adapter的终极封装 12 | */ 13 | class BaseFragmentPager2Adapter(fm: FragmentManager, lifecycle: Lifecycle, vararg frags: Fragment) : 14 | FragmentStateAdapter(fm, lifecycle) { 15 | 16 | constructor(frag: Fragment, vararg frags: Fragment) : 17 | this(frag.childFragmentManager, frag.lifecycle, *frags) 18 | 19 | constructor(act: AppCompatActivity, vararg frags: Fragment) : 20 | this(act.supportFragmentManager, act.lifecycle, *frags) 21 | 22 | // constructor(ui: IUIContext, vararg frags: Fragment) : 23 | // this(ui.currentFragmentManager, ui.lifecycle, *frags) 24 | 25 | var fragList = frags.toMutableList() 26 | set(value) { 27 | field.clear() 28 | field.addAll(value) 29 | notifyDataSetChanged() 30 | } 31 | 32 | override fun getItemCount(): Int = fragList.size 33 | 34 | override fun createFragment(position: Int): Fragment = fragList[position] 35 | 36 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import androidx.viewbinding.ViewBinding 6 | import com.wang.adapters.helper.ListAdapterHelper 7 | import com.wang.adapters.holder.BaseViewHolder 8 | import com.wang.adapters.interfaces.IHeaderFooterListAdapter 9 | import com.wang.adapters.utils.listPosition 10 | 11 | /** 12 | * RecyclerView listAdapter的基类 13 | * 可以加header、footer,如果是Grid需要自行处理setSpanSizeLookup头尾的跨度 14 | * [notifyItemChanged]相关方法时注意有header时需要+1,建议使用[notifyListItemChanged]等相关方法 15 | * 16 | * 见[BaseAdapter.create]等方法 17 | */ 18 | abstract class BaseListAdapter(list: List? = null) : BaseAdapter(), IHeaderFooterListAdapter { 19 | 20 | private val listHelper = ListAdapterHelper(this, list) 21 | 22 | override val list get() = listHelper.list 23 | 24 | override var headerView: View? 25 | get() = listHelper.headerView 26 | set(value) { 27 | listHelper.headerView = value 28 | } 29 | override var footerView: View? 30 | get() = listHelper.footerView 31 | set(value) { 32 | listHelper.footerView = value 33 | } 34 | 35 | override fun getItemCount() = headerViewCount + footerViewCount + listSize() 36 | 37 | @ListAdapterHelper.AdapterListType 38 | override fun getItemViewType(position: Int) = listHelper.getItemViewType(position) 39 | 40 | override fun onCreateViewHolder(parent: ViewGroup, @ListAdapterHelper.AdapterListType viewType: Int) = 41 | when (viewType) { 42 | ListAdapterHelper.TYPE_HEADER, ListAdapterHelper.TYPE_FOOTER -> listHelper.onCreateHeaderFooterViewHolder(parent) 43 | //TYPE_BODY 44 | else -> onCreateListViewHolder(parent) 45 | } 46 | 47 | override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { 48 | when (getItemViewType(position)) { 49 | ListAdapterHelper.TYPE_HEADER -> { 50 | listHelper.onBindHeaderFooterViewHolder(holder, headerView!!) 51 | } 52 | 53 | ListAdapterHelper.TYPE_FOOTER -> { 54 | listHelper.onBindHeaderFooterViewHolder(holder, footerView!!) 55 | } 56 | 57 | else -> {//TYPE_BODY 58 | onBindListViewHolder( 59 | holder as BaseViewHolder, 60 | list[holder.listPosition] 61 | ) 62 | } 63 | } 64 | } 65 | 66 | /////////////////////////////////////////////////////////////////////////// 67 | // 以下是增加的方法 68 | /////////////////////////////////////////////////////////////////////////// 69 | /** 70 | * 最终你的list的create 71 | * 72 | * 默认用DataBinding create 73 | * 完全不需要的话覆盖整个方法就行了,不会出问题 74 | * 你也可以重写来添加自己的默认逻辑,如:全局隐藏显示、嵌套rv的默认属性设置等 75 | */ 76 | open fun onCreateListViewHolder(parent: ViewGroup) = listHelper.onCreateDefaultViewHolder(parent, this) 77 | 78 | /** 79 | * 最终你的list的bind 80 | */ 81 | abstract fun onBindListViewHolder(holder: BaseViewHolder, bean: BEAN) 82 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseListCycleAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.viewbinding.ViewBinding 5 | import com.wang.adapters.helper.ListAdapterHelper 6 | import com.wang.adapters.holder.BaseViewHolder 7 | import com.wang.adapters.interfaces.IListAdapter 8 | import com.wang.adapters.utils.listPosition 9 | 10 | /** 11 | * 无限循环滑动的adapter 12 | */ 13 | abstract class BaseListCycleAdapter(list: List?) : BaseAdapter(), IListAdapter { 14 | private val mHelper = ListAdapterHelper(this, list) 15 | 16 | override fun getItemCount(): Int { 17 | if (list.isEmpty()) { 18 | return 0 19 | } 20 | return if (isCycle) Int.MAX_VALUE else listSize() 21 | } 22 | 23 | override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { 24 | onBindListViewHolder(holder as BaseViewHolder, list[position % list.size]) 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { 28 | return onCreateListViewHolder(parent) 29 | } 30 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 31 | // list相关的方法,其他方法请使用getList进行操作 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | override val list get() = mHelper.list 35 | 36 | /** 37 | * 获取指定bean 38 | */ 39 | override fun getItemData(listPosition: Int): BEAN { 40 | return super.getItemData(listPosition % list.size) 41 | } 42 | 43 | override fun getItemDataOrNull(listPosition: Int): BEAN? { 44 | return super.getItemDataOrNull(listPosition % list.size) 45 | } 46 | /////////////////////////////////////////////////////////////////////////// 47 | // 以下是增加的方法 48 | /////////////////////////////////////////////////////////////////////////// 49 | /** 50 | * 最终你的list的create 51 | * 52 | * 53 | * 默认用DataBinding create 54 | * 完全不需要的话覆盖整个方法就行了,不会出问题 55 | * 你也可以重写来添加自己的默认逻辑,如:全局隐藏显示、嵌套rv的默认属性设置等 56 | */ 57 | open fun onCreateListViewHolder(parent: ViewGroup) = mHelper.onCreateDefaultViewHolder(parent, this) 58 | 59 | /** 60 | * 最终你的list的bind 61 | * @param holder position见[cycleListPosition] 62 | */ 63 | abstract fun onBindListViewHolder(holder: BaseViewHolder, bean: BEAN) 64 | 65 | /** 66 | * true 默认值,可循环滑动 67 | */ 68 | var isCycle: Boolean = true 69 | set(isCycle) { 70 | if (field != isCycle) { 71 | field = isCycle 72 | notifyDataSetChanged() 73 | } 74 | } 75 | 76 | /** 77 | * 对position进行取余 78 | */ 79 | inline val BaseViewHolder.cycleListPosition get() = listPosition % list.size 80 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/adapter/BaseMultiItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.Keep 8 | import androidx.viewbinding.ViewBinding 9 | import com.wang.adapters.helper.ListAdapterHelper 10 | import com.wang.adapters.holder.BaseViewHolder 11 | import com.wang.adapters.interfaces.IHeaderFooterListAdapter 12 | import com.wang.adapters.utils.ViewBindingHelper 13 | import com.wang.adapters.utils.forEachReverseSequence 14 | import com.wang.adapters.utils.listPosition 15 | 16 | /** 17 | * @作者 王能 18 | * api很简单,就2个:[addMultipleItem]、[addDefaultMultipleItem] 19 | */ 20 | @SuppressLint("NotifyDataSetChanged") 21 | open class BaseMultiItemAdapter(list: List? = null) : BaseAdapter(), IHeaderFooterListAdapter { 22 | private val listHelper = ListAdapterHelper(this, list) 23 | 24 | override val list get() = listHelper.list 25 | 26 | override var headerView: View? 27 | get() = listHelper.headerView 28 | set(value) { 29 | listHelper.headerView = value 30 | } 31 | override var footerView: View? 32 | get() = listHelper.footerView 33 | set(value) { 34 | listHelper.footerView = value 35 | } 36 | 37 | /** 38 | * 第0个是兜底策略,所以遍历集合都是倒序 39 | */ 40 | private val idInfoList = ArrayList>(4) 41 | 42 | override fun getItemCount() = headerViewCount + footerViewCount + listSize() 43 | 44 | override fun getItemViewType(position: Int): Int { 45 | when (val type = listHelper.getItemViewType(position)) { 46 | ListAdapterHelper.TYPE_HEADER, ListAdapterHelper.TYPE_FOOTER -> return type 47 | else -> { 48 | val listPosition = position - headerViewCount 49 | idInfoList.forEachReverseSequence { index, listener -> 50 | if (listener.isThisType(this, listPosition, list[listPosition])) { 51 | return index 52 | } 53 | } 54 | throw IllegalArgumentException("第${listPosition}个没有对应的type或没有兜底处理") 55 | } 56 | } 57 | } 58 | 59 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { 60 | ListAdapterHelper.TYPE_HEADER, ListAdapterHelper.TYPE_FOOTER -> { 61 | listHelper.onCreateHeaderFooterViewHolder(parent) 62 | } 63 | //TYPE_BODY 64 | else -> { 65 | val clazz = idInfoList[viewType].getViewBindingClass() 66 | val vb = ViewBindingHelper.getViewBindingInstanceByClass( 67 | clazz, 68 | LayoutInflater.from(parent.context), 69 | parent, 70 | false 71 | ) 72 | BaseViewHolder(vb) 73 | } 74 | } 75 | 76 | override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { 77 | when (getItemViewType(position)) { 78 | ListAdapterHelper.TYPE_HEADER -> { 79 | listHelper.onBindHeaderFooterViewHolder(holder, headerView!!) 80 | } 81 | 82 | ListAdapterHelper.TYPE_FOOTER -> { 83 | listHelper.onBindHeaderFooterViewHolder(holder, footerView!!) 84 | } 85 | 86 | else -> {//TYPE_BODY 87 | idInfoList.forEachReverseSequence { _, listener -> 88 | val listPosition = holder.listPosition 89 | if (listener.isThisType(this, listPosition, list[listPosition])) { 90 | //kotlin bug,直接调用会提示必须传Nothing??? 91 | @Suppress("UNCHECKED_CAST") 92 | bindVB(holder as BaseViewHolder, list[listPosition], listener) 93 | return 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | @Suppress("NOTHING_TO_INLINE") 101 | private inline fun bindVB( 102 | holder: BaseViewHolder, 103 | bean: BEAN, 104 | listener: OnMultipleListListener 105 | ) { 106 | @Suppress("UNCHECKED_CAST") 107 | listener.onBindListViewHolder( 108 | adapter = this, 109 | holder = holder as BaseViewHolder, 110 | bean = bean 111 | ) 112 | } 113 | 114 | /** 115 | * 多条目实现类 116 | */ 117 | @Keep 118 | interface OnMultipleListListener { 119 | /** 120 | * 这个bean是否是当前类型 121 | */ 122 | fun isThisType( 123 | adapter: BaseMultiItemAdapter, 124 | listPosition: Int, 125 | bean: BEAN 126 | ): Boolean 127 | 128 | /** 129 | * 默认走反射 130 | */ 131 | fun getViewBindingClass(): Class { 132 | return ViewBindingHelper.getViewBindingClass(this.javaClass) 133 | ?: throw IllegalArgumentException("没有找到类${this}的ViewBinding,请检查") 134 | } 135 | 136 | fun onBindListViewHolder( 137 | adapter: BaseMultiItemAdapter, 138 | holder: BaseViewHolder, 139 | bean: BEAN 140 | ) 141 | } 142 | 143 | ///////////////////////////////////////////////////////////////////////////////////////////////// 144 | // 公共方法 145 | ///////////////////////////////////////////////////////////////////////////////////////////////// 146 | /** 147 | * 添加回调 148 | */ 149 | fun addMultipleItem(listener: OnMultipleListListener) { 150 | idInfoList.add(listener) 151 | notifyDataSetChanged() 152 | } 153 | 154 | /** 155 | *inline版实现 156 | */ 157 | inline fun addMultipleItem( 158 | crossinline isThisTypeCallback: BaseMultiItemAdapter.(listPosition: Int, bean: BEAN) -> Boolean, 159 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.(holder: BaseViewHolder, bean: BEAN) -> Unit, 160 | ) { 161 | addMultipleItem(createListener(isThisTypeCallback, onBindListViewHolderCallback)) 162 | } 163 | 164 | /** 165 | * 带vb 166 | */ 167 | inline fun addMultipleItem( 168 | crossinline isThisTypeCallback: BaseMultiItemAdapter.(listPosition: Int, bean: BEAN) -> Boolean, 169 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.(holder: BaseViewHolder, vb: VB, bean: BEAN) -> Unit, 170 | ) { 171 | addMultipleItem(isThisTypeCallback) { holder, bean -> this.onBindListViewHolderCallback(holder, holder.vb, bean) } 172 | } 173 | 174 | /** 175 | * 对于不同类型的bean,安全转为对应bean 176 | */ 177 | inline fun addAsMultipleItem( 178 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.(holder: BaseViewHolder, bean: T) -> Unit, 179 | ) { 180 | addMultipleItem(createListener({ _, bean -> bean is T }, { holder, bean -> this.onBindListViewHolderCallback(holder, bean as T) })) 181 | } 182 | 183 | /** 184 | * 带vb 185 | */ 186 | inline fun addAsMultipleItem( 187 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.(holder: BaseViewHolder, vb: VB, bean: T) -> Unit, 188 | ) { 189 | addAsMultipleItem { holder, bean -> this.onBindListViewHolderCallback(holder, holder.vb, bean) } 190 | } 191 | 192 | /** 193 | * 添加兜底type(else) 194 | * 注意[OnMultipleListListener.isThisType]请直接返回true,暂时不加限制了 195 | * 建议使用inline简化效果的重载方法 196 | */ 197 | fun addDefaultMultipleItem(listener: OnMultipleListListener) { 198 | idInfoList.add(0, listener) 199 | notifyDataSetChanged() 200 | } 201 | 202 | /** 203 | * inline版 204 | */ 205 | inline fun addDefaultMultipleItem( 206 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.( 207 | holder: BaseViewHolder, 208 | bean: BEAN 209 | ) -> Unit = { _, _ -> } 210 | ) { 211 | addDefaultMultipleItem(createListener({ _, _ -> true }, onBindListViewHolderCallback)) 212 | } 213 | 214 | inline fun createListener( 215 | crossinline isThisTypeCallback: BaseMultiItemAdapter.(listPosition: Int, bean: BEAN) -> Boolean, 216 | crossinline onBindListViewHolderCallback: BaseMultiItemAdapter.(holder: BaseViewHolder, bean: BEAN) -> Unit 217 | ) = object : OnMultipleListListener { 218 | override fun isThisType( 219 | adapter: BaseMultiItemAdapter, 220 | listPosition: Int, 221 | bean: BEAN 222 | ): Boolean { 223 | return adapter.isThisTypeCallback(listPosition, bean) 224 | } 225 | 226 | override fun getViewBindingClass(): Class { 227 | return VB::class.java 228 | } 229 | 230 | override fun onBindListViewHolder( 231 | adapter: BaseMultiItemAdapter, 232 | holder: BaseViewHolder, 233 | bean: BEAN 234 | ) { 235 | adapter.onBindListViewHolderCallback(holder, bean) 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/BaseContainerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.annotation.MainThread 7 | import androidx.collection.SimpleArrayMap 8 | import androidx.recyclerview.widget.GridLayoutManager 9 | import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.wang.adapters.adapter.BaseAdapter 12 | import com.wang.adapters.container.bean.IContainerBean 13 | import com.wang.adapters.container.bean.ItemAdapterPositionInfo 14 | import com.wang.adapters.container.item.BaseContainerItemAdapter 15 | import com.wang.adapters.container.item.OneContainerItemAdapter 16 | import com.wang.adapters.container.observer.IContainerObserver 17 | import com.wang.adapters.helper.ListAdapterHelper 18 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_FOOTER 19 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_HEADER 20 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_MAX 21 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_MIN 22 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_MINUS 23 | import com.wang.adapters.holder.BaseViewHolder 24 | import com.wang.adapters.interfaces.IHeaderFooterListAdapter 25 | import com.wang.adapters.utils.createContainerAdapter 26 | import kotlin.math.min 27 | 28 | /** 29 | * 一个超级adapter可以添加其他adapter 30 | * 31 | * 32 | * 可以用在如:天猫首页、bilibili、今日头条、聊天列表页面 33 | * 34 | * 35 | * 核心思想:每个[BEAN]的item都当作一个adapter,所以再调用时都有个currentBean,adapter更新/判断数据时以currentBean为准 36 | * 37 | * 38 | * 使用前提(都是无关紧要的,但也要看看): 39 | * 1.bean必须继承[IContainerBean] 40 | * 2.子adapter必须是[BaseContainerItemAdapter]、[OneContainerItemAdapter]的子类 41 | * 3.子adapter的type必须在[TYPE_MAX]、[TYPE_MIN]之间 42 | * 4.如果是GridLayoutManager必须在adapter前设置(在rv.setAdapter或[addAdapter]之前或手动调用[changedLayoutManager]) 43 | * 5.有header时直接调用BaseContainerAdapter的[notifyItemChanged]相关方法时需要+1(所有adapter的通病,建议使用[notifyListItemChanged])(子adapter刷新时无需考虑父的header) 44 | * 其他限制暂时没发现 45 | * 46 | * 47 | * https://blog.csdn.net/weimingjue/article/details/106468916 48 | */ 49 | @SuppressLint("NotifyDataSetChanged") 50 | class BaseContainerAdapter @JvmOverloads constructor(list: List? = null) : 51 | BaseAdapter(), IHeaderFooterListAdapter { 52 | private var lastCachePositionInfo: ItemAdapterPositionInfo? = null 53 | private var internalLastCachePositionInfo: ItemAdapterPositionInfo? = null 54 | private val adaptersManager = MyAdaptersManager() 55 | private val childObservers: IContainerObserver = object : IContainerObserver { 56 | override fun notifyDataSetChanged() { 57 | this@BaseContainerAdapter.notifyDataSetChanged() 58 | } 59 | 60 | override fun notifyItemChanged( 61 | relativePositionStart: Int, 62 | itemCount: Int, 63 | bean: IContainerBean 64 | ) { 65 | val newPosition = getAbsPosition(bean, relativePositionStart) 66 | this@BaseContainerAdapter.notifyItemRangeChanged(newPosition, itemCount) 67 | } 68 | 69 | override fun notifyItemInserted( 70 | relativePositionStart: Int, 71 | itemCount: Int, 72 | bean: IContainerBean 73 | ) { 74 | val newPosition = getAbsPosition(bean, relativePositionStart) 75 | this@BaseContainerAdapter.notifyItemRangeInserted(newPosition, itemCount) 76 | } 77 | 78 | override fun notifyItemMoved( 79 | relativeFromPosition: Int, 80 | relativePositionToPosition: Int, 81 | bean: IContainerBean 82 | ) { 83 | val newPosition = getAbsPosition(bean, relativeFromPosition) 84 | this@BaseContainerAdapter.notifyItemMoved( 85 | newPosition, 86 | newPosition + (relativePositionToPosition - relativeFromPosition) 87 | ) 88 | } 89 | 90 | override fun notifyItemRemoved( 91 | relativePositionStart: Int, 92 | itemCount: Int, 93 | bean: IContainerBean 94 | ) { 95 | val newPosition = getAbsPosition(bean, relativePositionStart) 96 | this@BaseContainerAdapter.notifyItemRangeRemoved(newPosition, itemCount) 97 | } 98 | } 99 | private var recyclerView: RecyclerView? = null 100 | private var lastLayoutManager: GridLayoutManager? = null 101 | 102 | /** 103 | * list相关代码合并 104 | */ 105 | private val listHelper = ListAdapterHelper(this, list) 106 | 107 | init { 108 | registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { 109 | override fun onChanged() { 110 | super.onChanged() 111 | onAdapterChanged() 112 | } 113 | 114 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { 115 | super.onItemRangeChanged(positionStart, itemCount) 116 | onAdapterChanged() 117 | } 118 | 119 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 120 | super.onItemRangeInserted(positionStart, itemCount) 121 | onAdapterChanged() 122 | } 123 | 124 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { 125 | super.onItemRangeRemoved(positionStart, itemCount) 126 | onAdapterChanged() 127 | } 128 | 129 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 130 | super.onItemRangeMoved(fromPosition, toPosition, itemCount) 131 | onAdapterChanged() 132 | } 133 | 134 | fun onAdapterChanged() { 135 | lastCachePositionInfo = null 136 | internalLastCachePositionInfo?.absPosition = -999 137 | } 138 | }) 139 | } 140 | 141 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> { 142 | return when (viewType) { 143 | TYPE_HEADER, TYPE_FOOTER -> { 144 | listHelper.onCreateHeaderFooterViewHolder(parent) 145 | } 146 | 147 | else -> { 148 | adaptersManager.getAdapter(viewType / TYPE_MINUS) 149 | .onCreateViewHolder(parent, viewType % TYPE_MINUS - TYPE_MAX) 150 | } 151 | } 152 | } 153 | 154 | override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { 155 | when (getItemViewType(position)) { 156 | TYPE_HEADER -> { 157 | listHelper.onBindHeaderFooterViewHolder(holder, listHelper.headerView!!) 158 | } 159 | 160 | TYPE_FOOTER -> { 161 | listHelper.onBindHeaderFooterViewHolder(holder, listHelper.footerView!!) 162 | } 163 | 164 | else -> { 165 | val info = getCacheItemPositionInfo(position, true) 166 | val itemAdapter = info.itemAdapter.castSuperAdapter() 167 | itemAdapter.onBindViewHolder( 168 | holder, 169 | list[info.containerListIndex], 170 | info.itemRelativePosition 171 | ) 172 | } 173 | } 174 | } 175 | 176 | override fun getItemViewType(position: Int): Int { 177 | if (hasHeaderView && position == 0) { 178 | return TYPE_HEADER 179 | } 180 | if (hasFooterView && itemCount == position + 1) { 181 | return TYPE_FOOTER 182 | } 183 | val info = getCacheItemPositionInfo(position, true) 184 | val itemAdapter = info.itemAdapter.castSuperAdapter() 185 | val itemType = 186 | itemAdapter.getItemViewType(list[info.containerListIndex], info.itemRelativePosition) 187 | if (itemType <= TYPE_MIN || itemType >= TYPE_MAX) { 188 | throw RuntimeException("你adapter(" + itemAdapter.javaClass + ")的type必须在" + TYPE_MIN + "~" + TYPE_MAX + "之间,type:" + itemType) 189 | } 190 | //根据mItemAdapters的position返回type,取的时候比较方便 191 | //此处返回的type>0 192 | return adaptersManager.getPosition(itemAdapter.javaClass) * TYPE_MINUS + itemType + TYPE_MAX 193 | } 194 | 195 | override fun getItemCount(): Int { 196 | var count = headerViewCount + footerViewCount 197 | listHelper.list.forEach { bean -> 198 | val itemAdapter = 199 | adaptersManager.getAdapter(bean.getBindAdapterClass()).castSuperAdapter() 200 | count += itemAdapter.getItemCount(bean) 201 | } 202 | return count 203 | } 204 | 205 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { 206 | this.recyclerView = recyclerView 207 | checkLayoutManager() 208 | } 209 | 210 | private fun checkLayoutManager() { 211 | (recyclerView?.layoutManager as? GridLayoutManager)?.let { 212 | if (lastLayoutManager != it) { 213 | changedLayoutManager(it) 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * 根据绝对position获取子adapter的相关信息 220 | * 221 | * @param absPosition 绝对position 222 | * @param internalRecycle 开启内部循环利用,返回值绝对不可声明final(当然为了安全,外部调用默认false) 223 | */ 224 | internal fun getCacheItemPositionInfo( 225 | absPosition: Int, 226 | internalRecycle: Boolean 227 | ): ItemAdapterPositionInfo { 228 | //取缓存 229 | val cache = if (internalRecycle) internalLastCachePositionInfo else lastCachePositionInfo 230 | if (cache?.absPosition == absPosition) { 231 | return cache 232 | } 233 | 234 | val containerListIndex = absPosition - headerViewCount 235 | 236 | //itemAdapter的position=0时的真实位置 237 | var itemStartPosition = 0 238 | listHelper.list.forEachIndexed { i, bean -> 239 | val itemAdapter = 240 | adaptersManager.getAdapter(bean.getBindAdapterClass()).castSuperAdapter() 241 | val itemCount = itemAdapter.getItemCount(bean) 242 | val nextStartPosition = itemStartPosition + itemCount 243 | 244 | if (nextStartPosition > containerListIndex) { 245 | //下一个adapter的位置比position大说明当前type就在这个adapter中 246 | 247 | val itemPosition = containerListIndex - itemStartPosition 248 | 249 | //当前状态 250 | val isFirst = containerListIndex == 0 251 | val isLast = containerListIndex == listHelper.list.lastIndex 252 | 253 | if (internalRecycle) { 254 | //内部使用则复用单独一个对象 255 | val info = internalLastCachePositionInfo?.also { 256 | it.absPosition = absPosition 257 | it.containerListIndex = i 258 | it.itemRelativePosition = itemPosition 259 | it.itemAdapter = itemAdapter 260 | it.hasHeader = hasHeaderView 261 | it.hasFooter = hasFooterView 262 | it.isFirst = isFirst 263 | it.isLast = isLast 264 | } ?: ItemAdapterPositionInfo( 265 | absPosition = absPosition, 266 | containerListIndex = i, 267 | itemPosition = itemPosition, 268 | itemAdapter = itemAdapter, 269 | hasHeader = hasHeaderView, 270 | hasFooter = hasFooterView, 271 | isFirst = isFirst, 272 | isLast = isLast 273 | ) 274 | internalLastCachePositionInfo = info 275 | return info 276 | } 277 | val info = ItemAdapterPositionInfo( 278 | absPosition = absPosition, 279 | containerListIndex = i, 280 | itemPosition = itemPosition, 281 | itemAdapter = itemAdapter, 282 | hasHeader = hasHeaderView, 283 | hasFooter = hasFooterView, 284 | isFirst = isFirst, 285 | isLast = isLast 286 | ) 287 | lastCachePositionInfo = info 288 | return info 289 | } else { 290 | //循环相加 291 | itemStartPosition = nextStartPosition 292 | } 293 | } 294 | throw RuntimeException("没有取到对应的type,可能你没有(及时)刷新adapter") 295 | } 296 | 297 | /** 298 | * position、adapter、class唯一并且可以互相取值 299 | */ 300 | private inner class MyAdaptersManager { 301 | val map = SimpleArrayMap>, Int>(8) 302 | val list = ArrayList>(8) 303 | 304 | fun addAdapter(adapters: List>) { 305 | adapters.forEach { adapter -> 306 | adapter.attachContainer(this@BaseContainerAdapter) 307 | adapter.registerDataSetObserver(childObservers) 308 | if (!map.containsKey(adapter.javaClass)) { 309 | list.add(adapter) 310 | map.put(adapter.javaClass, list.lastIndex) 311 | } 312 | } 313 | } 314 | 315 | fun getAdapter(position: Int): BaseContainerItemAdapter<*> { 316 | return list.getOrNull(position) 317 | ?: throw RuntimeException("缺少对应的adapter,adapter数量:" + list.size + ",当前index:" + position) 318 | } 319 | 320 | fun getAdapter(cls: Class>): BaseContainerItemAdapter<*> { 321 | val index = map[cls] ?: throw RuntimeException("缺少对应的adapter:$cls") 322 | return list[index] 323 | } 324 | 325 | fun getPosition(cls: Class>): Int { 326 | return map[cls] ?: throw NullPointerException("一般是数据变化没有(及时)刷新adapter导致的") 327 | } 328 | 329 | fun remove(position: Int) { 330 | list.removeAt(position).also { map.remove(it.javaClass) } 331 | } 332 | 333 | fun remove(cls: Class>?) { 334 | map.remove(cls)?.also { list.removeAt(it) } 335 | } 336 | 337 | fun clear() { 338 | list.clear() 339 | map.clear() 340 | } 341 | } 342 | 343 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 344 | // 以下是可调用方法 345 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 346 | /** 347 | * 添加adapter.重复则不会被添加,必须先删除 348 | * 当然可以预先添加用不到的adapter 349 | * [createContainerAdapter]、[createOneItemAdapter] 350 | */ 351 | fun addAdapter(vararg adapters: BaseContainerItemAdapter<*>) { 352 | addAdapter(adapters.toList()) 353 | } 354 | 355 | /** 356 | * 添加adapter.重复则不会被添加,必须先删除 357 | * 当然可以预先添加用不到的adapter 358 | * [createContainerAdapter]、[createOneItemAdapter] 359 | */ 360 | fun addAdapter(adapters: List>) { 361 | adaptersManager.addAdapter(adapters) 362 | checkLayoutManager() 363 | notifyDataSetChanged() 364 | } 365 | 366 | /** 367 | * 删除指定adapter 368 | * 369 | * @param adapterPosition 按添加顺序第几个 370 | */ 371 | fun removeAdapter(adapterPosition: Int) { 372 | adaptersManager.remove(adapterPosition) 373 | notifyDataSetChanged() 374 | } 375 | 376 | /** 377 | * 删除指定adapter 378 | * 379 | * @param adapterClass 哪个adapter 380 | */ 381 | fun removeAdapter(adapterClass: Class>?) { 382 | adaptersManager.remove(adapterClass) 383 | notifyDataSetChanged() 384 | } 385 | 386 | /** 387 | * 清空adapter 388 | */ 389 | fun removeAllAdapter() { 390 | adaptersManager.clear() 391 | notifyDataSetChanged() 392 | } 393 | 394 | /** 395 | * 返回所有的adapter 396 | */ 397 | fun getAdapters(): List> { 398 | //为了安全起见,不允许私自增删 399 | return adaptersManager.list.toList() 400 | } 401 | 402 | override val list get() = listHelper.list 403 | 404 | /** 405 | * 根据bean对象和adapter的相对位置获取绝对位置 406 | * 407 | * @param relativePosition 相对potion 408 | */ 409 | fun getAbsPosition(bean: IContainerBean, relativePosition: Int): Int { 410 | var position = relativePosition 411 | listHelper.list.forEach { listBean -> 412 | if (listBean === bean) { 413 | return position + headerViewCount 414 | } else { 415 | val itemAdapter = 416 | adaptersManager.getAdapter(listBean.getBindAdapterClass()).castSuperAdapter() 417 | position += itemAdapter.getItemCount(listBean) 418 | } 419 | } 420 | throw RuntimeException("在list中没有找到传入的bean对象$bean") 421 | } 422 | 423 | /** 424 | * 根据绝对position获取对应adapter的额外信息 425 | * 426 | * @param absPosition 一般为[BaseViewHolder.commonPosition] 427 | * @return 不建议声明为final,因为[notifyItemChanged]相关方法时并不会更新里面的position 428 | */ 429 | @MainThread 430 | fun getItemAdapterPositionInfo(absPosition: Int): ItemAdapterPositionInfo { 431 | return getCacheItemPositionInfo(absPosition, false) 432 | } 433 | 434 | /** 435 | * 根据bean和相对position获取对应adapter的额外信息 436 | * 437 | * @param relativePosition 相对potion 438 | * @return 不建议声明为final,因为[notifyItemChanged]相关方法时并不会更新里面的position 439 | */ 440 | @MainThread 441 | fun getItemAdapterPositionInfo( 442 | bean: IContainerBean, 443 | relativePosition: Int 444 | ): ItemAdapterPositionInfo { 445 | return getItemAdapterPositionInfo(getAbsPosition(bean, relativePosition)) 446 | } 447 | 448 | /** 449 | * 把rv的LayoutManager改成其他的GridLayoutManager时,此方法理论上没啥用 450 | */ 451 | fun changedLayoutManager(manager: GridLayoutManager) { 452 | lastLayoutManager = manager 453 | manager.spanSizeLookup = object : SpanSizeLookup() { 454 | override fun getSpanSize(position: Int): Int { 455 | if (hasHeaderView && position == 0) { 456 | return manager.spanCount 457 | } else if (hasFooterView && itemCount == position + 1) { 458 | return manager.spanCount 459 | } 460 | val info = getCacheItemPositionInfo(position, true) 461 | val itemAdapter = info.itemAdapter.castSuperAdapter() 462 | return itemAdapter.getSpanSize( 463 | list[info.containerListIndex], 464 | info.itemRelativePosition 465 | ) 466 | } 467 | } 468 | } 469 | 470 | override var headerView: View? 471 | get() = listHelper.headerView 472 | set(value) { 473 | listHelper.headerView = value 474 | } 475 | override var footerView: View? 476 | get() = listHelper.footerView 477 | set(value) { 478 | listHelper.footerView = value 479 | } 480 | 481 | ///////////////////////////////////////////////////////////////////////////////////////////////// 482 | // container不支持list的范围刷新效果(很难算出),此处重载为全部刷新 483 | ///////////////////////////////////////////////////////////////////////////////////////////////// 484 | override fun notifyListItemChanged(listPosition: Int) { 485 | if (listPosition < 0 || listPosition >= listSize()) { 486 | return 487 | } 488 | notifyDataSetChanged() 489 | } 490 | 491 | override fun notifyListItemRangeChanged(listPositionStart: Int, itemCount: Int) { 492 | if (listPositionStart < 0 || itemCount <= 0) { 493 | return 494 | } 495 | notifyDataSetChanged() 496 | } 497 | 498 | override fun notifyListItemInserted(listPosition: Int) { 499 | if (listPosition < 0 || listPosition >= listSize()) { 500 | return 501 | } 502 | notifyDataSetChanged() 503 | } 504 | 505 | override fun notifyListItemRangeInserted(listPositionStart: Int, itemCount: Int) { 506 | if (listPositionStart < 0 || listPositionStart > listSize() || itemCount <= 0) { 507 | return 508 | } 509 | notifyDataSetChanged() 510 | } 511 | 512 | override fun notifyListItemMoved(listFromPosition: Int, listToPosition: Int, isMovedData: Boolean) { 513 | if (listFromPosition == listToPosition || listFromPosition < 0 || listFromPosition >= listSize() || listToPosition < 0 || listToPosition >= listSize()) { 514 | return 515 | } 516 | if (isMovedData) { 517 | val bean = list.removeAt(listFromPosition) 518 | if (listFromPosition > listToPosition) { 519 | list.add(listToPosition, bean) 520 | } else { 521 | list.add(listToPosition - 1, bean) 522 | } 523 | } 524 | notifyDataSetChanged() 525 | } 526 | 527 | override fun notifyListItemRemoved(listPosition: Int, isRemoData: Boolean) { 528 | if (listPosition < 0) { 529 | return 530 | } 531 | if (isRemoData) { 532 | if (listPosition >= listSize()) { 533 | return 534 | } 535 | list.removeAt(listPosition) 536 | } 537 | notifyDataSetChanged() 538 | } 539 | 540 | override fun notifyListItemRangeRemoved(listPositionStart: Int, itemCount: Int, isRemoData: Boolean) { 541 | if (listPositionStart < 0 || itemCount <= 0) { 542 | return 543 | } 544 | if (isRemoData) { 545 | if (listPositionStart >= listSize()) { 546 | return 547 | } 548 | list.subList(listPositionStart, min(listSize(), listPositionStart + itemCount)).clear() 549 | } 550 | notifyDataSetChanged() 551 | } 552 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/bean/IContainerBean.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container.bean 2 | 3 | import com.wang.adapters.container.item.BaseContainerItemAdapter 4 | 5 | /** 6 | * 你的最外层bean必须继承该接口 7 | */ 8 | interface IContainerBean { 9 | /** 10 | * 这个bean属于哪个adapter 11 | */ 12 | fun getBindAdapterClass(): Class> 13 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/bean/ItemAdapterPositionInfo.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container.bean 2 | 3 | import com.wang.adapters.container.item.BaseContainerItemAdapter 4 | 5 | class ItemAdapterPositionInfo( 6 | absPosition: Int, 7 | containerListIndex: Int, 8 | itemPosition: Int, 9 | itemAdapter: BaseContainerItemAdapter<*>, 10 | hasHeader: Boolean, 11 | hasFooter: Boolean, 12 | isFirst: Boolean, 13 | isLast: Boolean, 14 | ) { 15 | /** 16 | * 绝对值,container的list position 17 | */ 18 | var absPosition = absPosition 19 | internal set 20 | 21 | /** 22 | * 绝对值,container的list position 23 | */ 24 | var containerListIndex = containerListIndex 25 | internal set 26 | 27 | /** 28 | * 相对值,子adapter对应的相对position 29 | */ 30 | var itemRelativePosition = itemPosition 31 | internal set 32 | 33 | var itemAdapter = itemAdapter 34 | internal set 35 | 36 | /** 37 | * 列表有没有header 38 | */ 39 | var hasHeader = hasHeader 40 | internal set 41 | 42 | /** 43 | * 列表有没有footer 44 | */ 45 | var hasFooter = hasFooter 46 | internal set 47 | 48 | /** 49 | * 是不是列表第一个(除了header) 50 | * 51 | * 52 | * 注意:整个adapter只有一个条目时既是第一个又是最后一个 53 | */ 54 | var isFirst = isFirst 55 | internal set 56 | 57 | /** 58 | * 是不是列表里中间的(不是header、也不是footer) 59 | */ 60 | val isCenter: Boolean 61 | get() = !(isFirst || isLast) 62 | 63 | /** 64 | * 是不是列表最后一个(除了footer) 65 | * 66 | * 67 | * 注意:整个adapter只有一个条目时既是第一个又是最后一个 68 | */ 69 | var isLast = isLast 70 | internal set 71 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/item/BaseContainerItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container.item 2 | 3 | import android.view.ViewGroup 4 | import androidx.annotation.IntRange 5 | import androidx.collection.ArraySet 6 | import com.wang.adapters.container.BaseContainerAdapter 7 | import com.wang.adapters.container.bean.IContainerBean 8 | import com.wang.adapters.container.observer.IContainerObserver 9 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_MAX 10 | import com.wang.adapters.helper.ListAdapterHelper.Companion.TYPE_MIN 11 | import com.wang.adapters.holder.BaseViewHolder 12 | 13 | /** 14 | * 和普通adapter操作一样,加了个currentBean来确定当前adapter的数据 15 | * 所有的position均为相对position 16 | * 获取adapter在整个RecyclerView的绝对position见[getCurrentPositionInfo]或[BaseViewHolder.commonPosition]、[BaseViewHolder.listPosition] 17 | * 简单的只有一个条目见[OneContainerItemAdapter] 18 | */ 19 | abstract class BaseContainerItemAdapter { 20 | private val observers = ArraySet() 21 | 22 | private var _containerAdapter: BaseContainerAdapter<*>? = null 23 | 24 | /** 25 | * observe主要用于notify 26 | * 此方法一般由父容器调用,所以不能加泛型 27 | */ 28 | open fun registerDataSetObserver(observer: IContainerObserver) { 29 | observers.add(observer) 30 | } 31 | 32 | open fun unregisterDataSetObserver(observer: IContainerObserver) { 33 | observers.remove(observer) 34 | } 35 | 36 | /** 37 | * 刷新全部的adapter数据,其他方法均是局部刷新 38 | */ 39 | open fun notifyDataSetChanged() { 40 | observers.forEach { it.notifyDataSetChanged() } 41 | } 42 | 43 | /** 44 | * @param relativePosition 就是item的position(我自己会计算绝对位置) 45 | * @param bean list的bean数据,没有bean的话无法确定位置 46 | */ 47 | open fun notifyItemChanged(relativePosition: Int, bean: BEAN) { 48 | notifyItemChanged(relativePosition, 1, bean) 49 | } 50 | 51 | open fun notifyItemChanged(relativePositionStart: Int, itemCount: Int, bean: BEAN) { 52 | observers.forEach { it.notifyItemChanged(relativePositionStart, itemCount, bean) } 53 | } 54 | 55 | open fun notifyItemInserted(relativePosition: Int, bean: BEAN) { 56 | notifyItemInserted(relativePosition, 1, bean) 57 | } 58 | 59 | open fun notifyItemInserted(relativePositionStart: Int, itemCount: Int, bean: BEAN) { 60 | observers.forEach { it.notifyItemInserted(relativePositionStart, itemCount, bean) } 61 | } 62 | 63 | open fun notifyItemMoved(relativeFromPosition: Int, relativeToPosition: Int, bean: BEAN) { 64 | observers.forEach { it.notifyItemMoved(relativeFromPosition, relativeToPosition, bean) } 65 | } 66 | 67 | open fun notifyItemRemoved(relativePosition: Int, bean: BEAN) { 68 | notifyItemRemoved(relativePosition, 1, bean) 69 | } 70 | 71 | open fun notifyItemRemoved(relativePositionStart: Int, itemCount: Int, bean: BEAN) { 72 | observers.forEach { it.notifyItemRemoved(relativePositionStart, itemCount, bean) } 73 | } 74 | 75 | /** 76 | * 将容器自己传进来(会在[BaseContainerAdapter.addAdapter]立即调用,正常使用不会为null) 77 | */ 78 | open fun attachContainer(containerAdapter: BaseContainerAdapter<*>) { 79 | _containerAdapter = containerAdapter 80 | } 81 | 82 | /** 83 | * <*>或者调用方法时泛型居然是Nothing,实属醉了 84 | */ 85 | internal fun castSuperAdapter() = 86 | this as BaseContainerItemAdapter 87 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 88 | // 以下是经常用到或重写的方法 89 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 90 | 91 | /** 92 | * 当前position在父adapter的附加信息 93 | * 94 | * 使用场景示例:有header展示线条,没header去掉线条;第一条展示红色,最后一条展示黑色 95 | */ 96 | open fun getCurrentPositionInfo( 97 | bean: IContainerBean, 98 | itemAdapterPosition: Int 99 | ) = containerAdapter.getItemAdapterPositionInfo(bean, itemAdapterPosition) 100 | 101 | /** 102 | * 返回容器(会在[BaseContainerAdapter.addAdapter]立即调用,正常使用不会为null) 103 | */ 104 | open val containerAdapter: BaseContainerAdapter<*> 105 | get() = _containerAdapter ?: throw NullPointerException("只有在addAdapter后才可调用") 106 | 107 | open fun getSpanSize(currentBean: BEAN, relativePosition: Int) = 1 108 | 109 | /** 110 | * @param relativePosition 相对的position 111 | * @return 不能超出范围, 超出就会被当成其他adapter的type 112 | * 当超出范围时会显式抛出异常 113 | */ 114 | @IntRange( 115 | from = TYPE_MIN.toLong(), 116 | to = TYPE_MAX.toLong() 117 | ) 118 | open fun getItemViewType(currentBean: BEAN, relativePosition: Int) = 0 119 | 120 | abstract fun getItemCount(currentBean: BEAN): Int 121 | 122 | /** 123 | * @param viewType 该adapter自己的type 124 | */ 125 | abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> 126 | 127 | /** 128 | * 想取绝对position见[BaseContainerAdapter.getAbsPosition]或[BaseViewHolder.commonPosition]、[BaseViewHolder.listPosition] 129 | * 130 | * @param relativePosition 属于该adapter的position 131 | * 如:[getItemCount]=1(每个bean只对应一条数据),这个position一直是0(就是没用的意思) 132 | * 如:[getItemCount]=xx(你的bean里面还有自己的list),这个position就是相对的值 133 | */ 134 | abstract fun onBindViewHolder( 135 | holder: BaseViewHolder<*>, 136 | currentBean: BEAN, 137 | relativePosition: Int, 138 | ) 139 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/item/OneContainerItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container.item 2 | 3 | import android.view.ViewGroup 4 | import androidx.viewbinding.ViewBinding 5 | import com.wang.adapters.container.bean.IContainerBean 6 | import com.wang.adapters.holder.BaseViewHolder 7 | import com.wang.adapters.utils.ViewBindingHelper 8 | import com.wang.adapters.utils.layoutInflater 9 | 10 | /** 11 | * 一个list的item仅对应一条数据,如:聊天 12 | * 13 | * 无资源id有2种解决方式(任选其一): 14 | * 1.什么都不做,根据泛型自动获取,但Proguard不能混淆[ViewBinding]的子类: 15 | * -keep class * extends androidx.databinding.ViewBinding 16 | * 2.覆盖[onCreateChildViewHolder],自己自定义即可 17 | * 18 | * 暂时不支持id,后续添加 19 | */ 20 | @Suppress("UNCHECKED_CAST") 21 | abstract class OneContainerItemAdapter : 22 | BaseContainerItemAdapter() { 23 | 24 | final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> { 25 | return onCreateChildViewHolder(parent) 26 | } 27 | 28 | final override fun onBindViewHolder( 29 | holder: BaseViewHolder<*>, 30 | currentBean: BEAN, 31 | relativePosition: Int 32 | ) { 33 | onBindChildViewHolder(holder as BaseViewHolder, currentBean) 34 | } 35 | 36 | /** 37 | * 仅一条数据,不允许重写 38 | */ 39 | final override fun getItemViewType(currentBean: BEAN, relativePosition: Int) = 0 40 | final override fun getItemCount(currentBean: BEAN) = 1 41 | 42 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 43 | // 公共方法 44 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | open fun getCurrentPositionInfo(bean: IContainerBean) = 47 | getCurrentPositionInfo(bean, 0) 48 | 49 | protected open fun onCreateChildViewHolder(parent: ViewGroup): BaseViewHolder { 50 | return BaseViewHolder( 51 | ViewBindingHelper.getViewBindingInstance(this, parent.layoutInflater, parent) 52 | ) 53 | } 54 | 55 | /** 56 | * 当然还有[getCurrentPositionInfo] 57 | */ 58 | protected abstract fun onBindChildViewHolder(holder: BaseViewHolder, currentBean: BEAN) 59 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/container/observer/IContainerObserver.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.container.observer 2 | 3 | import com.wang.adapters.container.bean.IContainerBean 4 | 5 | /** 6 | * observer 7 | * 暂时不加泛型:加泛型太繁琐,父容器register还容易出错 8 | */ 9 | interface IContainerObserver { 10 | /** 11 | * 刷新全部的adapter数据,其他方法均是局部刷新 12 | */ 13 | fun notifyDataSetChanged() 14 | 15 | /** 16 | * @param relativePosition 就是item的position(我自己会计算绝对位置) 17 | * @param bean list的bean数据,没有bean的话无法确定位置 18 | */ 19 | fun notifyItemChanged(relativePosition: Int, bean: IContainerBean) { 20 | notifyItemChanged(relativePosition, 1, bean) 21 | } 22 | 23 | fun notifyItemChanged(relativePositionStart: Int, itemCount: Int, bean: IContainerBean) 24 | 25 | fun notifyItemInserted(relativePosition: Int, bean: IContainerBean) { 26 | notifyItemInserted(relativePosition, 1, bean) 27 | } 28 | 29 | fun notifyItemInserted(relativePositionStart: Int, itemCount: Int, bean: IContainerBean) 30 | 31 | fun notifyItemMoved( 32 | relativeFromPosition: Int, 33 | relativePositionToPosition: Int, 34 | bean: IContainerBean 35 | ) 36 | 37 | fun notifyItemRemoved(relativePosition: Int, bean: IContainerBean) { 38 | notifyItemRemoved(relativePosition, 1, bean) 39 | } 40 | 41 | fun notifyItemRemoved(relativePositionStart: Int, itemCount: Int, bean: IContainerBean) 42 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/helper/ListAdapterHelper.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.helper 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.FrameLayout 6 | import androidx.annotation.IntDef 7 | import androidx.recyclerview.widget.RecyclerView 8 | import androidx.viewbinding.ViewBinding 9 | import com.wang.adapters.holder.BaseViewHolder 10 | import com.wang.adapters.interfaces.IListAdapter 11 | import com.wang.adapters.utils.MATCH_PARENT 12 | import com.wang.adapters.utils.ViewBindingHelper 13 | import com.wang.adapters.utils.WRAP_CONTENT 14 | import com.wang.adapters.utils.getDefLayoutParams 15 | import com.wang.adapters.utils.layoutInflater 16 | 17 | open class ListAdapterHelper( 18 | val adapter: IListAdapter, 19 | list: List? 20 | ) { 21 | val list: MutableList = if (list == null) ArrayList() else ArrayList(list) 22 | 23 | fun onCreateHeaderFooterViewHolder(parent: ViewGroup): BaseViewHolder<*> { 24 | val fl = FrameLayout(parent.context) 25 | fl.layoutParams = parent.getDefLayoutParams().apply { height = MATCH_PARENT } 26 | return BaseViewHolder(fl) 27 | } 28 | 29 | fun onBindHeaderFooterViewHolder(holder: BaseViewHolder<*>, headerOrFooterView: View) { 30 | val fl = holder.itemView as FrameLayout 31 | val oldParent = headerOrFooterView.parent as? ViewGroup 32 | if (oldParent != fl) { 33 | oldParent?.removeView(headerOrFooterView) 34 | fl.removeAllViews() 35 | syncParamsToChild(fl, headerOrFooterView) 36 | fl.addView(headerOrFooterView) 37 | } 38 | } 39 | 40 | /** 41 | * 将fl的宽高和child同步 42 | */ 43 | private fun syncParamsToChild( 44 | fl: FrameLayout, 45 | childView: View 46 | ) { 47 | val flParams = fl.layoutParams 48 | val childParams = childView.layoutParams 49 | if (flParams != null && childParams != null) { 50 | flParams.width = childParams.width 51 | flParams.height = childParams.height 52 | } 53 | } 54 | 55 | var headerView: View? = null 56 | set(value) { 57 | val oldHeaderView = field //旧view 58 | field = value 59 | if (field?.layoutParams == null) { 60 | field?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) 61 | } 62 | 63 | //4种情况 64 | if (value == null && oldHeaderView != null) { 65 | if (adapter is RecyclerView.Adapter<*>) { 66 | adapter.notifyItemRemoved(0) 67 | } else { 68 | adapter.notifyDataSetChanged() 69 | } 70 | } else if (value != null && oldHeaderView == null) { 71 | if (adapter is RecyclerView.Adapter<*>) { 72 | adapter.notifyItemInserted(0) 73 | } else { 74 | adapter.notifyDataSetChanged() 75 | } 76 | } else if (value !== oldHeaderView) { 77 | if (adapter is RecyclerView.Adapter<*>) { 78 | adapter.notifyItemChanged(0) 79 | } else { 80 | adapter.notifyDataSetChanged() 81 | } 82 | } //else相等忽略 83 | } 84 | 85 | var footerView: View? = null 86 | set(value) { 87 | val oldFooterView = field //旧view 88 | field = value 89 | if (field?.layoutParams == null) { 90 | field?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) 91 | } 92 | 93 | //4种情况 94 | if (value == null && oldFooterView != null) { 95 | if (adapter is RecyclerView.Adapter<*>) { 96 | adapter.notifyItemRemoved(adapter.getItemCount()) //count已经减一了,所以不用减了 97 | } else { 98 | adapter.notifyDataSetChanged() 99 | } 100 | } else if (value != null && oldFooterView == null) { 101 | if (adapter is RecyclerView.Adapter<*>) { 102 | adapter.notifyItemInserted(adapter.getItemCount() - 1) //count已经加一了,所以需要减掉 103 | } else { 104 | adapter.notifyDataSetChanged() 105 | } 106 | } else if (value !== oldFooterView) { 107 | if (adapter is RecyclerView.Adapter<*>) { 108 | adapter.notifyItemChanged(adapter.getItemCount() - 1) //count不变 109 | } else { 110 | adapter.notifyDataSetChanged() 111 | } 112 | } //else相等忽略 113 | } 114 | 115 | @IntDef(TYPE_BODY, TYPE_HEADER, TYPE_FOOTER) 116 | @Retention(AnnotationRetention.SOURCE) 117 | annotation class AdapterListType //该变量只能传入上面几种,否则会报错 118 | 119 | @AdapterListType 120 | fun getItemViewType(position: Int): Int { 121 | if (adapter.hasHeaderView && position == 0) { 122 | return TYPE_HEADER 123 | } 124 | if (adapter.hasFooterView && adapter.getItemCount() == position + 1) { 125 | return TYPE_FOOTER 126 | } 127 | return TYPE_BODY 128 | } 129 | 130 | fun onCreateDefaultViewHolder(parent: ViewGroup, obj: Any) = 131 | BaseViewHolder(ViewBindingHelper.getViewBindingInstance(obj, parent.layoutInflater, parent)) 132 | 133 | companion object { 134 | const val TYPE_MAX = 100_000 135 | const val TYPE_MIN = -100_000 136 | const val TYPE_MINUS = TYPE_MAX - TYPE_MIN 137 | 138 | const val TYPE_HEADER = TYPE_MIN - 1 139 | const val TYPE_FOOTER = TYPE_MIN - 2 140 | const val TYPE_BODY = 0 141 | 142 | } 143 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/holder/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.holder 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import androidx.viewbinding.ViewBinding 6 | import com.wang.adapters.utils.findRootVbByTag 7 | 8 | class BaseViewHolder : RecyclerView.ViewHolder { 9 | constructor(itemView: View) : super(itemView) { 10 | _vb = itemView.findRootVbByTag() 11 | } 12 | 13 | constructor(vb: T) : super(vb.root) { 14 | this._vb = vb 15 | } 16 | 17 | private val _vb: T? 18 | val vb get() = _vb ?: throw IllegalArgumentException("没有找到ViewBinding,请确认是否使用了ViewBinding:$itemView") 19 | 20 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/interfaces/IAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.interfaces 2 | 3 | import android.view.ViewGroup 4 | import com.wang.adapters.holder.BaseViewHolder 5 | 6 | /** 7 | * 所有adapter的接口 8 | */ 9 | interface IAdapter { 10 | fun getItemCount(): Int 11 | fun getItemViewType(position: Int): Int 12 | fun createViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> 13 | fun bindViewHolder(holder: BaseViewHolder<*>, position: Int) 14 | 15 | ///////////////////////////////////////////////////////////////////////////////////////////////// 16 | // notify相关方法 17 | ///////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | fun notifyDataSetChanged() 20 | 21 | /** 22 | * 也支持remove或insert最后一条(不建议使用) 23 | */ 24 | fun notifyItemChanged(position: Int) 25 | 26 | /** 27 | * 如果出现原数据和新数据size不相等,请分别调用其他方法,不要单独使用changed 28 | */ 29 | fun notifyItemRangeChanged(positionStart: Int, itemCount: Int) 30 | 31 | fun notifyItemInserted(position: Int) 32 | 33 | fun notifyItemRangeInserted(positionStart: Int, itemCount: Int) 34 | 35 | /** 36 | * 移动规则:先移除[fromPosition],移除完成后再添加到[toPosition],在list里相当于remoteAt(fromPosition)然后add(toPosition) 37 | */ 38 | fun notifyItemMoved(fromPosition: Int, toPosition: Int) 39 | 40 | fun notifyItemRemoved(position: Int) 41 | 42 | fun notifyItemRangeRemoved(positionStart: Int, itemCount: Int) 43 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/interfaces/IHeaderFooterListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.interfaces 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.widget.FrameLayout 7 | import androidx.annotation.LayoutRes 8 | 9 | /** 10 | * 所有list的adapter的接口 11 | */ 12 | interface IHeaderFooterListAdapter : IListAdapter { 13 | 14 | /** 15 | * 支持手动设置FrameLayout.LayoutParams的属性:with、height、margin,不支持gravity 16 | * 如果没有Params则默认宽高为match、wrap 17 | */ 18 | var headerView: View? 19 | var footerView: View? 20 | 21 | override val headerViewCount get() = if (hasHeaderView) 1 else 0 22 | override val hasHeaderView get() = headerView != null 23 | override val footerViewCount get() = if (hasFooterView) 1 else 0 24 | override val hasFooterView get() = footerView != null 25 | 26 | fun removeHeaderView() { 27 | headerView = null 28 | } 29 | 30 | fun removeFooterView() { 31 | footerView = null 32 | } 33 | 34 | fun setHeaderView(context: Context, @LayoutRes layoutRes: Int) { 35 | if (layoutRes == 0) { 36 | removeHeaderView() 37 | return 38 | } 39 | headerView = 40 | LayoutInflater.from(context).inflate(layoutRes, FrameLayout(context), false) 41 | } 42 | 43 | fun setFooterView(context: Context, @LayoutRes layoutRes: Int) { 44 | if (layoutRes == 0) { 45 | removeFooterView() 46 | return 47 | } 48 | footerView = 49 | LayoutInflater.from(context).inflate(layoutRes, FrameLayout(context), false) 50 | } 51 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/interfaces/IListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.interfaces 2 | 3 | import kotlin.math.min 4 | 5 | /** 6 | * 所有list的adapter的接口 7 | */ 8 | interface IListAdapter : IAdapter { 9 | val list: MutableList 10 | 11 | /** 12 | * 获取指定bean 13 | * 14 | * @throws IndexOutOfBoundsException 不用多说吧 15 | */ 16 | fun getItemData(listPosition: Int): BEAN { 17 | return list[listPosition] 18 | } 19 | 20 | fun getItemDataOrNull(listPosition: Int): BEAN? { 21 | return list.getOrNull(listPosition) 22 | } 23 | 24 | /** 25 | * 清空list,不刷新adapter 26 | */ 27 | fun clearList() { 28 | list.clear() 29 | } 30 | 31 | /** 32 | * 添加全部条目,不刷新adapter,[addAllListAndNotify] 33 | */ 34 | fun addAllList(addList: Collection?) { 35 | if (addList != null && list !== addList) { 36 | list.addAll(addList) 37 | } 38 | } 39 | 40 | fun listSize(): Int { 41 | return list.size 42 | } 43 | 44 | /** 45 | * list是否为空 46 | */ 47 | fun isEmptyList(): Boolean { 48 | return list.isEmpty() 49 | } 50 | 51 | ///////////////////////////////////////////////////////////////////////////////////////////////// 52 | // notify相关方法 53 | ///////////////////////////////////////////////////////////////////////////////////////////////// 54 | 55 | /** 56 | * 这个只是方便判断header、footer预留的属性,默认为0和false,由[IHeaderFooterListAdapter]实现 57 | */ 58 | val headerViewCount get() = 0 59 | val hasHeaderView get() = false 60 | val footerViewCount get() = 0 61 | val hasFooterView get() = false 62 | 63 | /** 64 | * 刷新全部数据 65 | * @param newList 新的list 66 | */ 67 | fun notifyDataSetChanged(newList: List?) { 68 | if (newList !== list) { //同一个对象当然啥都不需要干了 69 | list.clear() 70 | if (newList != null) { 71 | list.addAll(newList) 72 | } 73 | } 74 | notifyDataSetChanged() 75 | } 76 | 77 | /** 78 | * 刷新list的position,解决[notifyItemChanged]的position问题 79 | */ 80 | fun notifyListItemChanged(listPosition: Int) { 81 | if (listPosition < 0 || listPosition >= listSize()) { 82 | return 83 | } 84 | notifyItemChanged(listPosition + headerViewCount) 85 | } 86 | 87 | /** 88 | * 更新指定数据 89 | * @param newBean 被替换的数据 90 | * @param listPosition 被替换的位置 91 | */ 92 | fun notifyListItemChanged(newBean: BEAN, listPosition: Int) { 93 | if (listPosition < 0 || listPosition >= listSize()) { 94 | return 95 | } 96 | list[listPosition] = newBean 97 | notifyListItemChanged(listPosition) 98 | } 99 | 100 | /** 101 | * 更新第一条数据 102 | */ 103 | fun notifyListItemFirstChanged(newBean: BEAN) { 104 | notifyListItemChanged(newBean, 0) 105 | } 106 | 107 | /** 108 | * 更新最后一条数据 109 | */ 110 | fun notifyListItemLastChanged(newBean: BEAN) { 111 | notifyListItemChanged(newBean, listSize() - 1) 112 | } 113 | 114 | fun notifyListItemRangeChanged(listPositionStart: Int, itemCount: Int) { 115 | if (listPositionStart < 0 || itemCount <= 0) { 116 | return 117 | } 118 | notifyItemRangeChanged(listPositionStart + headerViewCount, itemCount) 119 | } 120 | 121 | /** 122 | * @param newList 被替换的数据 123 | */ 124 | fun notifyListItemRangeChanged(newList: List?, listPositionStart: Int) { 125 | if (newList.isNullOrEmpty() || listPositionStart < 0 || listPositionStart >= listSize()) { 126 | return 127 | } 128 | val outCount = newList.size + listPositionStart - listSize() 129 | 130 | when { 131 | outCount <= 0 -> { 132 | notifyListItemRangeChanged(listPositionStart, newList.size) 133 | newList.forEachIndexed { index, bean -> 134 | list[listPositionStart + index] = bean 135 | } 136 | } 137 | 138 | else -> { 139 | //刷新的数据比以前多了,多的数据使用Insert 140 | val oldSize = listSize() 141 | list.subList(listPositionStart, listSize()).clear() 142 | list.addAll(newList) 143 | notifyListItemRangeChanged(listPositionStart, oldSize - listPositionStart) 144 | notifyItemRangeInserted(oldSize, outCount) 145 | } 146 | } 147 | } 148 | 149 | fun notifyListItemInserted(listPosition: Int) { 150 | if (listPosition < 0 || listPosition >= listSize()) { 151 | return 152 | } 153 | notifyItemInserted(listPosition + headerViewCount) 154 | } 155 | 156 | /** 157 | * 将数据插入到[list]并刷新 158 | * @param insertBean 要插入的数据 159 | */ 160 | fun notifyListItemInserted(insertBean: BEAN, listPosition: Int) { 161 | if (listPosition < 0 || listPosition > listSize()) { 162 | return 163 | } 164 | list.add(listPosition, insertBean) 165 | notifyListItemInserted(listPosition) 166 | } 167 | 168 | fun notifyListItemFirstInserted(insertBean: BEAN) { 169 | notifyListItemInserted(insertBean, 0) 170 | } 171 | 172 | fun notifyListItemLastInserted(insertBean: BEAN) { 173 | notifyListItemInserted(insertBean, listSize()) 174 | } 175 | 176 | fun notifyListItemRangeInserted(listPositionStart: Int, itemCount: Int) { 177 | if (listPositionStart < 0 || listPositionStart > listSize() || itemCount <= 0) { 178 | return 179 | } 180 | 181 | notifyItemRangeInserted(listPositionStart + headerViewCount, itemCount) 182 | } 183 | 184 | /** 185 | * 将数据插入到[list]的指定位置并刷新 186 | * @param insertedList 插入的数据 187 | * @param listPositionStart 插入数据起始位置,默认插入到列表最后 188 | * @param itemCount [insertedList]要插入多少条进去,默认全部插入 189 | */ 190 | fun notifyListItemRangeInserted(insertedList: List?, listPositionStart: Int = listSize(), itemCount: Int = insertedList?.size ?: 0) { 191 | if (insertedList.isNullOrEmpty() || listPositionStart < 0 || listPositionStart > listSize() || itemCount <= 0) { 192 | return 193 | } 194 | when (listPositionStart) { 195 | 0 -> { 196 | if (insertedList.size == 1) { 197 | list.add(0, insertedList[0]) 198 | } else { 199 | val temp = insertedList + list 200 | list.clear() 201 | list.addAll(temp) 202 | } 203 | } 204 | 205 | listSize() -> { 206 | list.addAll(insertedList) 207 | } 208 | 209 | else -> { 210 | val t1 = list.subList(0, listPositionStart).toList() 211 | val t3 = list.subList(listPositionStart, listSize()).toList() 212 | list.clear() 213 | list.addAll(t1) 214 | list.addAll(insertedList) 215 | list.addAll(t3) 216 | } 217 | } 218 | notifyItemRangeInserted(listPositionStart + headerViewCount, itemCount) 219 | } 220 | 221 | /** 222 | * @param isMovedData 是否移动该数据 223 | * false:你已经自己移动过了,这里只需要调用更新数据 224 | * true:移动该条数据 225 | */ 226 | fun notifyListItemMoved(listFromPosition: Int, listToPosition: Int, isMovedData: Boolean = false) { 227 | if (listFromPosition == listToPosition || listFromPosition < 0 || listFromPosition >= listSize() || listToPosition < 0 || listToPosition >= listSize()) { 228 | return 229 | } 230 | if (isMovedData) { 231 | val bean = list.removeAt(listFromPosition) 232 | list.add(listToPosition, bean) 233 | } 234 | notifyItemMoved(listFromPosition + headerViewCount, listToPosition + headerViewCount) 235 | } 236 | 237 | /** 238 | * @param isRemoData 是否删除该数据 239 | * false:你已经自己删除过了,这里只需要调用更新数据 240 | * true:删除该条数据 241 | */ 242 | fun notifyListItemRemoved(listPosition: Int, isRemoData: Boolean = false) { 243 | if (listPosition < 0 || listPosition > listSize()) { 244 | return 245 | } 246 | if (isRemoData) { 247 | if (listPosition >= listSize()) { 248 | return 249 | } 250 | list.removeAt(listPosition) 251 | } 252 | notifyItemRemoved(listPosition + headerViewCount) 253 | } 254 | 255 | fun notifyListItemRangeRemoved(listPositionStart: Int, itemCount: Int, isRemoData: Boolean = false) { 256 | if (listPositionStart < 0 || listPositionStart > listSize() || itemCount <= 0) { 257 | return 258 | } 259 | if (isRemoData) { 260 | if (listPositionStart >= listSize()) { 261 | return 262 | } 263 | list.subList(listPositionStart, min(listSize(), listPositionStart + itemCount)).clear() 264 | } 265 | notifyItemRangeRemoved(listPositionStart + headerViewCount, itemCount) 266 | } 267 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/BaseAdapterExt.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.view.ViewGroup 4 | import androidx.viewbinding.ViewBinding 5 | import com.wang.adapters.adapter.BaseAdapter 6 | import com.wang.adapters.adapter.BaseExpandableAdapter 7 | import com.wang.adapters.adapter.BaseListAdapter 8 | import com.wang.adapters.adapter.BaseListCycleAdapter 9 | import com.wang.adapters.adapter.BaseMultiItemAdapter 10 | import com.wang.adapters.container.BaseContainerAdapter 11 | import com.wang.adapters.container.bean.IContainerBean 12 | import com.wang.adapters.holder.BaseViewHolder 13 | 14 | inline fun createBaseAdapter( 15 | crossinline onViewTypeCallback: BaseAdapter.(position: Int) -> Int, 16 | crossinline onCountCallback: BaseAdapter.() -> Int, 17 | crossinline onCreateCallback: BaseAdapter.(parent: ViewGroup, viewType: Int) -> BaseViewHolder, 18 | crossinline onBindCallback: BaseAdapter.(holder: BaseViewHolder<*>, position: Int) -> Unit 19 | ) = object : BaseAdapter() { 20 | 21 | override fun getItemViewType(position: Int) = this.onViewTypeCallback(position) 22 | 23 | override fun getItemCount() = this.onCountCallback() 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = this.onCreateCallback(parent, viewType) 26 | 27 | override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) = this.onBindCallback(holder, position) 28 | } 29 | 30 | /** 31 | * 不带ViewBinding功能,也不能使用[BaseViewHolder.vb] 32 | */ 33 | inline fun createListAdapter( 34 | list: List? = null, 35 | crossinline onCreateCallback: BaseListAdapter.(parent: ViewGroup) -> BaseViewHolder, 36 | crossinline onBindCallback: BaseListAdapter.(holder: BaseViewHolder<*>, bean: B) -> Unit 37 | ) = 38 | object : BaseListAdapter(list) { 39 | override fun onBindListViewHolder(holder: BaseViewHolder, bean: B) = 40 | this.onBindCallback(holder, bean) 41 | 42 | override fun onCreateListViewHolder(parent: ViewGroup): BaseViewHolder = 43 | this.onCreateCallback(parent) 44 | } 45 | 46 | /** 47 | * 在mvvm里,一般的adapter就是普通的v层,所以降级到activity里去 48 | */ 49 | inline fun createListVbAdapter( 50 | list: List? = null, 51 | crossinline onBindCallback: BaseListAdapter.(holder: BaseViewHolder, bean: B) -> Unit 52 | ) = 53 | object : BaseListAdapter(list) { 54 | override fun onBindListViewHolder(holder: BaseViewHolder, bean: B) = 55 | this.onBindCallback(holder, bean) 56 | 57 | override fun onCreateListViewHolder(parent: ViewGroup): BaseViewHolder = 58 | BaseViewHolder(ViewBindingHelper.getViewBindingInstanceByClass(parent.layoutInflater, parent)) 59 | } 60 | 61 | /** 62 | * 带vb 63 | */ 64 | inline fun createListVbAdapter( 65 | list: List? = null, 66 | crossinline onBindCallback: BaseListAdapter.(holder: BaseViewHolder, vb: VB, bean: B) -> Unit 67 | ) = createListVbAdapter(list) { holder, bean -> this.onBindCallback(holder, holder.vb, bean) } 68 | 69 | inline fun createCycleAdapter( 70 | list: List? = null, 71 | crossinline onBindCallback: BaseListCycleAdapter.(holder: BaseViewHolder, bean: B) -> Unit 72 | ) = 73 | object : BaseListCycleAdapter(list) { 74 | override fun onBindListViewHolder(holder: BaseViewHolder, bean: B) = 75 | this.onBindCallback(holder, bean) 76 | 77 | override fun onCreateListViewHolder(parent: ViewGroup): BaseViewHolder = 78 | BaseViewHolder(ViewBindingHelper.getViewBindingInstanceByClass(parent.layoutInflater, parent)) 79 | } 80 | 81 | /** 82 | * 带vb 83 | * @param onBindCallback holder的position见[BaseListCycleAdapter.cycleListPosition] 84 | */ 85 | inline fun createCycleAdapter( 86 | list: List? = null, 87 | crossinline onBindCallback: BaseListCycleAdapter.(holder: BaseViewHolder, vb: VB, bean: B) -> Unit 88 | ) = 89 | object : BaseListCycleAdapter(list) { 90 | override fun onBindListViewHolder(holder: BaseViewHolder, bean: B) = 91 | this.onBindCallback(holder, holder.vb, bean) 92 | 93 | override fun onCreateListViewHolder(parent: ViewGroup): BaseViewHolder = 94 | BaseViewHolder(ViewBindingHelper.getViewBindingInstanceByClass(parent.layoutInflater, parent)) 95 | } 96 | 97 | inline fun createExpandableAdapter( 98 | crossinline childCountCallback: BaseExpandableAdapter.(parentItem: B, parentPosition: Int) -> Int, 99 | crossinline onBindParentCallback: BaseExpandableAdapter.(holder: BaseViewHolder, parentItem: B, parentPosition: Int) -> Unit, 100 | crossinline onBindChildCallback: BaseExpandableAdapter.(holder: BaseViewHolder, parentItem: B, childPosition: Int) -> Unit 101 | ) = 102 | object : BaseExpandableAdapter(V1::class.java, V2::class.java) { 103 | 104 | override fun getChildCount(parentItem: B, parentPosition: Int) = this.childCountCallback(parentItem, parentPosition) 105 | 106 | override fun onBindParent(holder: BaseViewHolder, parentItem: B, parentPosition: Int) = 107 | this.onBindParentCallback(holder, parentItem, parentPosition) 108 | 109 | override fun onBindChild(holder: BaseViewHolder, parentItem: B, childPosition: Int) = 110 | this.onBindChildCallback(holder, parentItem, childPosition) 111 | } 112 | 113 | fun createMultiAdapter(list: List? = null) = BaseMultiItemAdapter(list) 114 | 115 | fun createContainerAdapter(list: List? = null) = BaseContainerAdapter(list) -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/Exts.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import androidx.recyclerview.widget.GridLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.viewbinding.ViewBinding 8 | import com.wang.adapters.R 9 | 10 | /** 11 | * 获取见[findVbByTag] 12 | */ 13 | fun ViewBinding.saveVbToTag() { 14 | this.root.setTag(R.id.tag_view_binding_obj, this) 15 | } 16 | 17 | @Suppress("NOTHING_TO_INLINE") 18 | inline fun Context.layoutInflate(): LayoutInflater = LayoutInflater.from(this) 19 | 20 | /** 21 | * 倒序遍历 22 | */ 23 | inline fun List.forEachReverseSequence(action: (index: Int, T) -> Unit) { 24 | for (index in downIndices) { 25 | action(index, this[index]) 26 | } 27 | } 28 | 29 | val Collection<*>.downIndices: IntProgression 30 | get() = size - 1 downTo 0 31 | 32 | /** 33 | * 如果有就直接返回,如果没有就创建并设置,一般用于被嵌套的RecyclerView中 34 | */ 35 | inline fun > RecyclerView.getAdapterOrCreate(createCallback: () -> T): T { 36 | return (adapter as? T) ?: createCallback.invoke().also { adapter = it } 37 | } 38 | 39 | /** 40 | * 如果有就直接返回,如果没有就创建并设置,一般用于被嵌套的RecyclerView中 41 | */ 42 | inline fun RecyclerView.getLayoutManagerOrCreate(createCallback: () -> T): T { 43 | return (layoutManager as? T) ?: createCallback.invoke().also { layoutManager = it } 44 | } 45 | 46 | /** 47 | * 设置[GridLayoutManager]的spanSizeLookup,如果不是GridLayoutManager则会直接崩溃 48 | */ 49 | inline fun RecyclerView.setGridLayoutManagerSpanSizeLookup(crossinline createCallback: (absPosition: Int) -> Int) { 50 | (this.layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { 51 | override fun getSpanSize(position: Int) = createCallback.invoke(position) 52 | } 53 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/ViewBindingHelper.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.annotation.MainThread 6 | import androidx.viewbinding.ViewBinding 7 | import java.lang.reflect.Method 8 | import java.lang.reflect.ParameterizedType 9 | 10 | @Suppress("UNCHECKED_CAST") 11 | object ViewBindingHelper { 12 | /** 13 | * 缓存已找到的class 14 | * viewBinding是有限的,所以不需要清 15 | */ 16 | private val cacheClass = HashMap>(64) 17 | private val cacheInflateMethod = HashMap, Method>(64) 18 | 19 | /** 20 | * 获取ViewBinding的class 21 | * 22 | * @param myClass 当前类的class(getClass()) 23 | */ 24 | @MainThread 25 | fun getViewBindingClass(myClass: Class<*>?): Class? { 26 | if (myClass == Any::class.java || myClass == null) { 27 | return null 28 | } 29 | val cacheKey = myClass.name 30 | cacheClass[cacheKey]?.let { 31 | return it as Class 32 | } 33 | //遍历父类所有泛型参数 34 | (myClass.genericSuperclass as? ParameterizedType)?.actualTypeArguments?.forEach { type -> 35 | if (type is Class<*>) { 36 | if (ViewBinding::class.java.isAssignableFrom(type)) { 37 | val clazz = type as Class 38 | cacheClass[cacheKey] = clazz//缓存 39 | return clazz 40 | } 41 | } 42 | } 43 | //继续循环父类查询 44 | return getViewBindingClass(myClass.superclass) 45 | } 46 | 47 | /** 48 | * 获取ViewBinding的class,直接指定泛型位置 49 | * 50 | * @param myClass 当前类的class(getClass()) 51 | * @param typeIndex 泛型的位置,如:你的泛型在第二个位置传1 52 | */ 53 | @MainThread 54 | fun getViewBindingClassFromIndex( 55 | myClass: Class<*>?, 56 | typeIndex: Int 57 | ): Class? { 58 | if (myClass == Any::class.java || myClass == null) { 59 | return null 60 | } 61 | val cacheKey = "${myClass.name}#$typeIndex" 62 | cacheClass[cacheKey]?.let { 63 | return it as Class 64 | } 65 | //直接取对应index的泛型参数 66 | ((myClass.genericSuperclass as? ParameterizedType) 67 | ?.actualTypeArguments 68 | ?.getOrNull(typeIndex) as? Class<*>) 69 | ?.let { 70 | if (ViewBinding::class.java.isAssignableFrom(it)) { 71 | val clazz = it as Class 72 | cacheClass[cacheKey] = clazz//缓存 73 | return clazz 74 | } 75 | } 76 | //继续循环父类查询 77 | return getViewBindingClassFromIndex(myClass.superclass, typeIndex) 78 | } 79 | 80 | /** 81 | * 根据类获取vb实例 82 | * @param obj 当前带ViewBinding泛型的实例类 83 | */ 84 | @MainThread 85 | fun getViewBindingInstance( 86 | obj: Any, 87 | layoutInflater: LayoutInflater, 88 | container: ViewGroup?, 89 | attachToParent: Boolean = false 90 | ): T { 91 | val bindingCls: Class = getViewBindingClass(obj.javaClass) 92 | ?: throw IllegalArgumentException("没有找到类${obj}的ViewBinding,请检查") 93 | return getViewBindingInstanceByClass(bindingCls, layoutInflater, container, attachToParent) 94 | } 95 | 96 | /** 97 | * 根据vb class获取vb实例 98 | */ 99 | @MainThread 100 | fun getViewBindingInstanceByClass( 101 | clz: Class, 102 | layoutInflater: LayoutInflater, 103 | container: ViewGroup?, 104 | attachToParent: Boolean = false 105 | ): T { 106 | try { 107 | val method = cacheInflateMethod.getOrPut(clz) { 108 | clz.getDeclaredMethod( 109 | "inflate", 110 | LayoutInflater::class.java, 111 | ViewGroup::class.java, 112 | Boolean::class.java 113 | ) 114 | } 115 | val vb = method.invoke(null, layoutInflater, container, attachToParent) as T 116 | //保存自身,方便其他框架使用 117 | vb.saveVbToTag() 118 | return vb 119 | } catch (e: Exception) { 120 | e.printStackTrace() 121 | throw IllegalArgumentException("无法实例化${clz},请注意是否开启了ViewBinding.inflate混淆", e) 122 | } 123 | } 124 | 125 | /** 126 | * inline实现 127 | */ 128 | inline fun getViewBindingInstanceByClass( 129 | layoutInflater: LayoutInflater, 130 | container: ViewGroup?, 131 | attachToParent: Boolean = false 132 | ) = getViewBindingInstanceByClass(T::class.java, layoutInflater, container, attachToParent) 133 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.View.OnClickListener 6 | import android.view.ViewGroup 7 | import androidx.annotation.IdRes 8 | import androidx.recyclerview.widget.RecyclerView 9 | import androidx.viewbinding.ViewBinding 10 | import com.wang.adapters.R 11 | 12 | /** 13 | * 避免快速点击 14 | */ 15 | @JvmOverloads 16 | inline fun T.setOnFastClickListener( 17 | clickInterval: Long = 300, 18 | crossinline block: (T) -> Unit 19 | ) { 20 | setOnClickListener(object : OnClickListener { 21 | var timestamp = 0L 22 | override fun onClick(v: View) { 23 | val now = System.currentTimeMillis() 24 | if (isClickable && now - timestamp >= clickInterval) { 25 | block(this@setOnFastClickListener) 26 | } 27 | timestamp = now 28 | } 29 | }) 30 | } 31 | 32 | @Suppress("NOTHING_TO_INLINE") 33 | inline fun View.findVbByTag() = rootView.getTypeTag(R.id.tag_view_binding_obj) 34 | 35 | /** 36 | * 根据root找到对应的vb(必须使用[ViewBindingHelper]相关方法或者手动调用[saveVbToTag]才会find到) 37 | */ 38 | @Suppress("NOTHING_TO_INLINE") 39 | inline fun View.findRootVbByTag() = rootView.findVbByTag() 40 | 41 | @Suppress("NOTHING_TO_INLINE") 42 | inline fun View.getTypeTag(@IdRes id: Int): T? = getTag(id) as? T 43 | 44 | inline val View.layoutInflater: LayoutInflater get() = context.layoutInflate() 45 | 46 | const val MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT 47 | const val WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT 48 | 49 | private val dlpMethod = run { 50 | val method = ViewGroup::class.java.getDeclaredMethod("generateDefaultLayoutParams") 51 | method.isAccessible = true 52 | return@run method 53 | } 54 | 55 | /** 56 | * [ViewGroup.generateDefaultLayoutParams]是protected的,所以有此拓展 57 | */ 58 | fun ViewGroup.getDefLayoutParams(): ViewGroup.LayoutParams { 59 | return if (this is RecyclerView) { 60 | this.layoutManager?.generateDefaultLayoutParams() ?: RecyclerView.LayoutParams( 61 | ViewGroup.LayoutParams.MATCH_PARENT, 62 | ViewGroup.LayoutParams.WRAP_CONTENT 63 | ) 64 | } else { 65 | dlpMethod.invoke(this) as ViewGroup.LayoutParams 66 | } 67 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/ViewGroupWrapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import androidx.core.view.get 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.viewpager.widget.ViewPager 8 | import androidx.viewpager2.widget.ViewPager2 9 | 10 | object ViewGroupWrapUtils { 11 | /** 12 | * 获得child的宽高 13 | */ 14 | fun getChildWidthHeight(vg: ViewGroup, listener: (width: Int, height: Int) -> Unit) { 15 | vg.post { 16 | if (vg.childCount > 0 && (vg[0].width > 0 || vg[0].height > 0)) { 17 | listener.invoke(vg[0].width, vg[0].height) 18 | } else { 19 | vg.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { 20 | override fun onLayoutChange(view: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { 21 | if (vg.childCount > 0) { 22 | val childAt = vg[0] 23 | if (childAt.width > 0 || childAt.height > 0) { 24 | vg.removeOnLayoutChangeListener(this) 25 | listener.invoke(childAt.width, childAt.height) 26 | } 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * 将vp或rv的高/宽与第一个child高/宽一致(相当于wrap) 36 | * 37 | * @param isWidth 修改宽还是高 38 | */ 39 | fun wrap(rv: RecyclerView, isWidth: Boolean) { 40 | rv.post { 41 | if (rv.childCount > 0) { 42 | setMeasureSize(rv, rv[0], isWidth) 43 | } else { 44 | rv.addOnChildAttachStateChangeListener(object : RecyclerView.OnChildAttachStateChangeListener { 45 | override fun onChildViewAttachedToWindow(view: View) { 46 | if (rv.childCount > 0) { 47 | rv.removeOnChildAttachStateChangeListener(this) 48 | setMeasureSize(rv, rv[0], isWidth) 49 | } 50 | } 51 | 52 | override fun onChildViewDetachedFromWindow(view: View) {} 53 | }) 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 将vp或rv的高/宽与第一个child高/宽一致(相当于wrap) 60 | * 61 | * @param isWidth 修改宽还是高 62 | */ 63 | fun wrap(vp: ViewPager, isWidth: Boolean) { 64 | vp.post { 65 | if (vp.childCount > 0) { 66 | setMeasureSize(vp, vp[0], isWidth) 67 | } else { 68 | vp.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { 69 | override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { 70 | if (vp.childCount > 0) { 71 | vp.removeOnLayoutChangeListener(this) 72 | setMeasureSize(vp, vp[0], isWidth) 73 | } 74 | } 75 | }) 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * 将vp或rv的高/宽与第一个child高/宽一致(相当于wrap) 82 | * 83 | * @param isWidth 修改宽还是高 84 | */ 85 | fun wrap(vp: ViewPager2, isWidth: Boolean) { 86 | val rv = vp[0] as RecyclerView 87 | rv.post { 88 | if (rv.childCount > 0) { 89 | setMeasureSize(vp, rv[0], isWidth) 90 | } else { 91 | rv.addOnChildAttachStateChangeListener(object : RecyclerView.OnChildAttachStateChangeListener { 92 | override fun onChildViewAttachedToWindow(view: View) { 93 | if (rv.childCount > 0) { 94 | rv.removeOnChildAttachStateChangeListener(this) 95 | setMeasureSize(vp, rv[0], isWidth) 96 | } 97 | } 98 | 99 | override fun onChildViewDetachedFromWindow(view: View) {} 100 | }) 101 | } 102 | } 103 | } 104 | 105 | private fun setMeasureSize(vg: ViewGroup, child: View, isWidth: Boolean) { 106 | child.post { 107 | val dm = vg.resources.displayMetrics 108 | if (isWidth) { 109 | val screenWidth = dm.widthPixels 110 | child.measure( 111 | View.MeasureSpec.makeMeasureSpec(screenWidth * 5, View.MeasureSpec.AT_MOST), 112 | View.MeasureSpec.makeMeasureSpec(child.measuredHeight, View.MeasureSpec.EXACTLY) 113 | ) 114 | vg.layoutParams.width = child.measuredWidth + vg.paddingLeft + vg.paddingRight 115 | } else { 116 | val screenHeight = dm.heightPixels 117 | child.measure( 118 | View.MeasureSpec.makeMeasureSpec(child.measuredWidth, View.MeasureSpec.EXACTLY), 119 | View.MeasureSpec.makeMeasureSpec(screenHeight * 5, View.MeasureSpec.AT_MOST) 120 | ) 121 | vg.layoutParams.height = child.measuredHeight + vg.paddingTop + vg.paddingBottom 122 | } 123 | vg.layoutParams = vg.layoutParams 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /adapters/src/main/java/com/wang/adapters/utils/ViewHolderExt.kt: -------------------------------------------------------------------------------- 1 | package com.wang.adapters.utils 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.wang.adapters.interfaces.IHeaderFooterListAdapter 6 | 7 | 8 | /** 9 | * 两个position有点头疼,无从选择,合并成一个 10 | * 11 | * 注意点: 12 | * list里注意header、footer 13 | * 完全就没bind过,肯定还是-1了 14 | */ 15 | inline val RecyclerView.ViewHolder.adapterLayoutPosition: Int get() = if (layoutPosition < 0) bindingAdapterPosition else layoutPosition 16 | 17 | /** 18 | * 获取list的真正position 19 | */ 20 | inline val RecyclerView.ViewHolder.listPosition: Int get() = getListPosition(bindingAdapter) 21 | 22 | @Suppress("NOTHING_TO_INLINE") 23 | inline fun RecyclerView.ViewHolder.getListPosition(adapter: RecyclerView.Adapter<*>?) = 24 | if (adapter is IHeaderFooterListAdapter<*>) 25 | adapterLayoutPosition - adapter.headerViewCount 26 | else 27 | adapterLayoutPosition 28 | 29 | inline fun RecyclerView.ViewHolder.setOnClickListener(crossinline block: (View) -> Unit) { 30 | this.itemView.setOnClickListener { block.invoke(it) } 31 | } 32 | 33 | inline fun RecyclerView.ViewHolder.setOnLongClickListener( 34 | checkPosition: Boolean = true, 35 | crossinline block: (View) -> Boolean 36 | ) { 37 | this.itemView.setOnLongClickListener { 38 | run { 39 | if (checkPosition && this.listPosition < 0) { 40 | return@run false 41 | } 42 | block.invoke(it) 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * 由于点击事件是延迟触发的,在此期间有极小概率触发adapter刷新导致position为-1,此处对position进行了判断 49 | */ 50 | @JvmOverloads 51 | inline fun RecyclerView.ViewHolder.setOnFastClickListener( 52 | checkPosition: Boolean = true, 53 | clickInterval: Long = 300, 54 | crossinline block: (View) -> Unit 55 | ) { 56 | this.itemView.setOnFastClickListener(clickInterval) { 57 | run { 58 | if (checkPosition && this.listPosition < 0) { 59 | return@run 60 | } 61 | block.invoke(it) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /adapters/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | namespace "com.wang.example" 6 | compileSdkVersion COMPILE_SDK_VERSION 7 | defaultConfig { 8 | applicationId "com.wang.example" 9 | minSdkVersion MIN_SDK_VERSION 10 | targetSdkVersion TARGET_SDK_VERSION 11 | versionCode 101 12 | versionName "1.01" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_17 19 | targetCompatibility JavaVersion.VERSION_17 20 | } 21 | kotlinOptions { 22 | jvmTarget = '17' 23 | } 24 | buildFeatures { 25 | viewBinding = true 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation project(':adapters') 32 | implementation 'androidx.recyclerview:recyclerview:1.3.1' 33 | implementation 'androidx.appcompat:appcompat:1.6.1' 34 | implementation "androidx.core:core-ktx:1.10.1" 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/wang/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wang.example 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.appcompat.widget.AppCompatTextView 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import com.wang.adapters.adapter.BaseAdapter 9 | import com.wang.adapters.utils.adapterLayoutPosition 10 | import com.wang.adapters.utils.createListVbAdapter 11 | import com.wang.adapters.utils.createMultiAdapter 12 | import com.wang.adapters.utils.getAdapterOrCreate 13 | import com.wang.adapters.utils.listPosition 14 | import com.wang.adapters.utils.setGridLayoutManagerSpanSizeLookup 15 | import com.wang.adapters.utils.setOnFastClickListener 16 | import com.wang.example.databinding.ActivityMainBinding 17 | import com.wang.example.databinding.AdapterMainListBinding 18 | import com.wang.example.databinding.AdapterMainMultiple0Binding 19 | import com.wang.example.databinding.AdapterMainMultiple1Binding 20 | import com.wang.example.databinding.AdapterMainMultipleDefBinding 21 | import com.wang.example.databinding.AdapterMainNesBinding 22 | import com.wang.example.databinding.AdapterMainNesItemBinding 23 | 24 | class MainActivity : AppCompatActivity() { 25 | 26 | fun String.toast() { 27 | Toast.makeText(this@MainActivity, this, Toast.LENGTH_SHORT).show() 28 | } 29 | 30 | /** 31 | * 无锁不支持并发的,适用于单线程 32 | */ 33 | @Suppress("NOTHING_TO_INLINE") 34 | inline fun lazyNone(noinline initializer: () -> T): Lazy = lazy(LazyThreadSafetyMode.NONE, initializer) 35 | 36 | private val listAdapter by lazyNone { 37 | createListVbAdapter { holder, vb, bean -> 38 | holder.itemView.setBackgroundColor(if (bean.contains("10")) 0xff999999.toInt() else 0xffffffff.toInt()) 39 | vb.tvText.text = bean 40 | vb.btButton.setOnFastClickListener { 41 | doSomething() 42 | } 43 | holder.setOnFastClickListener { 44 | "点击item了${holder.listPosition}".toast() 45 | } 46 | }.apply { 47 | //自带header、footer 48 | headerView = AppCompatTextView(this@MainActivity).apply { 49 | text = "这是header" 50 | } 51 | } 52 | } 53 | 54 | private val multiAdapter by lazyNone { 55 | createMultiAdapter().apply { 56 | addMultipleItem(isThisTypeCallback = { listPosition, _ -> listPosition % 3 == 0 }) { holder, vb, bean -> 57 | vb.tvText.setTextColor(0xff00ff00.toInt()) 58 | vb.tvText.text = "多条目0:${bean.text}" 59 | holder.setOnFastClickListener { 60 | "点击item了${holder.listPosition}".toast() 61 | } 62 | } 63 | addMultipleItem(isThisTypeCallback = { listPosition, _ -> listPosition % 3 == 1 }) { holder, vb, bean -> 64 | vb.tvText2.text = "多条目1:${bean.text}" 65 | } 66 | //多条目兜底 67 | addDefaultMultipleItem() 68 | 69 | headerView = AppCompatTextView(this@MainActivity).apply { text = "这是header" } 70 | footerView = AppCompatTextView(this@MainActivity).apply { text = "这是footer" } 71 | } 72 | } 73 | 74 | private val nesAdapter by lazyNone { 75 | createListVbAdapter { holder, vb, bean -> 76 | vb.tvNesText.text = "这是嵌套外层:${bean.text},${holder.listPosition},${holder.adapterLayoutPosition}" 77 | val itemAdapter = vb.rvItemList.getAdapterOrCreate { 78 | createListVbAdapter { holder, vb, bean -> 79 | vb.tvItem.text = "这是内层$bean,${holder.listPosition},${holder.adapterLayoutPosition}" 80 | }.apply { 81 | headerView = AppCompatTextView(this@MainActivity).apply { 82 | text = "这是内层header" 83 | setOnFastClickListener { 84 | "你点击了内层header".toast() 85 | } 86 | } 87 | footerView = AppCompatTextView(this@MainActivity).apply { text = "这是内层footer" } 88 | vb.rvItemList.setGridLayoutManagerSpanSizeLookup { 89 | when (it) { 90 | 0, (itemCount - 1) -> 2 91 | else -> 1 92 | } 93 | } 94 | } 95 | } 96 | itemAdapter.notifyDataSetChanged(bean.itemTextList) 97 | }.apply { 98 | headerView = AppCompatTextView(this@MainActivity).apply { 99 | text = "这是外层header" 100 | setTextColor(0xff00ff00.toInt()) 101 | } 102 | } 103 | } 104 | 105 | private var currentAdapter = 0 106 | private val adapters: Array by lazyNone { arrayOf(listAdapter, multiAdapter, nesAdapter) } 107 | 108 | private fun doSomething() { 109 | "点击了Button".toast() 110 | } 111 | 112 | override fun onCreate(savedInstanceState: Bundle?) { 113 | super.onCreate(savedInstanceState) 114 | val vb = ActivityMainBinding.inflate(layoutInflater) 115 | setContentView(vb.root) 116 | 117 | vb.tvChange.setOnFastClickListener { 118 | currentAdapter = (currentAdapter + 1) % adapters.size 119 | changeAdapter(vb) 120 | } 121 | 122 | vb.rvMain.layoutManager = LinearLayoutManager(this) 123 | val list = ArrayList() 124 | for (i in 0..99) { 125 | val tb = TestBean("第$i") 126 | if (i % 5 == 0) { 127 | for (j in 0..9) { 128 | tb.itemTextList.add("第$i,子$j") 129 | } 130 | } 131 | list.add(tb) 132 | } 133 | listAdapter.notifyDataSetChanged(list.map { it.text }) 134 | multiAdapter.notifyDataSetChanged(list) 135 | nesAdapter.notifyDataSetChanged(list) 136 | 137 | changeAdapter(vb) 138 | } 139 | 140 | private fun changeAdapter(vb: ActivityMainBinding) { 141 | vb.rvMain.adapter = adapters[currentAdapter] 142 | } 143 | 144 | class TestBean( 145 | val text: String, 146 | val itemTextList: ArrayList = arrayListOf() 147 | ) 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_main_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_main_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 |