├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── kotlinc.xml ├── misc.xml ├── smartfox_info.xml └── vcs.xml ├── README.md ├── ScreenShot ├── S20829-17471726.png ├── S20829-17472742.png ├── S20829-17474084.png └── S20829-17475072.png ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xiaobin │ │ └── bindingadapter │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xiaobin │ │ │ └── bindingadapter │ │ │ ├── bean │ │ │ └── ChatListBean.kt │ │ │ ├── ui │ │ │ ├── EmptyDemoActivity.java │ │ │ ├── StartActivity.java │ │ │ ├── base │ │ │ │ └── BaseActivity.java │ │ │ ├── grid │ │ │ │ ├── GridMultiActivity.java │ │ │ │ └── GridSingleActivity.java │ │ │ ├── linear │ │ │ │ ├── LinearMultiActivity.java │ │ │ │ └── LinearSingleActivity.java │ │ │ ├── pageState │ │ │ │ ├── MyEmptyPageView.java │ │ │ │ └── MyLoadMoreView.java │ │ │ └── staggered │ │ │ │ ├── StaggeredMultiActivity.java │ │ │ │ └── StaggeredSingleActivity.java │ │ │ └── utils │ │ │ └── BindAdapterUtils.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── error_image.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_svg_ban.xml │ │ ├── ic_svg_ok.xml │ │ └── shap_button.xml │ │ ├── layout │ │ ├── activity_base.xml │ │ ├── activity_empty.xml │ │ ├── item_grid.xml │ │ ├── item_head.xml │ │ ├── item_linear.xml │ │ ├── item_staggered.xml │ │ ├── item_staggered2.xml │ │ ├── item_staggered_head.xml │ │ ├── item_start.xml │ │ ├── layout_my_loading_more.xml │ │ ├── layout_my_page_empty.xml │ │ ├── layout_my_page_err.xml │ │ └── layout_my_page_loading.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xiaobin │ └── bindingadapter │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── quickbindadapter ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xiaobin │ │ └── quickbindadapter │ │ ├── BindHolder.kt │ │ ├── ItemData.kt │ │ ├── QuickBindAdapter.kt │ │ ├── QuickBindPagingAdapter.kt │ │ ├── holder │ │ ├── EmptyViewHolder.kt │ │ └── LoadViewHolder.kt │ │ ├── interfaces │ │ └── StaggeredFullSpan.kt │ │ ├── paging │ │ └── EmptyPageAdapter.kt │ │ └── view │ │ ├── BaseLoadView.kt │ │ ├── BasePageStateView.kt │ │ ├── DefaultEmptyStatePage.kt │ │ └── DefaultLoadView.kt │ └── res │ ├── layout │ ├── mc_xb_item_loadmore.xml │ ├── mc_xb_layout_page_empty.xml │ ├── mc_xb_layout_page_error.xml │ ├── mc_xb_layout_page_loading.xml │ └── mc_xb_layout_place_page.xml │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 96 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/smartfox_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickBindAdapter 2 | 3 | [![](https://jitpack.io/v/Matchas-xiaobin/QuickBindAdapter.svg)](https://jitpack.io/#Matchas-xiaobin/QuickBindAdapter) 4 | 5 | 使用Databinding简单快速实现RecyclerView多布局Adapter。只需要用这一个Adapter就够了。 6 | 7 | ### Demo的首页 8 | ![目录](ScreenShot/S20829-17471726.png "Demo的首页") 9 | 10 | ### 列表数据是空的时候显示的占位图 11 | ![空列表占位布局](ScreenShot/S20829-17472742.png "列表数据是空的时候显示的占位图") 12 | 13 | ### GridLayoutManager多种布局展示 14 | ![网格流多种布局展示](ScreenShot/S20829-17474084.png "GridLayoutManager多种布局展示") 15 | 16 | ### StaggeredLayoutManager多种布局展示 17 | ![瀑布流多种布局展示](ScreenShot/S20829-17475072.png "StaggeredLayoutManager多种布局展示") 18 | 19 | ## 在项目中引用 20 | 21 | ### Gradle 22 | 23 | Step 1. Add it in your root build.gradle at the end of repositories 24 | 25 | allprojects { 26 | repositories { 27 | ... 28 | maven { url 'https://jitpack.io' } 29 | } 30 | } 31 | 32 | Step 2. Add the dependency 33 | 34 | dependencies { 35 | implementation 'com.github.Matchas-xiaobin:QuickBindAdapter:3.0.8' 36 | } 37 | 38 | ### API多次大量改动,如果已经在使用老版本了,请注意斟酌是否升级版本: 39 | 40 | 请在demo中查看具体用法 41 | 请注意:必须开启dataBinding功能才能使用这个 42 | 请确认在项目模块下添加了如下代码: 43 | android { 44 | ... 45 | buildFeatures { 46 | dataBinding true 47 | } 48 | ... 49 | } 50 | 51 | ### 在代码中使用: 52 | 53 | QuickBindAdapter adapter = new QuickBindAdapter(); 54 | adapter.bind() 55 | adapter.setxxx() 56 | ... 57 | 58 | Kotlin: 59 | 1.委托 60 | private val adapter: QuickBindAdapter by lazy { 61 | QuickBindAdapter().apply{ 62 | bind(,,) 63 | addClicks(,,) 64 | ... 65 | } 66 | } 67 | 2.正常使用 68 | val adapter = QuickBinAdapter().apply{ 69 | bind(,,) 70 | addClicks(,,) 71 | ... 72 | } 73 | 74 | ### 绑定数据类型和布局 有几种布局,就绑定几次,每一种布局要对应一种数据类型 75 | 76 | 三个参数分别为: 数据类型 , 对应的布局 , 这个数据要设置到的属性 77 | adapter.bind(DataBean.class, R.layout.item_child, BR.data); 78 | adapter.bind(String.class, R.layout.item_group, BR.data); 79 | 80 | 绑定布局的时候,可以不绑定数据要设置到哪个属性 81 | 一般用来穿插一些固定内容不变化的布局,或者想用代码动态配置布局的,建议配合QuickBind一起使用: 82 | adapter.bind(class, layoutId); 83 | 84 | 多布局注意: 85 | 如果使用瀑布流StaggeredGridLayoutManager,假设你的spanCount为3,如果你想实现标题直接占满一行, 86 | 则需要将标题的这个实体类实现StaggeredFullSpan接口 87 | 并且瀑布流无法像gridLayoutManager那样自定义占几个spanCount 88 | 89 | ### 新增空数据时展示全屏占位图; 90 | 91 | 使用默认的空数据时展示的占位图控制器 92 | adapter.setEmptyView(context); 93 | 使用自定义的空数据时展示的占位图控制器,需要继承BasePlaceholder 94 | adapter.setEmptyView(MyEmptyPageView()); 95 | 96 | 展示 加载中 占位图: 97 | adapter.showLoadPage() 98 | adapter.showLoadPage(true)//强制显示 加载中 页面->如果有数据,则清空数据 99 | 展示 内容为空 占位图: 100 | adapter.showEmptyPage() 101 | adapter.showEmptyPage(true)//强制显示 空数据 页面->如果有数据,则清空数据 102 | 展示 加载错误 占位图: 103 | adapter.showErrorPage() 104 | adapter.showErrorPage(true)//强制显示 加载错误 页面->如果有数据,则清空数据 105 | 106 | ### QuickBind接口,用于只使用Databinding绑定控件,手动写复杂逻辑; 107 | 108 | adapter.setQuickBind((binding, itemData, position) -> { 109 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 110 | //如果是多布局,则需要做下判断: 111 | if (binding instanceof ItemStartBinding) { 112 | // R.layout.item_start 类型 的item 113 | } 114 | //也可以这样: 115 | if (itemData instanceof String) { 116 | // String 类型 的item 117 | } 118 | }); 119 | 120 | ### 添加子控件点击事件 对应每一种数据类型的item,添加其子控件的点击事件 121 | 122 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 123 | 124 | adapter.addClicks(String.class, R.id.tv_name); 125 | 126 | ### 点击事件: 127 | 128 | adapter.setOnItemClickListener(OnItemClickListener) 129 | 130 | adapter.setOnItemChildClickListener(OnItemClickListener) 131 | 132 | ### 长按事件: 133 | 134 | adapter.setOnItemLongClickListener(OnItemLongClickListener) 135 | 136 | adapter.setOnItemChildLongClickListener(OnItemLongClickListener) 137 | 138 | ### 获取item数据的api; 139 | 140 | 所有数据 141 | ItemData dataList = adapter.getListData(); 142 | 143 | 单个数据 144 | Object itemData = adapter.getItemData(position); 145 | 146 | ### 设置新数据: 147 | 148 | dataList 如果是需要多布局,建议使用ItemData 添加数据。 149 | 150 | 例如: 151 | ItemData dataList = new ItemData(); 152 | dataList.add("Group"); 153 | ChatListBean item; 154 | for (int i = 0; i < 5; i++) { 155 | item = new ChatListBean(); 156 | item.setId(String.valueOf(i)); 157 | dataList.add(item); 158 | } 159 | adapter.setNewData(dataList); 160 | 或者你也可以直接: 161 | adapter.addData("Group") 162 | ChatListBean item; 163 | for (int i = 0; i < 5; i++) { 164 | item = new ChatListBean(); 165 | item.setId(String.valueOf(i)); 166 | adapter.addData(item); 167 | } 168 | 169 | ### 添加数据 170 | 171 | 添加单个 172 | adapter.addData(object); 173 | 添加单个,并且让这个新加的处于可见范围 174 | adapter.addData(object, true); 175 | 176 | 添加多个到列表后面 177 | adapter.addData(arrayList); 178 | 添加多个到列表后面,并且让新增的第一个处于可见范围 179 | adapter.addData(arrayList, true); 180 | 181 | ### 插入数据到某个位置API; 182 | 183 | 插入单个数据到指定位置 184 | adapter.insertData(index, itemData); 185 | 插入单个数据到指定位置,并且让新插入的处于可见范围 186 | adapter.insertData(index, itemData, true); 187 | 188 | 从指定位置插入多个数据 189 | adapter.insertDatas(index, arraysData); 190 | 从指定位置插入多个数据,并且让新增的第一个处于可见范围 191 | adapter.insertDatas(index, arraysData, true); 192 | 193 | ### 移除单个 194 | 195 | adapter.remove(position); 196 | 197 | ### 替换单个 198 | 199 | //替换指定item 200 | adapter.replace(position, object); 201 | //替换指定item,并使其可见 202 | adapter.replace(position, object, true); 203 | 204 | ### 移动位置 205 | 206 | //移动某个item到另一个位置 207 | movedPositions(fromPosition, toPosition); 208 | //移动某个item到另一个位置,并且让这个item的新位置处于可见 209 | movedPositions(fromPosition, toPosition, true); 210 | 211 | ### 加载更多功能; 212 | 213 | 使用方式: 214 | adapter.setOnLoadMoreListener(@NonNull OnLoadMoreListener onLoadMoreListener); 215 | 加载成功: 216 | adapter.loadMoreSuccess(); 217 | 加载失败: 218 | adapter.loadMoreFailed(); 219 | 加载完成,没有更多数据了: 220 | adapter.loadMoreSuccessAndNoMore(); 221 | 主动调用加载更多: 222 | adapter.loadMore(); 223 | 禁止 触底自动加载更多 关闭后,列表滑动到底后,需要用户点击才会触发加载更多 224 | adapter.setAutoLoadMore(false); 225 | 关闭加载更多功能: 226 | adapter.setOnLoadMoreListener(null as (()->Unit)?) 227 | 或者(3.2.3版本开始支持): 228 | adapter.removeOnLoadMoreListener() 229 | 设置是否开启 当列表数据没有充满rv的情况下,也自动加载更多 230 | * 如果关闭了 触底自动加载更多,那么这个方法将不在起作用 231 | adapter.enableLoadMoreWhenPageNotFull(true); 232 | 233 | - 修改 正在加载 文字 234 | - 修改 加载成功 文字 235 | - 修改 加载失败 文字 236 | - 修改 没有更多数据了 文字 237 | 使用方式: 238 | DefaultLoadView loadView = new DefaultLoadView(context) 239 | loadView.setOnLoadingText("加载中...") 240 | loadView.setOnWaitLoadingText("点击加载更多") 241 | ... 242 | adapter.setLoadView(loadView); 243 | 244 | DefaultLoadView是内置的默认的。 245 | 自定义的布局需要继承BaseLoadView去实现。 246 | -------------------------------------------------------------------------------- /ScreenShot/S20829-17471726.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/ScreenShot/S20829-17471726.png -------------------------------------------------------------------------------- /ScreenShot/S20829-17472742.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/ScreenShot/S20829-17472742.png -------------------------------------------------------------------------------- /ScreenShot/S20829-17474084.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/ScreenShot/S20829-17474084.png -------------------------------------------------------------------------------- /ScreenShot/S20829-17475072.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/ScreenShot/S20829-17475072.png -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdk 34 9 | 10 | defaultConfig { 11 | applicationId "com.xiaobin.bindingadapter" 12 | minSdk 14 13 | targetSdk 34 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_17 20 | targetCompatibility JavaVersion.VERSION_17 21 | } 22 | buildFeatures { 23 | dataBinding true 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | namespace 'com.xiaobin.bindingadapter' 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | implementation 'androidx.appcompat:appcompat:1.6.1' 37 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 38 | testImplementation 'junit:junit:4.13.2' 39 | androidTestImplementation 'androidx.test:runner:1.5.2' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 41 | implementation 'androidx.recyclerview:recyclerview:1.3.1' 42 | implementation 'com.github.bumptech.glide:glide:4.15.1' 43 | annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' 44 | implementation project(path: ':quickbindadapter') 45 | implementation "androidx.core:core-ktx:1.10.1" 46 | //noinspection GradleDependency 47 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 48 | implementation 'androidx.cardview:cardview:1.0.0' 49 | } 50 | repositories { 51 | mavenCentral() 52 | } 53 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xiaobin/bindingadapter/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.xiaobin.bindingadapter", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/bean/ChatListBean.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.bean 2 | 3 | import com.xiaobin.bindingadapter.R 4 | import com.xiaobin.quickbindadapter.interfaces.StaggeredFullSpan 5 | 6 | /** 7 | * @author 小斌 8 | * @data 2019/7/30 9 | */ 10 | class ChatListBean { 11 | private var id: String? = null 12 | val drawableId: Int 13 | get() = R.mipmap.ic_launcher 14 | val name: String 15 | get() = "这个用户没有名字。ID:$id" 16 | val time: String 17 | get() = "16:16" 18 | val message: String 19 | get() = "这个人想和你聊天。" 20 | 21 | fun getId(): String { 22 | return if (id == null) "" else id!! 23 | } 24 | 25 | fun setId(id: String?) { 26 | this.id = id ?: "" 27 | } 28 | } 29 | 30 | class ChatListBean2 { 31 | private var id: String? = null 32 | val drawableId: Int 33 | get() = R.mipmap.ic_launcher 34 | val name: String 35 | get() = "这个用户没有名字。ID:$id" 36 | val time: String 37 | get() = "16:16" 38 | val message: String 39 | get() = "这个人想和你聊天。" 40 | 41 | fun getId(): String { 42 | return if (id == null) "" else id!! 43 | } 44 | 45 | fun setId(id: String?) { 46 | this.id = id ?: "" 47 | } 48 | } 49 | 50 | data class StaggeredHeadBean( 51 | val title: String 52 | ) : StaggeredFullSpan -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/EmptyDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.Toast; 6 | 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | 9 | import com.xiaobin.bindingadapter.BR; 10 | import com.xiaobin.bindingadapter.R; 11 | import com.xiaobin.bindingadapter.bean.ChatListBean; 12 | import com.xiaobin.bindingadapter.databinding.ActivityEmptyBinding; 13 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 14 | import com.xiaobin.bindingadapter.ui.pageState.MyEmptyPageView; 15 | import com.xiaobin.quickbindadapter.view.DefaultEmptyStatePage; 16 | import com.xiaobin.quickbindadapter.view.PageState; 17 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 18 | 19 | /** 20 | * @author 小斌 21 | * @data 2019/8/20 22 | **/ 23 | public class EmptyDemoActivity extends BaseActivity { 24 | 25 | private QuickBindAdapter bindAdapter; 26 | 27 | @Override 28 | protected boolean showBackButton() { 29 | return true; 30 | } 31 | 32 | @Override 33 | protected int getLayoutId() { 34 | return R.layout.activity_empty; 35 | } 36 | 37 | @Override 38 | protected String getActionTitle() { 39 | return "空数据占位布局"; 40 | } 41 | 42 | @Override 43 | protected void initView(Bundle savedInstanceState) { 44 | //修改默认初始显示无数据, 不修改则是加载中 45 | DefaultEmptyStatePage defaultEmptyStatePage = new DefaultEmptyStatePage(this); 46 | defaultEmptyStatePage.setDefaultPage(PageState.Empty); 47 | bindAdapter = QuickBindAdapter.Companion.create() 48 | .bind(ChatListBean.class, R.layout.item_linear, BR.data) 49 | .setEmptyView(defaultEmptyStatePage);//设置默认的无数据时占位布局 50 | 51 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 52 | binding.recyclerView.setAdapter(bindAdapter); 53 | } 54 | 55 | public void addData(View view) { 56 | int i = bindAdapter.getDataCount() % 2; 57 | if (i == 0) { 58 | ChatListBean item = new ChatListBean(); 59 | item.setId("嘿咻"); 60 | bindAdapter.addData(item, true); 61 | } else { 62 | bindAdapter.addData(12, true); 63 | } 64 | } 65 | 66 | public void reduceData(View view) { 67 | if (bindAdapter.getItemCount() > 0) 68 | bindAdapter.remove(bindAdapter.getItemCount() - 1); 69 | } 70 | 71 | public void loadPage(View view) { 72 | bindAdapter.showLoadPage(true); 73 | } 74 | 75 | public void errPage(View view) { 76 | bindAdapter.showErrorPage(true); 77 | } 78 | 79 | public void myPageState(View view) { 80 | Toast.makeText(this, "使用自定义布局", Toast.LENGTH_SHORT).show(); 81 | bindAdapter.setEmptyView(new MyEmptyPageView()); 82 | //在上面initView中,已经设置了默认的布局,所以这里可以重新设置一下adapter达到这个布局的目的 83 | binding.recyclerView.setAdapter(bindAdapter); 84 | } 85 | 86 | public void defaultPageState(View view) { 87 | Toast.makeText(this, "使用默认布局", Toast.LENGTH_SHORT).show(); 88 | bindAdapter.setEmptyView(new DefaultEmptyStatePage(this)); 89 | //在上面initView中,已经设置了默认的布局,所以这里可以重新设置一下adapter达到这个布局的目的 90 | binding.recyclerView.setAdapter(bindAdapter); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/StartActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | 9 | import com.xiaobin.bindingadapter.BR; 10 | import com.xiaobin.bindingadapter.R; 11 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 12 | import com.xiaobin.bindingadapter.databinding.ItemStartBinding; 13 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 14 | import com.xiaobin.bindingadapter.ui.grid.GridMultiActivity; 15 | import com.xiaobin.bindingadapter.ui.grid.GridSingleActivity; 16 | import com.xiaobin.bindingadapter.ui.linear.LinearMultiActivity; 17 | import com.xiaobin.bindingadapter.ui.linear.LinearSingleActivity; 18 | import com.xiaobin.bindingadapter.ui.staggered.StaggeredMultiActivity; 19 | import com.xiaobin.bindingadapter.ui.staggered.StaggeredSingleActivity; 20 | import com.xiaobin.quickbindadapter.view.DefaultEmptyStatePage; 21 | import com.xiaobin.quickbindadapter.view.DefaultLoadView; 22 | import com.xiaobin.quickbindadapter.view.DefaultLoadViewConfigsBean; 23 | import com.xiaobin.quickbindadapter.view.DefaultPlacePageConfigsBean; 24 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 25 | 26 | import java.util.Iterator; 27 | import java.util.LinkedList; 28 | 29 | /** 30 | * @author 小斌 31 | * @data 2019/7/31 32 | **/ 33 | public class StartActivity extends BaseActivity { 34 | 35 | @Override 36 | protected int getLayoutId() { 37 | return R.layout.activity_base; 38 | } 39 | 40 | @Override 41 | protected void initView(Bundle savedInstanceState) { 42 | /** 43 | * 全局配置默认加载更多布局的配置,仅对使用默认加载更多布局生效,尽可能早配置。 44 | * 可以放在Application里配置,这是优先级最低的 45 | * 配置这个不会影响在创建adapter时再自定义配置: 46 | * 比如: 47 | * DefaultLoadView view = new DefaultLoadView() 48 | * view.setNoMoreDataText("") 49 | * view.setOnFailedText("") 50 | * ... 51 | * adapter.setLoadView(view) 52 | */ 53 | DefaultLoadView.Companion.createGlobalConfig(() -> { 54 | DefaultLoadViewConfigsBean data = new DefaultLoadViewConfigsBean(); 55 | data.setNoMoreDataText("没有更多数据啦~~"); 56 | data.setOnFailedText("加载失败555~"); 57 | data.setOnLoadingText("正在超级努力的加载更多啦~~"); 58 | data.setOnSuccessText("加载成功啦,哒哒哒~"); 59 | data.setOnLoadingTextColor(Color.GREEN); 60 | data.setOnFailedTextColor(Color.RED); 61 | data.setOnSuccessTextColor(Color.BLUE); 62 | data.setNoMoreDataTextColor(Color.LTGRAY); 63 | return data; 64 | }); 65 | /** 66 | * 同理配置全局占位页 67 | * 仅对使用默认 占位页(DefaultPlaceholder) 控件生效 68 | */ 69 | DefaultEmptyStatePage.Companion.createGlobalConfig(() -> { 70 | DefaultPlacePageConfigsBean data = new DefaultPlacePageConfigsBean(); 71 | data.setEmptyText("似乎是空的"); 72 | data.setErrorText("获取失败!\n请检查网络!"); 73 | return data; 74 | }); 75 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 76 | QuickBindAdapter adapter = new QuickBindAdapter(this); 77 | 78 | DefaultLoadView defaultLoadItem = new DefaultLoadView(this); 79 | defaultLoadItem.setNoMoreDataText("我滴任务完成啦~"); 80 | defaultLoadItem.setOnWaitLoadingText("快碰我,我要更多!~"); 81 | defaultLoadItem.setOnFailedText("跟我玩鹰滴是吧!"); 82 | defaultLoadItem.setOnLoadingText("客官请稍等片刻~"); 83 | defaultLoadItem.setOnSuccessText("轻轻松松~"); 84 | //如果不设置字体颜色,会应用全局配置里的字体颜色。 85 | // defaultLoadItem.setOnLoadingTextColor(); 86 | //设置默认的加载更多布局 87 | adapter.setLoadMoreItemView(defaultLoadItem); 88 | //是否启用 触底 自动触发 加载更多 89 | adapter.setAutoLoadMore(true); 90 | //是否启用 列表内容没有充满布局时 触发 加载更多 91 | //setAutoLoadMore(false)的时候,即使enableLoadMoreWhenPageNotFull(true)也不会生效 92 | adapter.enableLoadMoreWhenPageNotFull(true); 93 | //设置默认的 空列表 占位布局控制器 94 | adapter.setEmptyView(new DefaultEmptyStatePage(this)); 95 | //绑定数据类型和对应的布局 96 | adapter.bind(String.class, R.layout.item_start, BR.data); 97 | //使用额外的item内容处理 98 | adapter.setQuickBind((binding, itemData, position) -> { 99 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickBind 接口,然后传入这里 100 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 101 | //如果是多布局,则需要做下判断: 102 | if (binding instanceof ItemStartBinding) { 103 | // R.layout.item_start 类型布局,在这里面写这个布局的逻辑代码 104 | } 105 | //也可以这样: 106 | if (itemData instanceof String) { 107 | // R.layout.item_start 类型布局 108 | } 109 | return null; 110 | }); 111 | //绑定列表item的点击事件 112 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 113 | Intent intent = new Intent(); 114 | Class mClass; 115 | switch (position) { 116 | case 1: 117 | //点击了 LinearLayout多布局 + 加载更多 这个item 118 | mClass = LinearMultiActivity.class; 119 | break; 120 | case 2: 121 | //点击了 GridLayout单布局 这个item 122 | mClass = GridSingleActivity.class; 123 | break; 124 | case 3: 125 | //点击了 GridLayout多布局 + 加载更多 这个item 126 | mClass = GridMultiActivity.class; 127 | break; 128 | case 4: 129 | //点击了 StaggeredGridLayout单布局 这个item 130 | mClass = StaggeredSingleActivity.class; 131 | break; 132 | case 5: 133 | //点击了 StaggeredGridLayout多布局 + 加载更多 这个item 134 | mClass = StaggeredMultiActivity.class; 135 | break; 136 | case 6: 137 | //点击了 空数据占位布局 这个item 138 | mClass = EmptyDemoActivity.class; 139 | break; 140 | default: 141 | //点击了 LinearLayout单布局 这个item 142 | mClass = LinearSingleActivity.class; 143 | break; 144 | } 145 | intent.setClass(this, mClass); 146 | startActivity(intent); 147 | return null; 148 | }); 149 | 150 | //数据,这里为了模拟加载更多效果,使用LinkedList逐个加载数据 151 | LinkedList linkedList = new LinkedList<>(); 152 | linkedList.add("LinearLayout单布局"); 153 | linkedList.add("LinearLayout多布局 + 加载更多"); 154 | linkedList.add("GridLayout单布局"); 155 | linkedList.add("GridLayout多布局 + 加载更多"); 156 | linkedList.add("StaggeredGridLayout单布局"); 157 | linkedList.add("StaggeredGridLayout多布局 + 加载更多"); 158 | linkedList.add("空数据占位布局"); 159 | Iterator iterator = linkedList.iterator(); 160 | //一秒后,添加一条数据 161 | binding.getRoot().postDelayed(() -> { 162 | adapter.addData(iterator.next()); 163 | }, 1000); 164 | 165 | /** 166 | * 配置加载更多监听,如果配置了这个,即使没有调用过adapter.setLoadMoreItemView()方法,则自动使用默认的加载布局 167 | * 如果配置加载更多方法,等同于也调用了adapter.setLoadMoreItemView(new DefaultLoadView(this)); 168 | */ 169 | // adapter.setLoadMoreItemView(new DefaultLoadView(this));//设置加载更多的item样式 170 | adapter.setOnLoadMoreListener(() -> {//设置加载更多监听,触发加载更多的时候,则会执行里面的内容 171 | //每隔0.3s,增加一条数据 172 | binding.getRoot().postDelayed(() -> { 173 | if (iterator.hasNext()) { 174 | //建议是先调用loadMoreSuccess再设置数据 175 | adapter.addData(iterator.next()); 176 | adapter.loadMoreSuccess(); 177 | } else { 178 | adapter.loadMoreSuccessAndNoMore(); 179 | } 180 | }, 300); 181 | return null; 182 | }); 183 | //给列表绑定adapter适配器 184 | binding.recyclerView.setAdapter(adapter); 185 | 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.base; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.MenuItem; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.appcompat.app.ActionBar; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.databinding.DataBindingUtil; 12 | import androidx.databinding.ViewDataBinding; 13 | 14 | import com.xiaobin.bindingadapter.R; 15 | 16 | /** 17 | * @author 小斌 18 | * @data 2019/7/31 19 | **/ 20 | public abstract class BaseActivity extends AppCompatActivity { 21 | 22 | protected VD binding; 23 | 24 | protected boolean showBackButton() { 25 | return false; 26 | } 27 | 28 | protected abstract int getLayoutId(); 29 | 30 | protected String getActionTitle() { 31 | return getString(R.string.app_name); 32 | } 33 | 34 | protected abstract void initView(Bundle savedInstanceState); 35 | 36 | @Override 37 | protected void onCreate(@Nullable Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | binding = DataBindingUtil.inflate(LayoutInflater.from(this), getLayoutId(), null, false); 40 | setContentView(binding.getRoot()); 41 | 42 | initView(savedInstanceState); 43 | 44 | ActionBar actionBar = getSupportActionBar(); 45 | if (actionBar != null) { 46 | actionBar.setTitle(getActionTitle()); 47 | if (showBackButton()) { 48 | actionBar.setDisplayHomeAsUpEnabled(true); 49 | } else { 50 | actionBar.setDisplayHomeAsUpEnabled(false); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 57 | if (item.getItemId() == android.R.id.home) { 58 | finish(); 59 | return true; 60 | } 61 | return super.onOptionsItemSelected(item); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/grid/GridMultiActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.grid; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toast; 5 | 6 | import androidx.recyclerview.widget.GridLayoutManager; 7 | 8 | import com.xiaobin.bindingadapter.BR; 9 | import com.xiaobin.bindingadapter.R; 10 | import com.xiaobin.bindingadapter.bean.ChatListBean; 11 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 12 | import com.xiaobin.bindingadapter.databinding.ItemGridBinding; 13 | import com.xiaobin.bindingadapter.databinding.ItemHeadBinding; 14 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 15 | import com.xiaobin.quickbindadapter.ItemData; 16 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 17 | 18 | /** 19 | * GridLayoutManager 多布局示例 20 | * 21 | * @author xiaobin 22 | */ 23 | public class GridMultiActivity extends BaseActivity { 24 | 25 | @Override 26 | protected boolean showBackButton() { 27 | return true; 28 | } 29 | 30 | @Override 31 | protected int getLayoutId() { 32 | return R.layout.activity_base; 33 | } 34 | 35 | @Override 36 | protected String getActionTitle() { 37 | return "GridLayout多布局"; 38 | } 39 | 40 | @Override 41 | protected void initView(Bundle savedInstanceState) { 42 | QuickBindAdapter adapter = new QuickBindAdapter(this); 43 | //绑定数据类型和布局 44 | adapter.bind(ChatListBean.class, R.layout.item_grid, BR.data); 45 | adapter.bind(String.class, R.layout.item_head, BR.data); 46 | //添加子控件点击事件 47 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 48 | adapter.addClicks(String.class, R.id.tv_name); 49 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3); 50 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 51 | @Override 52 | public int getSpanSize(int position) { 53 | Object itemData = adapter.getItemData(position); 54 | if (itemData instanceof ChatListBean) { 55 | return 1; 56 | } else if (itemData instanceof String) { 57 | return 3; 58 | } 59 | return 1; 60 | } 61 | }); 62 | //倒序排列 63 | // gridLayoutManager.setReverseLayout(true); 64 | //务必先调用setLayoutManager 65 | binding.recyclerView.setLayoutManager(gridLayoutManager); 66 | binding.recyclerView.setAdapter(adapter); 67 | 68 | adapter.setOnLoadMoreListener(() -> { 69 | Toast.makeText(this, "加载更多触发", Toast.LENGTH_SHORT).show(); 70 | binding.getRoot().postDelayed(() -> { 71 | ItemData dataList = new ItemData(); 72 | ChatListBean item; 73 | for (int i = 0; i < 15; i++) { 74 | switch (i) { 75 | case 0: 76 | dataList.add("分组一"); 77 | break; 78 | case 3: 79 | dataList.add("分组二"); 80 | break; 81 | case 5: 82 | dataList.add("分组三"); 83 | break; 84 | case 7: 85 | dataList.add("分组四"); 86 | break; 87 | default: 88 | break; 89 | } 90 | item = new ChatListBean(); 91 | item.setId(String.valueOf(i)); 92 | dataList.add(item); 93 | } 94 | adapter.addData(dataList, true); 95 | if (adapter.getItemCount() > 50) { 96 | adapter.loadMoreSuccessAndNoMore(); 97 | } else { 98 | adapter.loadMoreSuccess(); 99 | } 100 | }, 1500); 101 | return null; 102 | }); 103 | 104 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 105 | adapter.setQuickBind((binding, itemData, position) -> { 106 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 107 | //如果是多布局,则在这里需要判断布局类型,参考: 108 | if (binding instanceof ItemGridBinding) { 109 | // R.layout.item_grid 类型布局 110 | } else if (binding instanceof ItemHeadBinding) { 111 | // R.layout.item_head 类型布局 112 | } 113 | //也可以这样: 114 | if (itemData instanceof ChatListBean) { 115 | // R.layout.item_grid 类型布局 116 | } else if (itemData instanceof String) { 117 | // R.layout.item_head 类型布局 118 | } 119 | return null; 120 | }); 121 | //绑定item的点击事件 122 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 123 | //item点击事件 124 | if (data instanceof ChatListBean) { 125 | ChatListBean mData = (ChatListBean) data; 126 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条", Toast.LENGTH_SHORT).show(); 127 | } else if (data instanceof String) { 128 | Toast.makeText(this, "点击的是 " + data + " 分组条", Toast.LENGTH_SHORT).show(); 129 | } 130 | return null; 131 | }); 132 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 133 | //item上子控件 点击事件 134 | int viewId = view.getId(); 135 | if (data instanceof ChatListBean) { 136 | ChatListBean mData = (ChatListBean) data; 137 | if (viewId == R.id.iv_image) { 138 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 头像", Toast.LENGTH_SHORT).show(); 139 | } else if (viewId == R.id.tv_name) { 140 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 名字", Toast.LENGTH_SHORT).show(); 141 | } else if (viewId == R.id.tv_message) { 142 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 消息", Toast.LENGTH_SHORT).show(); 143 | } 144 | } else if (data instanceof String) { 145 | Toast.makeText(this, "点击的是 " + data + " 分组条的 TextView", Toast.LENGTH_SHORT).show(); 146 | } 147 | return null; 148 | }); 149 | 150 | //数据方式一: 151 | ItemData dataList = new ItemData(); 152 | ChatListBean item; 153 | for (int i = 0; i < 15; i++) { 154 | switch (i) { 155 | case 0: 156 | dataList.add("分组一"); 157 | break; 158 | case 3: 159 | dataList.add("分组二"); 160 | break; 161 | case 5: 162 | dataList.add("分组三"); 163 | break; 164 | case 7: 165 | dataList.add("分组四"); 166 | break; 167 | default: 168 | break; 169 | } 170 | item = new ChatListBean(); 171 | item.setId(String.valueOf(i)); 172 | dataList.add(item); 173 | } 174 | adapter.setNewData(dataList); 175 | 176 | //数据方式二: 177 | // List dataList2 = new ArrayList<>(); 178 | // for (int i = 0; i < 15; i++) { 179 | // dataList2.add(new ChatListBean()); 180 | // } 181 | // adapter.setNewData(dataList2); 182 | 183 | //移除数据: 184 | // adapter.remove(adapter.getItemCount() - 1); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/grid/GridSingleActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.grid; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.widget.Toast; 6 | 7 | import androidx.recyclerview.widget.GridLayoutManager; 8 | 9 | import com.xiaobin.bindingadapter.BR; 10 | import com.xiaobin.bindingadapter.R; 11 | import com.xiaobin.bindingadapter.bean.ChatListBean; 12 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 13 | import com.xiaobin.bindingadapter.databinding.ItemGridBinding; 14 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 15 | import com.xiaobin.quickbindadapter.ItemData; 16 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 17 | 18 | /** 19 | * GridLayoutManager 示例 20 | * @author xiaobin 21 | */ 22 | public class GridSingleActivity extends BaseActivity { 23 | 24 | @Override 25 | protected boolean showBackButton() { 26 | return true; 27 | } 28 | 29 | @Override 30 | protected int getLayoutId() { 31 | return R.layout.activity_base; 32 | } 33 | 34 | @Override 35 | protected String getActionTitle() { 36 | return "GridLayout单布局"; 37 | } 38 | 39 | @Override 40 | protected void initView(Bundle savedInstanceState) { 41 | QuickBindAdapter adapter = new QuickBindAdapter(this); 42 | //绑定数据类型和布局 43 | adapter.bind(ChatListBean.class, R.layout.item_grid, BR.data); 44 | //添加子控件点击事件 45 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 46 | 47 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3); 48 | gridLayoutManager.setReverseLayout(false); 49 | binding.recyclerView.setLayoutManager(gridLayoutManager); 50 | binding.recyclerView.setAdapter(adapter); 51 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 52 | adapter.setQuickBind((binding, itemData, position) -> { 53 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 54 | ItemGridBinding mBinding = (ItemGridBinding) binding; 55 | mBinding.tvName.setTextColor(Color.RED); 56 | return null; 57 | }); 58 | //绑定item的点击事件 59 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 60 | //item点击事件 61 | ChatListBean itemData = (ChatListBean) data; 62 | Toast.makeText(this, "ID: " + itemData.getId(), Toast.LENGTH_SHORT).show(); 63 | return null; 64 | }); 65 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 66 | //item上子控件 点击事件 67 | ChatListBean itemData = (ChatListBean) data; 68 | int viewId = view.getId(); 69 | if (viewId == R.id.iv_image) { 70 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 头像", Toast.LENGTH_SHORT).show(); 71 | } else if (viewId == R.id.tv_name) { 72 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 名字", Toast.LENGTH_SHORT).show(); 73 | } else if (viewId == R.id.tv_message) { 74 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 消息", Toast.LENGTH_SHORT).show(); 75 | } 76 | return null; 77 | }); 78 | 79 | //数据方式一: 80 | ItemData dataList = new ItemData(); 81 | ChatListBean item; 82 | for (int i = 0; i < 15; i++) { 83 | item = new ChatListBean(); 84 | item.setId(String.valueOf(i)); 85 | dataList.add(item); 86 | } 87 | adapter.setNewData(dataList); 88 | 89 | //数据方式二: 90 | // List dataList2 = new ArrayList<>(); 91 | // for (int i = 0; i < 15; i++) { 92 | // item = new ChatListBean(); 93 | // item.setId(String.valueOf(i)); 94 | // dataList2.add(item); 95 | // } 96 | // adapter.setNewData(dataList2); 97 | 98 | //添加数据: 99 | item = new ChatListBean(); 100 | item.setId("嘿咻"); 101 | adapter.addData(item); 102 | 103 | //移除数据: 104 | // adapter.remove(adapter.getItemCount() - 1); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/linear/LinearMultiActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.linear; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.widget.Toast; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.recyclerview.widget.LinearLayoutManager; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.xiaobin.bindingadapter.BR; 13 | import com.xiaobin.bindingadapter.R; 14 | import com.xiaobin.bindingadapter.bean.ChatListBean; 15 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 16 | import com.xiaobin.bindingadapter.databinding.ItemHeadBinding; 17 | import com.xiaobin.bindingadapter.databinding.ItemLinearBinding; 18 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 19 | import com.xiaobin.quickbindadapter.ItemData; 20 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 21 | 22 | /** 23 | * LinearLayoutManager 多布局示例 24 | * 25 | * @author xiaobin 26 | */ 27 | public class LinearMultiActivity extends BaseActivity { 28 | 29 | @Override 30 | protected boolean showBackButton() { 31 | return true; 32 | } 33 | 34 | @Override 35 | protected int getLayoutId() { 36 | return R.layout.activity_base; 37 | } 38 | 39 | @Override 40 | protected String getActionTitle() { 41 | return "LinearLayout多布局"; 42 | } 43 | 44 | @Override 45 | protected void initView(Bundle savedInstanceState) { 46 | QuickBindAdapter adapter = new QuickBindAdapter(this); 47 | //绑定数据类型和布局 48 | adapter.bind(ChatListBean.class, R.layout.item_linear, BR.data); 49 | adapter.bind(String.class, R.layout.item_head, BR.data); 50 | //添加子控件点击事件 51 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 52 | adapter.addClicks(String.class, R.id.tv_name); 53 | 54 | //务必先调用setLayoutManager 55 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 56 | binding.recyclerView.setAdapter(adapter); 57 | adapter.setOnLoadMoreListener(() -> { 58 | Toast.makeText(this, "加载更多触发", Toast.LENGTH_SHORT).show(); 59 | binding.getRoot().postDelayed(() -> { 60 | ItemData dataList = new ItemData(); 61 | ChatListBean item; 62 | for (int i = 0; i < 15; i++) { 63 | switch (i) { 64 | case 0: 65 | dataList.add("分组一"); 66 | break; 67 | case 3: 68 | dataList.add("分组二"); 69 | break; 70 | case 5: 71 | dataList.add("分组三"); 72 | break; 73 | case 7: 74 | dataList.add("分组四"); 75 | break; 76 | default: 77 | break; 78 | } 79 | item = new ChatListBean(); 80 | item.setId(String.valueOf(i)); 81 | dataList.add(item); 82 | } 83 | adapter.addData(dataList); 84 | Log.d("TTTTT", "initView: 拿到数据,调用addData()"); 85 | if (adapter.getItemCount() > 50) { 86 | adapter.loadMoreSuccessAndNoMore(); 87 | } else { 88 | adapter.loadMoreSuccess(); 89 | } 90 | }, 1500); 91 | return null; 92 | }); 93 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 94 | adapter.setQuickBind((binding, itemData, position) -> { 95 | //如果是多布局,则在这里需要判断布局类型,参考: 96 | if (binding instanceof ItemLinearBinding) { 97 | // R.layout.item_linear 类型布局,在这里面写这个布局的逻辑代码 98 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 99 | } else if (binding instanceof ItemHeadBinding) { 100 | // R.layout.item_head 类型布局 101 | } 102 | //也可以这样: 103 | if (itemData instanceof ChatListBean) { 104 | // R.layout.item_linear 类型布局 105 | } else if (itemData instanceof String) { 106 | // R.layout.item_head 类型布局 107 | } 108 | return null; 109 | }); 110 | //绑定item的点击事件 111 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 112 | //item点击事件 113 | if (data instanceof ChatListBean) { 114 | ChatListBean mData = (ChatListBean) data; 115 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条", Toast.LENGTH_SHORT).show(); 116 | } else if (data instanceof String) { 117 | Toast.makeText(this, "点击的是 " + data + " 分组条", Toast.LENGTH_SHORT).show(); 118 | } 119 | return null; 120 | }); 121 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 122 | //item上子控件 点击事件 123 | int viewId = view.getId(); 124 | if (data instanceof ChatListBean) { 125 | ChatListBean mData = (ChatListBean) data; 126 | if (viewId == R.id.iv_image) { 127 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 头像", Toast.LENGTH_SHORT).show(); 128 | } else if (viewId == R.id.tv_name) { 129 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 名字", Toast.LENGTH_SHORT).show(); 130 | } else if (viewId == R.id.tv_message) { 131 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 消息", Toast.LENGTH_SHORT).show(); 132 | } 133 | } else if (data instanceof String) { 134 | Toast.makeText(this, "点击的是 " + data + " 分组条的 TextView", Toast.LENGTH_SHORT).show(); 135 | } 136 | 137 | return null; 138 | }); 139 | 140 | //数据方式一: 141 | ItemData dataList = new ItemData(); 142 | ChatListBean item; 143 | for (int i = 0; i < 15; i++) { 144 | switch (i) { 145 | case 0: 146 | dataList.add("分组一"); 147 | break; 148 | case 3: 149 | dataList.add("分组二"); 150 | break; 151 | case 5: 152 | dataList.add("分组三"); 153 | break; 154 | case 7: 155 | dataList.add("分组四"); 156 | break; 157 | default: 158 | break; 159 | } 160 | item = new ChatListBean(); 161 | item.setId(String.valueOf(i)); 162 | dataList.add(item); 163 | } 164 | adapter.setNewData(dataList); 165 | 166 | //数据方式二: 167 | // List dataList2 = new ArrayList<>(); 168 | // for (int i = 0; i < 15; i++) { 169 | // dataList2.add(new ChatListBean()); 170 | // } 171 | // adapter.setNewData(dataList2); 172 | 173 | //添加数据: 174 | adapter.addData("分组五"); 175 | adapter.addData(new ChatListBean()); 176 | 177 | //移除数据: 178 | // adapter.remove(adapter.getItemCount() - 1); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/linear/LinearSingleActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.linear; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.widget.Toast; 6 | 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | 9 | import com.xiaobin.bindingadapter.BR; 10 | import com.xiaobin.bindingadapter.R; 11 | import com.xiaobin.bindingadapter.bean.ChatListBean; 12 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 13 | import com.xiaobin.bindingadapter.databinding.ItemLinearBinding; 14 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 15 | import com.xiaobin.quickbindadapter.ItemData; 16 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 17 | 18 | /** 19 | * LinearLayoutManager 示例 20 | * 21 | * @author xiaobin 22 | */ 23 | public class LinearSingleActivity extends BaseActivity { 24 | 25 | @Override 26 | protected boolean showBackButton() { 27 | return true; 28 | } 29 | 30 | @Override 31 | protected int getLayoutId() { 32 | return R.layout.activity_base; 33 | } 34 | 35 | @Override 36 | protected String getActionTitle() { 37 | return "LinearLayout单布局"; 38 | } 39 | 40 | @Override 41 | protected void initView(Bundle savedInstanceState) { 42 | QuickBindAdapter adapter = new QuickBindAdapter(this); 43 | //绑定数据类型和布局 44 | adapter.bind(ChatListBean.class, R.layout.item_linear, BR.data); 45 | //添加子控件点击事件 46 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 47 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 48 | binding.recyclerView.setAdapter(adapter); 49 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 50 | adapter.setQuickBind((binding, itemData, position) -> { 51 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 52 | ItemLinearBinding mBinding = (ItemLinearBinding) binding; 53 | mBinding.tvName.setTextColor(Color.RED); 54 | return null; 55 | }); 56 | //绑定item的点击事件 57 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 58 | //item点击事件 59 | ChatListBean itemData = (ChatListBean) data; 60 | Toast.makeText(this, "Index: " + position + ", ID: " + itemData.getId(), Toast.LENGTH_SHORT).show(); 61 | return null; 62 | }); 63 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 64 | //item上子控件 点击事件 65 | ChatListBean itemData = (ChatListBean) data; 66 | int viewId = view.getId(); 67 | if (viewId == R.id.iv_image) { 68 | Toast.makeText(this, "点击的是Index: " + position + ", ID: " + itemData.getId() + " 的 头像", Toast.LENGTH_SHORT).show(); 69 | } else if (viewId == R.id.tv_name) { 70 | Toast.makeText(this, "点击的是Index: " + position + ", ID: " + itemData.getId() + " 的 名字", Toast.LENGTH_SHORT).show(); 71 | } else if (viewId == R.id.tv_message) { 72 | Toast.makeText(this, "点击的是Index: " + position + ", ID: " + itemData.getId() + " 的 消息", Toast.LENGTH_SHORT).show(); 73 | } 74 | return null; 75 | }); 76 | adapter.setOnItemLongClickListener((adapter1, view, data, position) -> { 77 | //item长按事件,长按删除这个item 78 | // ChatListBean itemData = (ChatListBean) data; 79 | // adapter.remove(position); 80 | // Toast.makeText(this, "删除index: " + position + ", ID:" + itemData.getId(), Toast.LENGTH_SHORT).show(); 81 | 82 | //item长按事件,在这个位置插入一个新的item 83 | ChatListBean newData = new ChatListBean(); 84 | String s = String.valueOf(System.currentTimeMillis()); 85 | newData.setId(s.substring(s.length() -3)); 86 | adapter.insertData(position, newData); 87 | Toast.makeText(this, "插入index: " + position + ", ID:" + newData.getId(), Toast.LENGTH_SHORT).show(); 88 | return true; 89 | }); 90 | 91 | //数据方式一: 92 | ItemData dataList = new ItemData(); 93 | ChatListBean item; 94 | for (int i = 0; i < 15; i++) { 95 | item = new ChatListBean(); 96 | item.setId(String.valueOf(i)); 97 | dataList.add(item); 98 | } 99 | adapter.setNewData(dataList); 100 | 101 | //数据方式二: 102 | // List dataList2 = new ArrayList<>(); 103 | // for (int i = 0; i < 15; i++) { 104 | // item = new ChatListBean(); 105 | // item.setId(String.valueOf(i)); 106 | // dataList2.add(item); 107 | // } 108 | // adapter.setNewData(dataList2); 109 | 110 | //添加数据: 111 | item = new ChatListBean(); 112 | item.setId("嘿咻"); 113 | adapter.addData(item); 114 | 115 | //移除数据: 116 | // adapter.remove(adapter.getItemCount() - 1); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/pageState/MyEmptyPageView.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.pageState; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.xiaobin.bindingadapter.R; 6 | import com.xiaobin.bindingadapter.databinding.LayoutMyPageEmptyBinding; 7 | import com.xiaobin.bindingadapter.databinding.LayoutMyPageErrBinding; 8 | import com.xiaobin.bindingadapter.databinding.LayoutMyPageLoadingBinding; 9 | import com.xiaobin.quickbindadapter.view.BasePageStateView; 10 | import com.xiaobin.quickbindadapter.view.PageState; 11 | 12 | /** 13 | * 自定义空数据占位布局 14 | */ 15 | public class MyEmptyPageView extends BasePageStateView< 16 | LayoutMyPageEmptyBinding, 17 | LayoutMyPageErrBinding, 18 | LayoutMyPageLoadingBinding> { 19 | 20 | public MyEmptyPageView() { 21 | super(R.layout.layout_my_page_empty, 22 | R.layout.layout_my_page_err, 23 | R.layout.layout_my_page_loading); 24 | } 25 | 26 | @Override 27 | protected void onPageCreate(@NonNull PageState action) { 28 | 29 | } 30 | 31 | @Override 32 | protected void onActionCall(@NonNull PageState action) { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/pageState/MyLoadMoreView.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.pageState; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import com.xiaobin.bindingadapter.R; 9 | import com.xiaobin.bindingadapter.databinding.LayoutMyLoadingMoreBinding; 10 | import com.xiaobin.quickbindadapter.view.BaseLoadView; 11 | 12 | /** 13 | * 自定义加载更多item 14 | */ 15 | public class MyLoadMoreView extends BaseLoadView { 16 | 17 | public MyLoadMoreView() { 18 | super(R.layout.layout_my_loading_more); 19 | } 20 | 21 | @Override 22 | protected void initView(@Nullable LayoutMyLoadingMoreBinding loadView) { 23 | if (loadView == null) return; 24 | loadView.ivImage.setVisibility(View.GONE); 25 | loadView.progress.setVisibility(View.VISIBLE); 26 | loadView.tvLabel.setText("正在努力加载中。。。"); 27 | } 28 | 29 | protected void onLoading(LayoutMyLoadingMoreBinding loadView) { 30 | if (loadView == null) return; 31 | loadView.ivImage.setVisibility(View.GONE); 32 | loadView.progress.setVisibility(View.VISIBLE); 33 | loadView.tvLabel.setText("正在努力加载中。。。"); 34 | } 35 | 36 | protected void onNoMoreData(LayoutMyLoadingMoreBinding loadView) { 37 | if (loadView == null) return; 38 | loadView.ivImage.setVisibility(View.VISIBLE); 39 | loadView.ivImage.setImageResource(R.drawable.ic_svg_ok); 40 | loadView.progress.setVisibility(View.GONE); 41 | loadView.tvLabel.setText("全都加载完了"); 42 | } 43 | 44 | protected void onLoadSuccess(LayoutMyLoadingMoreBinding loadView) { 45 | if (loadView == null) return; 46 | loadView.ivImage.setVisibility(View.VISIBLE); 47 | loadView.ivImage.setImageResource(R.drawable.ic_svg_ok); 48 | loadView.progress.setVisibility(View.GONE); 49 | loadView.tvLabel.setText("加载成功"); 50 | } 51 | 52 | protected void onLoadFailed(LayoutMyLoadingMoreBinding loadView) { 53 | if (loadView == null) return; 54 | loadView.ivImage.setVisibility(View.VISIBLE); 55 | loadView.ivImage.setImageResource(R.drawable.error_image); 56 | loadView.progress.setVisibility(View.GONE); 57 | loadView.tvLabel.setText("加载失败"); 58 | } 59 | 60 | protected void onWaitLoading(LayoutMyLoadingMoreBinding loadView) { 61 | if (loadView == null) return; 62 | loadView.ivImage.setVisibility(View.VISIBLE); 63 | loadView.ivImage.setImageResource(R.drawable.error_image); 64 | loadView.progress.setVisibility(View.GONE); 65 | loadView.tvLabel.setText("点击加载更多"); 66 | } 67 | 68 | @Override 69 | protected void onStateChange(LayoutMyLoadingMoreBinding loadView, @NonNull LoadMoreState state) { 70 | switch (state) { 71 | case LOADING: 72 | onLoading(loadView); 73 | break; 74 | case SUCCESS: 75 | onLoadSuccess(loadView); 76 | break; 77 | case FAILED: 78 | onLoadFailed(loadView); 79 | break; 80 | case NO_MORE: 81 | onNoMoreData(loadView); 82 | break; 83 | case WAIT_LOADING: 84 | onWaitLoading(loadView); 85 | break; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/staggered/StaggeredMultiActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.staggered; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toast; 5 | 6 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 7 | 8 | import com.xiaobin.bindingadapter.BR; 9 | import com.xiaobin.bindingadapter.R; 10 | import com.xiaobin.bindingadapter.bean.ChatListBean; 11 | import com.xiaobin.bindingadapter.bean.ChatListBean2; 12 | import com.xiaobin.bindingadapter.bean.StaggeredHeadBean; 13 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 14 | import com.xiaobin.bindingadapter.databinding.ItemStaggered2Binding; 15 | import com.xiaobin.bindingadapter.databinding.ItemStaggeredBinding; 16 | import com.xiaobin.bindingadapter.databinding.ItemStaggeredHeadBinding; 17 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 18 | import com.xiaobin.quickbindadapter.ItemData; 19 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 20 | 21 | /** 22 | * StaggeredLayoutManager 多布局示例 23 | * 24 | * @author xiaobin 25 | */ 26 | public class StaggeredMultiActivity extends BaseActivity { 27 | 28 | @Override 29 | protected boolean showBackButton() { 30 | return true; 31 | } 32 | 33 | @Override 34 | protected int getLayoutId() { 35 | return R.layout.activity_base; 36 | } 37 | 38 | @Override 39 | protected String getActionTitle() { 40 | return "StaggeredGridLayout多布局"; 41 | } 42 | 43 | @Override 44 | protected void initView(Bundle savedInstanceState) { 45 | QuickBindAdapter adapter = new QuickBindAdapter(this); 46 | //绑定数据类型和布局 47 | adapter.bind(ChatListBean.class, R.layout.item_staggered, BR.data); 48 | adapter.bind(ChatListBean2.class, R.layout.item_staggered2, BR.data); 49 | /** 50 | * 这是一个横跨整个屏幕宽度的头部 51 | * 因为StaggeredGridLayoutManager没有提供GridLayoutManager的spanLookup接口 52 | * 所以这里想要实现这个效果,只需要将需要当做头部的布局所对应的数据包装一下实体类并且务必实现StaggeredFullSpan接口 53 | * 否则不生效 54 | * 参考:StaggeredHeadBean 类 55 | */ 56 | adapter.bind(StaggeredHeadBean.class, R.layout.item_staggered_head, BR.data); 57 | //添加子控件点击事件 58 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 59 | adapter.addClicks(ChatListBean2.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 60 | adapter.addClicks(StaggeredHeadBean.class, R.id.tv_name); 61 | 62 | //务必先调用setLayoutManager 63 | binding.recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); 64 | binding.recyclerView.setAdapter(adapter); 65 | 66 | adapter.setOnLoadMoreListener(() -> { 67 | Toast.makeText(this, "加载更多触发", Toast.LENGTH_SHORT).show(); 68 | binding.getRoot().postDelayed(() -> { 69 | adapter.addData(createSomeData(), true); 70 | if (adapter.getItemCount() > 50) { 71 | adapter.loadMoreSuccessAndNoMore(); 72 | } else { 73 | adapter.loadMoreSuccess(); 74 | } 75 | }, 1500); 76 | return null; 77 | }); 78 | 79 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 80 | adapter.setQuickBind((binding, itemData, position) -> { 81 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 82 | //如果是多布局,则在这里需要判断布局类型,参考: 83 | if (binding instanceof ItemStaggeredBinding) { 84 | // R.layout.item_staggered 类型布局 85 | } else if (binding instanceof ItemStaggered2Binding) { 86 | // R.layout.item_staggered2 类型布局 87 | } else if (binding instanceof ItemStaggeredHeadBinding) { 88 | // R.layout.item_staggered_head 类型布局 89 | } 90 | //也可以这样: 91 | if (itemData instanceof ChatListBean) { 92 | // R.layout.item_staggered 类型布局 93 | } else if (itemData instanceof ChatListBean2) { 94 | // R.layout.item_staggered2 类型布局 95 | } else if (itemData instanceof StaggeredHeadBean) { 96 | // R.layout.item_staggered_head 类型布局 97 | } 98 | return null; 99 | }); 100 | //绑定item的点击事件 101 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 102 | //item点击事件 103 | if (data instanceof ChatListBean) { 104 | ChatListBean mData = (ChatListBean) data; 105 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条", Toast.LENGTH_SHORT).show(); 106 | } else if (data instanceof ChatListBean2) { 107 | ChatListBean2 mData = (ChatListBean2) data; 108 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条", Toast.LENGTH_SHORT).show(); 109 | } else if (data instanceof StaggeredHeadBean) { 110 | StaggeredHeadBean mData = (StaggeredHeadBean) data; 111 | Toast.makeText(this, "点击的是 " + mData.getTitle() + " 分组条", Toast.LENGTH_SHORT).show(); 112 | } 113 | return null; 114 | }); 115 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 116 | //item上子控件 点击事件 117 | int viewId = view.getId(); 118 | if (data instanceof ChatListBean) { 119 | ChatListBean mData = (ChatListBean) data; 120 | if (viewId == R.id.iv_image) { 121 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 头像", Toast.LENGTH_SHORT).show(); 122 | } else if (viewId == R.id.tv_name) { 123 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 名字", Toast.LENGTH_SHORT).show(); 124 | } else if (viewId == R.id.tv_message) { 125 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 消息", Toast.LENGTH_SHORT).show(); 126 | } 127 | } else if (data instanceof ChatListBean2) { 128 | ChatListBean2 mData = (ChatListBean2) data; 129 | if (viewId == R.id.iv_image) { 130 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 头像", Toast.LENGTH_SHORT).show(); 131 | } else if (viewId == R.id.tv_name) { 132 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 名字", Toast.LENGTH_SHORT).show(); 133 | } else if (viewId == R.id.tv_message) { 134 | Toast.makeText(this, "点击的是 " + mData.getId() + " 消息条的 消息", Toast.LENGTH_SHORT).show(); 135 | } 136 | } else if (data instanceof StaggeredHeadBean) { 137 | StaggeredHeadBean mData = (StaggeredHeadBean) data; 138 | Toast.makeText(this, "点击的是 " + mData.getTitle() + " 分组条的 TextView", Toast.LENGTH_SHORT).show(); 139 | } 140 | 141 | return null; 142 | }); 143 | 144 | //数据方式一: 145 | adapter.setNewData(createSomeData()); 146 | 147 | //数据方式二: 148 | // List dataList2 = new ArrayList<>(); 149 | // for (int i = 0; i < 15; i++) { 150 | // dataList2.add(new ChatListBean()); 151 | // } 152 | // adapter.setNewData(dataList2); 153 | 154 | //添加数据: 155 | adapter.addData("分组五"); 156 | adapter.addData(new ChatListBean()); 157 | 158 | //移除数据: 159 | // adapter.remove(adapter.getItemCount() - 1); 160 | } 161 | 162 | private ItemData createSomeData() { 163 | ItemData dataList = new ItemData(); 164 | for (int i = 0; i < 15; i++) { 165 | switch (i) { 166 | case 0: 167 | dataList.add(new StaggeredHeadBean("分组一")); 168 | break; 169 | case 3: 170 | dataList.add(new StaggeredHeadBean("分组二")); 171 | break; 172 | case 5: 173 | dataList.add(new StaggeredHeadBean("分组三")); 174 | break; 175 | case 7: 176 | dataList.add(new StaggeredHeadBean("分组四")); 177 | break; 178 | default: 179 | break; 180 | } 181 | if (i % 3 == 0) { 182 | ChatListBean2 item = new ChatListBean2(); 183 | item.setId(String.valueOf(i)); 184 | dataList.add(item); 185 | } else { 186 | ChatListBean item = new ChatListBean(); 187 | item.setId(String.valueOf(i)); 188 | dataList.add(item); 189 | } 190 | } 191 | return dataList; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/ui/staggered/StaggeredSingleActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.ui.staggered; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.widget.Toast; 6 | 7 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 8 | 9 | import com.xiaobin.bindingadapter.BR; 10 | import com.xiaobin.bindingadapter.R; 11 | import com.xiaobin.bindingadapter.bean.ChatListBean; 12 | import com.xiaobin.bindingadapter.databinding.ActivityBaseBinding; 13 | import com.xiaobin.bindingadapter.databinding.ItemStaggeredBinding; 14 | import com.xiaobin.bindingadapter.ui.base.BaseActivity; 15 | import com.xiaobin.quickbindadapter.ItemData; 16 | import com.xiaobin.quickbindadapter.QuickBindAdapter; 17 | 18 | /** 19 | * StaggeredLayoutManager 示例 20 | * @author xiaobin 21 | */ 22 | public class StaggeredSingleActivity extends BaseActivity { 23 | 24 | @Override 25 | protected boolean showBackButton() { 26 | return true; 27 | } 28 | 29 | @Override 30 | protected int getLayoutId() { 31 | return R.layout.activity_base; 32 | } 33 | 34 | @Override 35 | protected String getActionTitle() { 36 | return "StaggeredGridLayout单布局"; 37 | } 38 | 39 | @Override 40 | protected void initView(Bundle savedInstanceState) { 41 | QuickBindAdapter adapter = new QuickBindAdapter(this); 42 | //绑定数据类型和布局 43 | adapter.bind(ChatListBean.class, R.layout.item_staggered, BR.data); 44 | //添加子控件点击事件 45 | adapter.addClicks(ChatListBean.class, R.id.iv_image, R.id.tv_name, R.id.tv_message); 46 | 47 | binding.recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); 48 | binding.recyclerView.setAdapter(adapter); 49 | //如果你想要在这里或者是在adapter中,写逻辑代码,可以这样:也可以单独写个类 实现 QuickCovert接口,然后传入这里 50 | adapter.setQuickBind((binding, itemData, position) -> { 51 | // binding 是这个item本身,itemData 是这个item的数据,position 是这个item所在列表中的位置 52 | ItemStaggeredBinding mBinding = (ItemStaggeredBinding) binding; 53 | mBinding.tvName.setTextColor(Color.RED); 54 | return null; 55 | }); 56 | //绑定item的点击事件 57 | adapter.setOnItemClickListener((adapter1, view, data, position) -> { 58 | //item点击事件 59 | ChatListBean itemData = (ChatListBean) data; 60 | Toast.makeText(this, "ID: " + itemData.getId(), Toast.LENGTH_SHORT).show(); 61 | return null; 62 | }); 63 | adapter.setOnItemChildClickListener((adapter1, view, data, position) -> { 64 | //item上子控件 点击事件 65 | ChatListBean itemData = (ChatListBean) data; 66 | int viewId = view.getId(); 67 | if (viewId == R.id.iv_image) { 68 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 头像", Toast.LENGTH_SHORT).show(); 69 | } else if (viewId == R.id.tv_name) { 70 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 名字", Toast.LENGTH_SHORT).show(); 71 | } else if (viewId == R.id.tv_message) { 72 | Toast.makeText(this, "点击的是ID: " + itemData.getId() + " 的 消息", Toast.LENGTH_SHORT).show(); 73 | } 74 | return null; 75 | }); 76 | 77 | //数据方式一: 78 | ItemData dataList = new ItemData(); 79 | ChatListBean item; 80 | for (int i = 0; i < 15; i++) { 81 | item = new ChatListBean(); 82 | item.setId(String.valueOf(i)); 83 | dataList.add(item); 84 | } 85 | adapter.setNewData(dataList); 86 | 87 | //数据方式二: 88 | // List dataList2 = new ArrayList<>(); 89 | // for (int i = 0; i < 15; i++) { 90 | // item = new ChatListBean(); 91 | // item.setId(String.valueOf(i)); 92 | // dataList2.add(item); 93 | // } 94 | // adapter.setNewData(dataList2); 95 | 96 | //添加数据: 97 | item = new ChatListBean(); 98 | item.setId("嘿咻"); 99 | adapter.addData(item); 100 | 101 | //移除数据: 102 | // adapter.remove(adapter.getItemCount() - 1); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaobin/bindingadapter/utils/BindAdapterUtils.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter.utils; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | 8 | import androidx.annotation.DrawableRes; 9 | import androidx.core.content.ContextCompat; 10 | import androidx.databinding.BindingAdapter; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.bumptech.glide.request.RequestOptions; 14 | 15 | /** 16 | * @author 小斌 17 | * @data 2019/7/30 18 | **/ 19 | public class BindAdapterUtils { 20 | 21 | @BindingAdapter("circleImage") 22 | public static void circleImage(View view, String url) { 23 | Glide.with(view.getContext()) 24 | .load(url) 25 | .apply(RequestOptions.circleCropTransform() 26 | .error(new ColorDrawable(Color.WHITE)) 27 | .fallback(new ColorDrawable(Color.RED))) 28 | .into((ImageView) view); 29 | } 30 | 31 | @BindingAdapter("circleImage") 32 | public static void circleImage(View view, @DrawableRes int ids) { 33 | Glide.with(view.getContext()) 34 | .load(ContextCompat.getDrawable(view.getContext(), ids)) 35 | .apply(RequestOptions.circleCropTransform() 36 | .error(new ColorDrawable(Color.WHITE)) 37 | .fallback(new ColorDrawable(Color.RED))) 38 | .into((ImageView) view); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/error_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_svg_ban.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_svg_ok.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shap_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 19 | 20 | 34 | 35 | 49 | 50 | 65 | 66 | 80 | 81 | 95 | 96 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_grid.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 23 | 24 | 33 | 34 | 50 | 51 | 63 | 64 | 76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_head.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_linear.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 23 | 24 | 33 | 34 | 49 | 50 | 61 | 62 | 74 | 75 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_staggered.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 23 | 24 | 34 | 35 | 51 | 52 | 64 | 65 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_staggered2.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 23 | 24 | 34 | 35 | 51 | 52 | 64 | 65 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_staggered_head.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 20 | 21 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_my_loading_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 25 | 33 | 34 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_my_page_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 24 | 25 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_my_page_err.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 25 | 26 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_my_page_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 25 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | QuickBindAdapter 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/xiaobin/bindingadapter/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaobin.bindingadapter; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.9.0' 5 | repositories { 6 | maven { url = "https://jitpack.io" } 7 | maven { url = "https://maven.google.com" } 8 | google() 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.1.1' 13 | // classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | maven { url = "https://jitpack.io" } 23 | maven { url = "https://maven.google.com" } 24 | google() 25 | mavenCentral() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | android.defaults.buildfeatures.buildconfig=true 21 | android.nonTransitiveRClass=false 22 | android.nonFinalResIds=false 23 | 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matcha-xiaobin/QuickBindAdapter/5dc0b680f921e32535a13f9098817267866276eb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 19 15:38:08 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /quickbindadapter/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /quickbindadapter/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | group = 'com.github.Matchas-xiaobin' 7 | 8 | android { 9 | namespace 'com.xiaobin.quickbindadapter' 10 | compileSdk 34 11 | 12 | defaultConfig { 13 | minSdk 14 14 | targetSdk 34 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_17 19 | targetCompatibility JavaVersion.VERSION_17 20 | } 21 | buildFeatures { 22 | dataBinding true 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | } 32 | 33 | dependencies { 34 | implementation("androidx.core:core-ktx:1.10.1") 35 | implementation("androidx.appcompat:appcompat:1.6.1") 36 | implementation("androidx.recyclerview:recyclerview:1.3.1") 37 | implementation("androidx.paging:paging-runtime-ktx:3.2.0") 38 | } 39 | -------------------------------------------------------------------------------- /quickbindadapter/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/BindHolder.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter 2 | 3 | import android.view.View 4 | import androidx.databinding.ViewDataBinding 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 7 | 8 | /** 9 | * @author 小斌 10 | * @data 2019/6/24 11 | */ 12 | open class BindHolder : ViewHolder { 13 | 14 | var binding: ViewDataBinding? = null 15 | private set 16 | 17 | var fullSpan: Boolean = false 18 | private set 19 | 20 | constructor(view: View) : super(view) 21 | 22 | constructor( 23 | binding: ViewDataBinding, 24 | lifecycleOwner: LifecycleOwner? = null, 25 | fullSpan: Boolean = false 26 | ) : super(binding.root) { 27 | this.binding = binding 28 | this.fullSpan = fullSpan 29 | if (lifecycleOwner is LifecycleOwner) { 30 | binding.lifecycleOwner = lifecycleOwner 31 | } else { 32 | binding.lifecycleOwner = null 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/ItemData.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter 2 | 3 | import java.util.ArrayList 4 | 5 | /** 6 | * @author 小斌 7 | * @data 2019/7/10 8 | */ 9 | class ItemData : ArrayList { 10 | constructor() {} 11 | constructor(initialCapacity: Int) : super(initialCapacity) {} 12 | constructor(c: Collection<*>) : super(c) {} 13 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/QuickBindPagingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import androidx.annotation.LayoutRes 10 | import androidx.core.view.ViewCompat 11 | import androidx.databinding.DataBindingUtil 12 | import androidx.databinding.ViewDataBinding 13 | import androidx.lifecycle.LifecycleOwner 14 | import androidx.paging.PagingDataAdapter 15 | import androidx.recyclerview.widget.ConcatAdapter 16 | import androidx.recyclerview.widget.DiffUtil 17 | import androidx.recyclerview.widget.GridLayoutManager 18 | import androidx.recyclerview.widget.LinearLayoutManager 19 | import androidx.recyclerview.widget.RecyclerView 20 | import androidx.recyclerview.widget.StaggeredGridLayoutManager 21 | import com.xiaobin.quickbindadapter.interfaces.StaggeredFullSpan 22 | import com.xiaobin.quickbindadapter.paging.EmptyPageAdapter 23 | import com.xiaobin.quickbindadapter.view.BaseLoadView 24 | import com.xiaobin.quickbindadapter.view.BasePageStateView 25 | import kotlinx.coroutines.CoroutineDispatcher 26 | import kotlinx.coroutines.Dispatchers 27 | 28 | /** 29 | * Paging 分页专用适配器,增删改需要修改数据源头的方式实现 30 | * 这里只提供与QuickBindAdapter一样的多种布局实现 31 | * 由于技术有限,这里限制了数据类型 32 | * 加载更多条请使用 扩展方法: withLoadStateHeaderAndFooter 实现 33 | * @see withLoadStateHeaderAndFooter 34 | * @see withLoadStateHeader 35 | * @see withLoadStateFooter 36 | * 37 | * 如果泛型 T 定义了 具体的类型将失去多布局的主要意义,建议使用 Any 38 | * 当然,固定一种数据类型也可以使用这个适配器,可以方便随时切换不同的布局 39 | * 比如从一行1个item,变成1行2个、3个甚至更多个,不同数量使用不同的布局适配 40 | */ 41 | class QuickBindPagingAdapter( 42 | itemCallback: DiffUtil.ItemCallback, 43 | private val lifecycleOwner: LifecycleOwner? = null, 44 | mainDispatcher: CoroutineDispatcher = Dispatchers.Main, 45 | workerDispatcher: CoroutineDispatcher = Dispatchers.Default, 46 | ) : PagingDataAdapter(itemCallback, mainDispatcher, workerDispatcher) { 47 | 48 | // 布局ID - 数据类型 互相绑定 49 | private val itemViewTypes = HashMap, Int>() 50 | private val itemBindVal = HashMap, Int>() 51 | 52 | private val UNKNOWN_VIEW_TYPE = -101 53 | 54 | private val UNKNOWN_VIEW_TYPE_TEXT = "Undefined data type!" 55 | 56 | private var mRecyclerView: RecyclerView? = null 57 | 58 | private var curEmptyAdapter: EmptyPageAdapter? = null 59 | private var concatAdapter: ConcatAdapter? = null 60 | 61 | /** 62 | * 请给RecyclerView设置这个adapter, 参考: 63 | * recyclerView.adapter = pagingAdapter.adapter 64 | */ 65 | val adapter: RecyclerView.Adapter<*> 66 | get() { 67 | return if (concatAdapter == null) this else concatAdapter!! 68 | } 69 | 70 | /** 71 | * 启用状态View, 包括 底部加载更多状态,以及占满RV的空数据缺省页 72 | * 请在调用这个方法后,重新给RecyclerView设置适配器 73 | * 请设置 this.adapter 74 | * @see adapter 给 recyclerView设置的适配器 75 | * @param context 上下文对象 76 | * @param emptyStatePage 自定义 空数据占位 View 77 | * @param loadStateItem 自定义底部 加载更多状态 View 78 | * @param enableEmptyPage 是否显示 空数据占位 布局 79 | * @param enableLoadMoreItem 是否显示底部 加载更多状态 View 80 | * @param retry 点击 重新加载 数据回调 81 | */ 82 | fun enableStatusView( 83 | context: Context, 84 | emptyStatePage: BasePageStateView<*, *, *>? = null, 85 | loadStateItem: BaseLoadView<*>? = null, 86 | enableEmptyPage: Boolean = true, 87 | enableLoadMoreItem: Boolean = false, 88 | retry: () -> Unit = {} 89 | ) { 90 | val getItemCount: () -> Int = { 91 | itemCount 92 | } 93 | curEmptyAdapter?.let { 94 | concatAdapter?.removeAdapter(it) 95 | } 96 | curEmptyAdapter = EmptyPageAdapter( 97 | getItemCount, 98 | emptyStatePage, 99 | loadStateItem, 100 | lifecycleOwner, 101 | enableEmptyPage, 102 | enableLoadMoreItem, 103 | retry 104 | ) 105 | curEmptyAdapter?.let { 106 | concatAdapter = withLoadStateFooter(it) 107 | } 108 | } 109 | 110 | /** 111 | * 绑定 数据类型 和 视图ID 112 | */ 113 | fun bind(clazz: Class<*>, @LayoutRes layoutId: Int, brId: Int = 0) { 114 | itemViewTypes[clazz] = layoutId 115 | itemBindVal[clazz] = brId 116 | refreshScreenItems(clazz) 117 | } 118 | 119 | /** 120 | * 刷新当前可见Item 121 | */ 122 | private fun refreshScreenItems(clazz: Class<*>?) { 123 | mRecyclerView?.let { recyclerView -> 124 | recyclerView.layoutManager?.let { layoutManager -> 125 | var start = 0 126 | var end = Math.max(0, itemCount - 1) 127 | when (layoutManager) { 128 | is LinearLayoutManager -> { 129 | start = layoutManager.findFirstVisibleItemPosition() 130 | end = layoutManager.findLastVisibleItemPosition() 131 | } 132 | 133 | is StaggeredGridLayoutManager -> { 134 | val firstIndexArray = layoutManager.findFirstVisibleItemPositions(null) 135 | val lastIndexArray = layoutManager.findLastVisibleItemPositions(null) 136 | if (firstIndexArray.isNotEmpty() && lastIndexArray.isNotEmpty()) { 137 | firstIndexArray.sort() 138 | lastIndexArray.sort() 139 | start = firstIndexArray.first() 140 | end = lastIndexArray.last() 141 | } 142 | } 143 | } 144 | for (position in start..end) { 145 | if (position < itemCount) { 146 | if (clazz == null || getItem(position)?.javaClass == clazz) { 147 | notifyItemChanged(position) 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | override fun getItemViewType(position: Int): Int { 156 | if (position >= itemCount) return super.getItemViewType(position) 157 | val data = getItem(position) ?: return UNKNOWN_VIEW_TYPE 158 | return itemViewTypes[data::class.java] ?: UNKNOWN_VIEW_TYPE 159 | } 160 | 161 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindHolder { 162 | var mClass: Class<*>? = null 163 | if (viewType != UNKNOWN_VIEW_TYPE) { 164 | for (itemViewType in itemViewTypes) { 165 | if (itemViewType.value == viewType) { 166 | mClass = itemViewType.key 167 | break 168 | } 169 | } 170 | } 171 | return if (mClass != null) { 172 | val isFullSpan = StaggeredFullSpan::class.java.isAssignableFrom(mClass) 173 | val binding = DataBindingUtil.inflate( 174 | LayoutInflater.from(parent.context), 175 | viewType, 176 | parent, 177 | false 178 | ) 179 | BindHolder( 180 | binding = binding, 181 | lifecycleOwner = null, 182 | fullSpan = isFullSpan 183 | ) 184 | } else { 185 | BindHolder(TextView(parent.context).apply { 186 | text = UNKNOWN_VIEW_TYPE_TEXT 187 | gravity = Gravity.CENTER_VERTICAL 188 | ViewCompat.setPaddingRelative(this, 12, 12, 12, 12) 189 | layoutParams = ViewGroup.LayoutParams( 190 | ViewGroup.LayoutParams.MATCH_PARENT, 191 | ViewGroup.LayoutParams.WRAP_CONTENT 192 | ) 193 | }) 194 | } 195 | } 196 | 197 | override fun onBindViewHolder( 198 | holder: BindHolder, 199 | position: Int, 200 | payloads: MutableList 201 | ) { 202 | if (payloads.isEmpty()) { 203 | onBindViewHolder(holder, position) 204 | return 205 | } 206 | val itemData = getItem(position) ?: return 207 | if (quickBindPayloads != null) { 208 | quickBindPayloads?.invoke(holder.binding, itemData, position, payloads) 209 | } else { 210 | super.onBindViewHolder(holder, position, payloads) 211 | } 212 | } 213 | 214 | override fun onBindViewHolder(holder: BindHolder, position: Int) { 215 | val itemViewType = getItemViewType(position) 216 | if (itemViewType == UNKNOWN_VIEW_TYPE) { 217 | return 218 | } 219 | val data = getItem(position) ?: return 220 | itemBindVal[data::class.java]?.let { brId -> 221 | if (brId != 0) { 222 | holder.binding?.setVariable(brId, data) 223 | } 224 | } 225 | quickBind?.invoke(holder.binding ?: return, data, position) 226 | if (onItemClickListener != null) { 227 | holder.binding?.root?.setOnClickListener { 228 | onItemClickListener?.invoke(this, data, position) 229 | } 230 | } else { 231 | holder.binding?.root?.setOnClickListener(null) 232 | } 233 | if (onItemLongClickListener != null) { 234 | holder.binding?.root?.setOnLongClickListener { 235 | return@setOnLongClickListener onItemLongClickListener?.invoke( 236 | this, 237 | data, 238 | position 239 | ) ?: false 240 | } 241 | } else { 242 | holder.binding?.root?.setOnLongClickListener(null) 243 | } 244 | if (onItemChildClickListener != null && childClicks.isNotEmpty()) { 245 | childClicks.forEach { viewId -> 246 | holder.binding?.root?.findViewById(viewId)?.setOnClickListener { childView -> 247 | onItemChildClickListener?.invoke( 248 | this, 249 | childView, 250 | data, 251 | position 252 | ) 253 | } 254 | } 255 | } else { 256 | onItemChildClickListener = null 257 | } 258 | if (onItemChildLongClickListener != null && childLongClicks.isNotEmpty()) { 259 | childLongClicks.forEach { viewId -> 260 | holder.binding?.root?.findViewById(viewId) 261 | ?.setOnLongClickListener { childView -> 262 | return@setOnLongClickListener onItemChildLongClickListener?.invoke( 263 | this, 264 | childView, 265 | data, 266 | position 267 | ) ?: false 268 | } 269 | } 270 | } else { 271 | onItemChildLongClickListener = null 272 | } 273 | } 274 | 275 | override fun onViewAttachedToWindow(holder: BindHolder) { 276 | super.onViewAttachedToWindow(holder) 277 | //时刻检查spanSizeLookup是否需要覆盖自定义的 278 | refreshSpanSizeLookup() 279 | val lp = holder.itemView.layoutParams 280 | if (lp is StaggeredGridLayoutManager.LayoutParams) { 281 | if (holder.fullSpan) { 282 | lp.isFullSpan = true 283 | } 284 | } 285 | } 286 | 287 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { 288 | super.onAttachedToRecyclerView(recyclerView) 289 | this.mRecyclerView = recyclerView 290 | 291 | //更新lookup 292 | refreshSpanSizeLookup() 293 | } 294 | 295 | private var customLookUp: GridLayoutManager.SpanSizeLookup? = null 296 | 297 | private val spanSizeLookUp = object : GridLayoutManager.SpanSizeLookup() { 298 | override fun getSpanSize(position: Int): Int { 299 | var itemViewType = adapter.getItemViewType(position) 300 | if (adapter is ConcatAdapter) { 301 | //获取真实的itemViewType 302 | itemViewType = (adapter as ConcatAdapter).getWrappedAdapterAndPosition(position) 303 | .first.getItemViewType(position) 304 | } 305 | return if (itemViewType == UNKNOWN_VIEW_TYPE 306 | || itemViewType == EmptyPageAdapter.LOAD_VIEW_TYPE 307 | || itemViewType == EmptyPageAdapter.EMPTY_VIEW_TYPE 308 | ) { 309 | (mRecyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: 1 310 | } else { 311 | customLookUp?.getSpanSize(position) ?: 1 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * 刷新SpanSizeLookup 318 | */ 319 | private fun refreshSpanSizeLookup() { 320 | mRecyclerView?.let { rv -> 321 | val layoutManager = rv.layoutManager 322 | if (layoutManager is GridLayoutManager) { 323 | val rvSpanSizeLookup = layoutManager.spanSizeLookup 324 | if (rvSpanSizeLookup != spanSizeLookUp) { 325 | //需要重新覆盖spanSizeLookup 326 | customLookUp = rvSpanSizeLookup 327 | layoutManager.spanSizeLookup = spanSizeLookUp 328 | } 329 | } else if (layoutManager == null) { 330 | throw NullPointerException("请在完成初始化前先给RecyclerView设置LayoutManager!") 331 | } 332 | } 333 | } 334 | 335 | var quickBind: ((mBinding: ViewDataBinding?, data: T, position: Int) -> Unit)? = 336 | null 337 | 338 | var quickBindPayloads: (( 339 | mBinding: ViewDataBinding?, 340 | data: T, 341 | position: Int, 342 | payloads: MutableList 343 | ) -> Unit)? = null 344 | 345 | var onItemClickListener: ((adapter: QuickBindPagingAdapter, data: T, position: Int) -> Unit)? = 346 | null 347 | 348 | var onItemLongClickListener: ((adapter: QuickBindPagingAdapter, data: T, position: Int) -> Boolean)? = 349 | null 350 | 351 | private var onItemChildClickListener: (( 352 | adapter: QuickBindPagingAdapter, 353 | view: View, 354 | data: T, 355 | position: Int 356 | ) -> Unit)? = null 357 | 358 | private val childClicks = mutableSetOf() 359 | 360 | private var onItemChildLongClickListener: (( 361 | adapter: QuickBindPagingAdapter, 362 | view: View, 363 | data: T, 364 | position: Int 365 | ) -> Boolean)? = null 366 | 367 | private val childLongClicks = mutableSetOf() 368 | 369 | fun onItemChildClickListener( 370 | vararg viewId: Int, 371 | onItemChildClickListener: ((adapter: QuickBindPagingAdapter, view: View, data: T, position: Int) -> Unit)? 372 | ) { 373 | if (onItemChildClickListener != null) { 374 | childClicks.addAll(viewId.asList()) 375 | } else { 376 | childClicks.clear() 377 | } 378 | this.onItemChildClickListener = onItemChildClickListener 379 | } 380 | 381 | fun onItemChildLongClickListener( 382 | vararg viewId: Int, 383 | onItemChildLongClickListener: ((adapter: QuickBindPagingAdapter, view: View, data: T, position: Int) -> Boolean)? 384 | ) { 385 | if (onItemChildLongClickListener != null) { 386 | childLongClicks.addAll(viewId.asList()) 387 | } else { 388 | childLongClicks.clear() 389 | } 390 | this.onItemChildLongClickListener = onItemChildLongClickListener 391 | } 392 | 393 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/holder/EmptyViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.holder 2 | 3 | import android.view.View 4 | import androidx.databinding.ViewDataBinding 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.xiaobin.quickbindadapter.BindHolder 7 | import com.xiaobin.quickbindadapter.view.BasePageStateView 8 | import com.xiaobin.quickbindadapter.view.PageState 9 | 10 | class EmptyViewHolder : BindHolder { 11 | 12 | lateinit var emptyStatePageView: BasePageStateView<*, *, *> 13 | 14 | private constructor(view: View) : super(view) 15 | 16 | constructor( 17 | emptyStatePage: BasePageStateView<*, *, *>, 18 | binding: ViewDataBinding, 19 | lifecycleOwner: LifecycleOwner? = null 20 | ) : super(binding, lifecycleOwner) { 21 | emptyStatePageView = emptyStatePage 22 | } 23 | 24 | fun showLoadingPage() { 25 | emptyStatePageView.setPageState(PageState.Loading) 26 | } 27 | 28 | fun showErrorPage() { 29 | emptyStatePageView.setPageState(PageState.Error) 30 | } 31 | 32 | fun showEmptyPage() { 33 | emptyStatePageView.setPageState(PageState.Empty) 34 | } 35 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/holder/LoadViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.holder 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import androidx.lifecycle.LifecycleOwner 5 | import com.xiaobin.quickbindadapter.BindHolder 6 | import com.xiaobin.quickbindadapter.view.BaseLoadView 7 | 8 | class LoadViewHolder( 9 | val loadStateView: BaseLoadView<*>, 10 | binding: ViewDataBinding, 11 | lifecycleOwner: LifecycleOwner? = null, 12 | ) : BindHolder(binding, lifecycleOwner) { 13 | 14 | fun onLoading() { 15 | loadStateView.isLoading() 16 | } 17 | 18 | fun onWait() { 19 | loadStateView.isWaitLoading() 20 | } 21 | 22 | fun onSuccess() { 23 | loadStateView.isLoadMoreSuccess() 24 | } 25 | 26 | fun onError() { 27 | loadStateView.isLoadMoreFailed() 28 | } 29 | 30 | fun noMore() { 31 | loadStateView.isNoMoreData() 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/interfaces/StaggeredFullSpan.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.interfaces 2 | 3 | interface StaggeredFullSpan {} -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/paging/EmptyPageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.paging 2 | 3 | import android.view.ViewGroup 4 | import android.widget.Space 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.paging.LoadState 7 | import androidx.paging.LoadStateAdapter 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.xiaobin.quickbindadapter.BindHolder 10 | import com.xiaobin.quickbindadapter.holder.EmptyViewHolder 11 | import com.xiaobin.quickbindadapter.holder.LoadViewHolder 12 | import com.xiaobin.quickbindadapter.view.BaseLoadView 13 | import com.xiaobin.quickbindadapter.view.BasePageStateView 14 | import com.xiaobin.quickbindadapter.view.DefaultEmptyStatePage 15 | import com.xiaobin.quickbindadapter.view.DefaultLoadView 16 | 17 | /** 18 | * 空数据占位布局 19 | */ 20 | internal class EmptyPageAdapter( 21 | private val getCount: () -> Int, 22 | private val pageView: BasePageStateView<*, *, *>? = null, 23 | private val loadView: BaseLoadView<*>? = null, 24 | private val lifecycleOwner: LifecycleOwner? = null, 25 | private val enableEmptyPage: Boolean = true, 26 | private val enableLoadMoreItem: Boolean = false, 27 | private val retry: () -> Unit = {}, 28 | ) : LoadStateAdapter() { 29 | 30 | companion object { 31 | const val LOAD_VIEW_TYPE = -1 32 | const val EMPTY_VIEW_TYPE = -2 33 | } 34 | 35 | private var mRecyclerView: RecyclerView? = null 36 | 37 | private fun showLoadItem(): Boolean { 38 | return getCount.invoke() > 0 39 | } 40 | 41 | override fun getStateViewType(loadState: LoadState): Int { 42 | return if (showLoadItem()) LOAD_VIEW_TYPE else EMPTY_VIEW_TYPE 43 | } 44 | 45 | override fun displayLoadStateAsItem(loadState: LoadState): Boolean { 46 | return enableLoadMoreItem || (enableEmptyPage && !showLoadItem()) 47 | } 48 | 49 | override fun onBindViewHolder(holder: BindHolder, loadState: LoadState) { 50 | //两种布局,分别绑定 51 | if (holder is LoadViewHolder) { 52 | when (loadState) { 53 | is LoadState.Loading -> holder.onLoading() 54 | is LoadState.Error -> holder.onError() 55 | is LoadState.NotLoading -> { 56 | if (loadState.endOfPaginationReached) { 57 | holder.noMore() 58 | } else { 59 | holder.onWait() 60 | } 61 | } 62 | } 63 | } else if (holder is EmptyViewHolder) { 64 | when (loadState) { 65 | is LoadState.Error -> holder.showErrorPage() 66 | LoadState.Loading -> holder.showLoadingPage() 67 | is LoadState.NotLoading -> holder.showEmptyPage() 68 | } 69 | } 70 | } 71 | 72 | override fun onCreateViewHolder( 73 | parent: ViewGroup, 74 | loadState: LoadState 75 | ): BindHolder { 76 | return if (showLoadItem() && enableLoadMoreItem) { 77 | loadView?.createViewHolder(parent, lifecycleOwner) 78 | ?: DefaultLoadView.defaultLoadView(parent.context) 79 | .createViewHolder(parent, lifecycleOwner) { 80 | retry() 81 | } 82 | } else if (!showLoadItem() && enableEmptyPage) { 83 | pageView?.createViewHolder(parent, lifecycleOwner) 84 | ?: DefaultEmptyStatePage(parent.context).createViewHolder(parent, lifecycleOwner) 85 | } else { 86 | BindHolder(Space(parent.context)) 87 | } 88 | } 89 | 90 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { 91 | super.onAttachedToRecyclerView(recyclerView) 92 | mRecyclerView = recyclerView 93 | } 94 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/view/BaseLoadView.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.view 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.databinding.ViewDataBinding 7 | import androidx.lifecycle.LifecycleOwner 8 | import com.xiaobin.quickbindadapter.BindHolder 9 | import com.xiaobin.quickbindadapter.holder.LoadViewHolder 10 | 11 | abstract class BaseLoadView(private val layoutId: Int) { 12 | 13 | enum class LoadMoreState { 14 | LOADING, SUCCESS, FAILED, NO_MORE, WAIT_LOADING, NO_STATE 15 | } 16 | 17 | private var viewHolder: LoadViewHolder? = null 18 | 19 | //加载更多 20 | var loadMoreState = LoadMoreState.NO_STATE 21 | private set 22 | 23 | private var loadView: T? = null 24 | 25 | fun createViewHolder( 26 | parent: ViewGroup, 27 | lifecycleOwner: LifecycleOwner? = null, 28 | onClickLoadMore: () -> Unit = {} 29 | ): LoadViewHolder { 30 | loadView = DataBindingUtil.inflate( 31 | LayoutInflater.from(parent.context), 32 | layoutId, 33 | parent, false 34 | ) 35 | initView(loadView) 36 | loadView!!.root.setOnClickListener { 37 | if (loadMoreState == LoadMoreState.NO_MORE || 38 | loadMoreState == LoadMoreState.LOADING || 39 | loadMoreState == LoadMoreState.SUCCESS || 40 | loadView == null 41 | ) { 42 | //加载中,加载结束并且没有更多数据时,不允许触发点击事件 43 | return@setOnClickListener 44 | } 45 | onClickLoadMore() 46 | } 47 | //恢复状态 48 | when (loadMoreState) { 49 | LoadMoreState.LOADING -> isLoading(true) 50 | LoadMoreState.SUCCESS -> isLoadMoreSuccess(true) 51 | LoadMoreState.FAILED -> isLoadMoreFailed(true) 52 | LoadMoreState.NO_MORE -> isNoMoreData(true) 53 | LoadMoreState.WAIT_LOADING -> isWaitLoading(true) 54 | LoadMoreState.NO_STATE -> isWaitLoading(true) 55 | } 56 | viewHolder = LoadViewHolder(this, loadView!!, lifecycleOwner) 57 | return viewHolder!! 58 | } 59 | 60 | /** 61 | * 变更为 等待用户点击加载更多 状态 62 | * 禁止了自动加载更多的时候,才会调用这个方法 63 | */ 64 | fun isWaitLoading(ignore: Boolean = false) { 65 | if (!ignore && loadMoreState == LoadMoreState.WAIT_LOADING) return 66 | loadMoreState = LoadMoreState.WAIT_LOADING 67 | loadView?.let { 68 | onStateChange(it, LoadMoreState.WAIT_LOADING) 69 | } 70 | } 71 | 72 | /** 73 | * 变更为 正在加载更多 状态 74 | */ 75 | fun isLoading(ignore: Boolean = false) { 76 | if (!ignore && loadMoreState == LoadMoreState.LOADING) return 77 | loadMoreState = LoadMoreState.LOADING 78 | loadView?.let { 79 | onStateChange(it, LoadMoreState.LOADING) 80 | } 81 | } 82 | 83 | /** 84 | * 变更为 加载更多完成且无更多数据 状态 85 | */ 86 | fun isNoMoreData(ignore: Boolean = false) { 87 | if (!ignore && loadMoreState == LoadMoreState.NO_MORE) return 88 | loadMoreState = LoadMoreState.NO_MORE 89 | loadView?.let { 90 | onStateChange(it, LoadMoreState.NO_MORE) 91 | } 92 | } 93 | 94 | /** 95 | * 变更为 加载更多成功 状态 96 | */ 97 | fun isLoadMoreSuccess(ignore: Boolean = false) { 98 | if (!ignore && loadMoreState == LoadMoreState.SUCCESS) return 99 | loadMoreState = LoadMoreState.SUCCESS 100 | loadView?.let { 101 | onStateChange(it, LoadMoreState.SUCCESS) 102 | } 103 | } 104 | 105 | /** 106 | * 变更为 加载更多失败了 状态 107 | */ 108 | fun isLoadMoreFailed(ignore: Boolean = false) { 109 | if (!ignore && loadMoreState == LoadMoreState.FAILED) return 110 | loadMoreState = LoadMoreState.FAILED 111 | loadView?.let { 112 | onStateChange(it, LoadMoreState.FAILED) 113 | } 114 | } 115 | 116 | protected abstract fun initView(loadView: T?) 117 | 118 | /** 119 | * 状态变化 120 | */ 121 | protected abstract fun onStateChange(loadView: T, state: LoadMoreState) 122 | 123 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/view/BasePageStateView.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.view 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.databinding.ViewDataBinding 8 | import androidx.lifecycle.LifecycleOwner 9 | import com.xiaobin.quickbindadapter.BindHolder 10 | import com.xiaobin.quickbindadapter.R 11 | import com.xiaobin.quickbindadapter.databinding.McXbLayoutPlacePageBinding 12 | import com.xiaobin.quickbindadapter.holder.EmptyViewHolder 13 | 14 | abstract class BasePageStateView< 15 | EmptyPage : ViewDataBinding, 16 | ErrorPage : ViewDataBinding, 17 | LoadingPage : ViewDataBinding 18 | >( 19 | private val emptyPageLayoutId: Int = 0, 20 | private val errorPageLayoutId: Int = 0, 21 | private val loadingPageLayoutId: Int = 0, 22 | ) { 23 | 24 | var emptyPageDecorView: McXbLayoutPlacePageBinding? = null 25 | private set 26 | private var lifecycleOwner: LifecycleOwner? = null 27 | 28 | private var currentPageState: PageState = PageState.Loading 29 | 30 | var emptyView: EmptyPage? = null 31 | var loadingView: LoadingPage? = null 32 | var errorView: ErrorPage? = null 33 | 34 | /** 35 | * 三种状态的页面不是一开始就创建了的,所以这个方法可以监听到哪个页面被首次创建 36 | */ 37 | protected abstract fun onPageCreate(action: PageState) 38 | 39 | /** 40 | * 当切换状态的时候,这里可以监听到当前是哪个页面 41 | */ 42 | protected abstract fun onActionCall(action: PageState) 43 | 44 | /** 45 | * 设置默认的页面,并切换到这个页面 46 | */ 47 | fun setDefaultPage(pageState: PageState) { 48 | currentPageState = pageState 49 | } 50 | 51 | /** 52 | * 获取当前的页面状态 53 | */ 54 | fun getCurrentPageState(): PageState { 55 | return currentPageState 56 | } 57 | 58 | fun createViewHolder(parent: ViewGroup, mLifecycleOwner: LifecycleOwner? = null): EmptyViewHolder { 59 | emptyView = null 60 | loadingView = null 61 | errorView = null 62 | emptyPageDecorView = DataBindingUtil.inflate( 63 | LayoutInflater.from(parent.context), 64 | R.layout.mc_xb_layout_place_page, 65 | parent, 66 | false 67 | ) 68 | mLifecycleOwner?.apply { 69 | lifecycleOwner = this 70 | emptyPageDecorView!!.lifecycleOwner = this 71 | } 72 | setPageState(currentPageState) 73 | return EmptyViewHolder(this, emptyPageDecorView!!, mLifecycleOwner) 74 | } 75 | 76 | private fun createPageViewBinding(layoutId: Int): T? { 77 | if (layoutId == 0) return null 78 | val parent = emptyPageDecorView!!.flPlaceholder 79 | val pageBindingView: T = DataBindingUtil.inflate( 80 | LayoutInflater.from(parent.context), 81 | layoutId, parent, false 82 | ) 83 | lifecycleOwner?.let { 84 | pageBindingView.lifecycleOwner = it 85 | } 86 | return pageBindingView 87 | } 88 | 89 | private fun getEmptyPage(): EmptyPage? { 90 | if (!check()) return null 91 | return if (emptyPageLayoutId == 0) { 92 | null 93 | } else if (emptyView != null) { 94 | lifecycleOwner?.let { 95 | emptyView!!.lifecycleOwner = it 96 | } 97 | emptyView 98 | } else { 99 | emptyView = createPageViewBinding(emptyPageLayoutId) 100 | onPageCreate(PageState.Empty) 101 | emptyView 102 | } 103 | } 104 | 105 | private fun getLoadingPage(): LoadingPage? { 106 | if (!check()) return null 107 | return if (loadingPageLayoutId == 0) { 108 | null 109 | } else if (loadingView != null) { 110 | lifecycleOwner?.let { 111 | loadingView!!.lifecycleOwner = it 112 | } 113 | loadingView 114 | } else { 115 | loadingView = createPageViewBinding(loadingPageLayoutId) 116 | onPageCreate(PageState.Loading) 117 | loadingView 118 | } 119 | } 120 | 121 | private fun getErrorPage(): ErrorPage? { 122 | if (!check()) return null 123 | return if (errorPageLayoutId == 0) { 124 | null 125 | } else if (errorView != null) { 126 | lifecycleOwner?.let { 127 | errorView!!.lifecycleOwner = it 128 | } 129 | errorView 130 | } else { 131 | errorView = createPageViewBinding(errorPageLayoutId) 132 | onPageCreate(PageState.Error) 133 | errorView 134 | } 135 | } 136 | 137 | fun refreshPage() { 138 | setPageState(currentPageState) 139 | } 140 | 141 | fun setPageState(action: PageState) { 142 | if (!check() && currentPageState != action) { 143 | setDefaultPage(action) 144 | return 145 | } 146 | val view: View? = when (action) { 147 | PageState.Empty -> { 148 | getEmptyPage()?.root 149 | } 150 | PageState.Error -> { 151 | getErrorPage()?.root 152 | } 153 | PageState.Loading -> { 154 | getLoadingPage()?.root 155 | } 156 | PageState.Finish -> { 157 | null 158 | } 159 | } 160 | emptyPageDecorView?.flPlaceholder?.removeAllViews() 161 | view?.apply { 162 | emptyPageDecorView?.flPlaceholder?.addView(this, -1, -1) 163 | } 164 | currentPageState = action 165 | onActionCall(action) 166 | } 167 | 168 | private fun check(): Boolean { 169 | if (emptyPageDecorView == null) { 170 | return false 171 | } 172 | return true 173 | } 174 | } 175 | 176 | enum class PageState { 177 | Loading, Empty, Error, Finish 178 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/view/DefaultEmptyStatePage.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import androidx.annotation.ColorInt 6 | import com.xiaobin.quickbindadapter.R 7 | import com.xiaobin.quickbindadapter.databinding.* 8 | 9 | class DefaultPlacePageConfigsBean : Cloneable { 10 | var emptyText: String = "" 11 | var errorText: String = "" 12 | 13 | @ColorInt 14 | var emptyTextColor: Int = Color.LTGRAY 15 | 16 | @ColorInt 17 | var errorTextColor: Int = Color.RED 18 | 19 | public override fun clone(): DefaultPlacePageConfigsBean { 20 | return try { 21 | return super.clone() as? DefaultPlacePageConfigsBean 22 | ?: DefaultPlacePageConfigsBean() 23 | } catch (e: CloneNotSupportedException) { 24 | DefaultPlacePageConfigsBean() 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * 默认的 状态 页面 31 | */ 32 | class DefaultEmptyStatePage(private val context: Context) : 33 | BasePageStateView( 34 | R.layout.mc_xb_layout_page_empty, 35 | R.layout.mc_xb_layout_page_error, 36 | R.layout.mc_xb_layout_page_loading, 37 | ) { 38 | 39 | companion object { 40 | //全局配置 41 | var globalConfig: DefaultPlacePageConfigsBean? = null 42 | private set 43 | 44 | /** 45 | * 调用set方法,全局替换默认状态页为自定义的状态页 46 | * 如果统一设置为自定义的状态页,那么就不需要配置全局配置项了,因为那个是给默认状态页用的 47 | */ 48 | var defaultStatePage: (Context) -> BasePageStateView<*, *, *> = { 49 | DefaultEmptyStatePage(it) 50 | } 51 | 52 | //创建全局配置 53 | fun createGlobalConfig(result: () -> DefaultPlacePageConfigsBean) { 54 | globalConfig = result.invoke() 55 | } 56 | 57 | //创建全局配置 58 | fun createGlobalConfig(result: DefaultPlacePageConfigsBean) { 59 | globalConfig = result 60 | } 61 | } 62 | 63 | init { 64 | if (globalConfig == null) { 65 | createGlobalConfig(DefaultPlacePageConfigsBean().apply { 66 | emptyText = context.getString(R.string.place_onEmpty) 67 | errorText = context.getString(R.string.place_onError) 68 | emptyTextColor = Color.LTGRAY 69 | errorTextColor = Color.RED 70 | }) 71 | } 72 | } 73 | 74 | private var config: DefaultPlacePageConfigsBean = globalConfig!!.clone() 75 | set(value) { 76 | field = if (value === globalConfig) { 77 | //如果是设置的全局配置,则克隆一份 78 | value.clone() 79 | } else { 80 | value 81 | } 82 | } 83 | 84 | override fun onPageCreate(action: PageState) { 85 | when (action) { 86 | PageState.Empty -> { 87 | emptyView?.apply { 88 | tvEmpty.setTextColor(config.emptyTextColor) 89 | tvEmpty.text = config.emptyText 90 | } 91 | 92 | } 93 | PageState.Error -> { 94 | errorView?.apply { 95 | tvError.setTextColor(config.errorTextColor) 96 | tvError.text = config.errorText 97 | } 98 | } 99 | PageState.Loading -> { 100 | } 101 | PageState.Finish -> { 102 | } 103 | } 104 | } 105 | 106 | override fun onActionCall(action: PageState) { 107 | } 108 | 109 | fun setErrorPageTextColor(@ColorInt textColor: Int) { 110 | config.errorTextColor = textColor 111 | errorView?.apply { 112 | tvError.setTextColor(config.errorTextColor) 113 | } 114 | } 115 | 116 | fun setErrorPageText(text: String) { 117 | config.errorText = text 118 | errorView?.apply { 119 | tvError.text = config.errorText 120 | } 121 | } 122 | 123 | fun setEmptyPageTextColor(@ColorInt textColor: Int) { 124 | config.emptyTextColor = textColor 125 | emptyView?.apply { 126 | tvEmpty.setTextColor(config.emptyTextColor) 127 | } 128 | } 129 | 130 | fun setEmptyPageText(text: String) { 131 | config.emptyText = text 132 | emptyView?.apply { 133 | tvEmpty.text = config.emptyText 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/java/com/xiaobin/quickbindadapter/view/DefaultLoadView.kt: -------------------------------------------------------------------------------- 1 | package com.xiaobin.quickbindadapter.view 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import android.graphics.Color 6 | import android.os.Build 7 | import androidx.annotation.ColorInt 8 | import com.xiaobin.quickbindadapter.R 9 | import com.xiaobin.quickbindadapter.databinding.McXbItemLoadmoreBinding 10 | 11 | class DefaultLoadViewConfigsBean : Cloneable { 12 | var onLoadingText: String = "" 13 | var onFailedText: String = "" 14 | var onWaitLoadingText: String = "" 15 | var noMoreDataText: String = "" 16 | var onSuccessText: String = "" 17 | 18 | @ColorInt 19 | var onLoadingTextColor: Int = Color.LTGRAY 20 | 21 | @ColorInt 22 | var onWaitLoadingTextColor: Int = Color.LTGRAY 23 | 24 | @ColorInt 25 | var onFailedTextColor: Int = Color.LTGRAY 26 | 27 | @ColorInt 28 | var noMoreDataTextColor: Int = Color.LTGRAY 29 | 30 | @ColorInt 31 | var onSuccessTextColor: Int = Color.LTGRAY 32 | 33 | public override fun clone(): DefaultLoadViewConfigsBean { 34 | return try { 35 | return super.clone() as? DefaultLoadViewConfigsBean 36 | ?: DefaultLoadViewConfigsBean() 37 | } catch (e: CloneNotSupportedException) { 38 | DefaultLoadViewConfigsBean() 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * 默认的加载更多Item布局 45 | */ 46 | class DefaultLoadView(private val context: Context) : 47 | BaseLoadView(R.layout.mc_xb_item_loadmore) { 48 | 49 | companion object { 50 | //全局配置 51 | var globalConfig: DefaultLoadViewConfigsBean? = null 52 | private set 53 | 54 | /** 55 | * 调用set方法,全局替换默认加载更多样式为自定义的加载更多样式 56 | * 如果统一设置为自定义的加载更多样式,那么就不需要配置全局配置项了,因为那个是给默认加载更多样式用的 57 | */ 58 | var defaultLoadView: (Context) -> BaseLoadView<*> = { 59 | DefaultLoadView(it) 60 | } 61 | 62 | //创建全局配置 63 | fun createGlobalConfig(result: () -> DefaultLoadViewConfigsBean) { 64 | globalConfig = result.invoke() 65 | } 66 | 67 | //创建全局配置 68 | fun createGlobalConfig(result: DefaultLoadViewConfigsBean) { 69 | globalConfig = result 70 | } 71 | 72 | } 73 | 74 | init { 75 | if (globalConfig == null) { 76 | createGlobalConfig(DefaultLoadViewConfigsBean().apply { 77 | onLoadingText = context.getString(R.string.load_onLoading) 78 | onFailedText = context.getString(R.string.load_onFailed) 79 | onWaitLoadingText = context.getString(R.string.load_onWaitLoading) 80 | noMoreDataText = context.getString(R.string.load_noMoreData) 81 | onSuccessText = context.getString(R.string.load_onSuccess) 82 | onLoadingTextColor = Color.LTGRAY 83 | onWaitLoadingTextColor = Color.LTGRAY 84 | onFailedTextColor = Color.LTGRAY 85 | noMoreDataTextColor = Color.LTGRAY 86 | onSuccessTextColor = Color.LTGRAY 87 | }) 88 | } 89 | } 90 | 91 | private var config: DefaultLoadViewConfigsBean = globalConfig!!.clone() 92 | set(value) { 93 | field = if (value === globalConfig) { 94 | //如果是设置的全局配置,则克隆一份 95 | value.clone() 96 | } else { 97 | value 98 | } 99 | } 100 | 101 | fun setOnWaitLoadingText(text: String) { 102 | config.onWaitLoadingText = text 103 | } 104 | 105 | fun setOnLoadingText(text: String) { 106 | config.onLoadingText = text 107 | } 108 | 109 | fun setOnFailedText(text: String) { 110 | config.onFailedText = text 111 | } 112 | 113 | fun setNoMoreDataText(text: String) { 114 | config.noMoreDataText = text 115 | } 116 | 117 | fun setOnSuccessText(text: String) { 118 | config.onSuccessText = text 119 | } 120 | 121 | fun setOnWaitLoadingTextColor(@ColorInt color: Int) { 122 | config.onWaitLoadingTextColor = color 123 | } 124 | 125 | fun setOnLoadingTextColor(@ColorInt color: Int) { 126 | config.onLoadingTextColor = color 127 | } 128 | 129 | fun setOnFailedTextColor(@ColorInt color: Int) { 130 | config.onFailedTextColor = color 131 | } 132 | 133 | fun setNoMoreDataTextColor(@ColorInt color: Int) { 134 | config.noMoreDataTextColor = color 135 | } 136 | 137 | fun setOnSuccessTextColor(@ColorInt color: Int) { 138 | config.onSuccessTextColor = color 139 | } 140 | 141 | override fun initView(loadView: McXbItemLoadmoreBinding?) { 142 | } 143 | 144 | override fun onStateChange(loadView: McXbItemLoadmoreBinding, state: LoadMoreState) { 145 | when (state) { 146 | LoadMoreState.LOADING -> { 147 | loadView.loading = true 148 | loadView.text = config.onLoadingText 149 | loadView.tvText.setTextColor(config.onLoadingTextColor) 150 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 151 | loadView.progress.progressTintList = 152 | ColorStateList.valueOf(config.onLoadingTextColor) 153 | } 154 | } 155 | LoadMoreState.SUCCESS -> { 156 | loadView.loading = false 157 | var text = config.onSuccessText 158 | if (text.isBlank()) { 159 | text = context.getString(R.string.load_onSuccess) 160 | } 161 | loadView.text = text 162 | loadView.tvText.setTextColor(config.onSuccessTextColor) 163 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 164 | loadView.progress.progressTintList = 165 | ColorStateList.valueOf(config.onSuccessTextColor) 166 | } 167 | } 168 | LoadMoreState.FAILED -> { 169 | loadView.loading = false 170 | var text = config.onFailedText 171 | if (text.isBlank()) { 172 | text = context.getString(R.string.load_onFailed) 173 | } 174 | loadView.text = text 175 | loadView.tvText.setTextColor(config.onFailedTextColor) 176 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 177 | loadView.progress.progressTintList = 178 | ColorStateList.valueOf(config.onFailedTextColor) 179 | } 180 | } 181 | LoadMoreState.NO_MORE -> { 182 | loadView.loading = false 183 | var text = config.noMoreDataText 184 | if (text.isBlank()) { 185 | text = context.getString(R.string.load_noMoreData) 186 | } 187 | loadView.text = text 188 | loadView.tvText.setTextColor(config.noMoreDataTextColor) 189 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 190 | loadView.progress.progressTintList = 191 | ColorStateList.valueOf(config.noMoreDataTextColor) 192 | } 193 | } 194 | LoadMoreState.WAIT_LOADING -> { 195 | loadView.loading = false 196 | var text = config.onWaitLoadingText 197 | if (text.isBlank()) { 198 | text = context.getString(R.string.load_onWaitLoading) 199 | } 200 | loadView.text = text 201 | loadView.tvText.setTextColor(config.onWaitLoadingTextColor) 202 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 203 | loadView.progress.progressTintList = 204 | ColorStateList.valueOf(config.onWaitLoadingTextColor) 205 | } 206 | } 207 | else -> {} 208 | } 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/layout/mc_xb_item_loadmore.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | 23 | 24 | 30 | 31 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/layout/mc_xb_layout_page_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/layout/mc_xb_layout_page_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/layout/mc_xb_layout_page_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/layout/mc_xb_layout_place_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /quickbindadapter/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 正在加载中 5 | 加载失败了,点击重试 6 | 点击加载更多 7 | 没有更多了 8 | 完成加载 9 | 10 | 没有内容 11 | 获取失败 12 | 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':quickbindadapter' 2 | --------------------------------------------------------------------------------