├── adapters
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ └── values
│ │ │ └── ids.xml
│ │ └── java
│ │ └── com
│ │ └── wang
│ │ └── adapters
│ │ ├── container
│ │ ├── bean
│ │ │ ├── IContainerBean.kt
│ │ │ └── ItemAdapterPositionInfo.kt
│ │ ├── observer
│ │ │ └── IContainerObserver.kt
│ │ ├── item
│ │ │ ├── OneContainerItemAdapter.kt
│ │ │ └── BaseContainerItemAdapter.kt
│ │ └── BaseContainerAdapter.kt
│ │ ├── holder
│ │ └── BaseViewHolder.kt
│ │ ├── adapter
│ │ ├── BaseAdapter.kt
│ │ ├── BaseFragmentPager2Adapter.kt
│ │ ├── BaseListCycleAdapter.kt
│ │ ├── BaseListAdapter.kt
│ │ ├── BaseExpandableAdapter.kt
│ │ └── BaseMultiItemAdapter.kt
│ │ ├── interfaces
│ │ ├── IAdapter.kt
│ │ ├── IHeaderFooterListAdapter.kt
│ │ └── IListAdapter.kt
│ │ ├── utils
│ │ ├── Exts.kt
│ │ ├── ViewHolderExt.kt
│ │ ├── ViewExt.kt
│ │ ├── ViewBindingHelper.kt
│ │ ├── ViewGroupWrapUtils.kt
│ │ └── BaseAdapterExt.kt
│ │ └── helper
│ │ └── ListAdapterHelper.kt
└── build.gradle
├── app
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ └── strings.xml
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── layout
│ │ │ ├── adapter_main_multiple_def.xml
│ │ │ ├── adapter_main_header.xml
│ │ │ ├── adapter_main_nes_item.xml
│ │ │ ├── adapter_main_multiple_1.xml
│ │ │ ├── adapter_main_multiple_0.xml
│ │ │ ├── adapter_main_list.xml
│ │ │ ├── activity_main.xml
│ │ │ └── adapter_main_nes.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── wang
│ │ └── example
│ │ └── MainActivity.kt
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/adapters/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | example
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weimingjue/BaseAdapter/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.apk
3 | *.zip
4 | local.properties
5 | *.hprof
6 | /captures
7 | ~$*
8 | .DS_Store
9 | .idea
10 | .gradle
11 | build
--------------------------------------------------------------------------------
/adapters/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weimingjue/BaseAdapter/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weimingjue/BaseAdapter/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 07 16:10:31 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_multiple_def.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_nes_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_multiple_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_multiple_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | mavenLocal()
7 | maven { setUrl("https://maven.aliyun.com/repository/public") }
8 | maven { setUrl("https://maven.aliyun.com/repository/google") }
9 | maven { setUrl("https://jitpack.io") }
10 | }
11 | }
12 | dependencyResolutionManagement {
13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
14 | repositories {
15 | google()
16 | mavenCentral()
17 | mavenLocal()
18 | maven { setUrl("https://maven.aliyun.com/repository/public") }
19 | maven { setUrl("https://maven.aliyun.com/repository/google") }
20 | maven { setUrl("https://jitpack.io") }
21 | }
22 | }
23 | include ':app', ':adapters'
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_main_nes.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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)
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------