Gitee 地址:kmvvm
Github 地址:kmvvm
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
apply plugin: 'kotlin-kapt' // 使用 kapt 注解处理工具
apply plugin: 'kotlinx-serialization' //序列化
buildFeatures {
viewBinding = true
}
Gitee
implementation "com.gitee.catchpig.kmvvm:mvvm:last_version"
kapt "com.gitee.catchpig.kmvvm:compiler:last_version"
Github
implementation "com.github.catchpig.kmvvm:mvvm:last_version"
kapt "com.github.catchpig.kmvvm:compiler:last_version"
interface IGlobalConfig {
/**
* 标题栏高度
* @return Int
*/
@DimenRes
fun getTitleHeight(): Int
/**
* 标题栏的返回按钮资源
* @return Int
*/
@DrawableRes
fun getTitleBackIcon(): Int
/**
* 标题栏背景颜色
* @return Int
*/
@ColorRes
fun getTitleBackground(): Int
/**
* 标题栏文本颜色
* @return Int
*/
@ColorRes
fun getTitleTextColor(): Int
/**
* 标题栏下方是否需要横线
* @return Boolean
*/
fun isShowTitleLine(): Boolean
/**
* 标题栏下方横线颜色
* @return Int
*/
@ColorRes
fun getTitleLineColor(): Int
/**
* loading的颜色
* @return Int
*/
@ColorRes
fun getLoadingColor(): Int
/**
* loading的背景颜色
* @return Int
*/
@ColorRes
fun getLoadingBackground(): Int
/**
* RecyclerView的空页面ViewBinding
* @param parent ViewGroup
* @return ViewBinding
*/
fun getRecyclerEmptyBanding(parent: ViewGroup): ViewBinding
/**
* 刷新每页加载个数
* @return Int
*/
fun getPageSize(): Int
/**
* 刷新起始页的index(有些后台设置的0,有些后台设置1)
*/
fun getStartPageIndex(): Int
}
- 实现IGlobalConfig 接口,并在实现类上加上注解GlobalConfig
使用示例:
@GlobalConfig
class MvvmGlobalConfig : IGlobalConfig {
override fun getTitleHeight(): Int {
return R.dimen.title_bar_height
}
override fun getTitleBackIcon(): Int {
return R.drawable.back_black
}
override fun getTitleBackground(): Int {
return R.color.colorPrimary
}
override fun getTitleTextColor(): Int {
return R.color.white
}
override fun isShowTitleLine(): Boolean {
return true
}
override fun getTitleLineColor(): Int {
return R.color.color_black
}
override fun getLoadingColor(): Int {
return R.color.color_black
}
override fun getLoadingBackground(): Int {
return R.color.white
}
override fun getRecyclerEmptyBanding(parent: ViewGroup): ViewBinding {
return LayoutEmptyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}
override fun getPageSize(): Int {
return 16
}
override fun getStartPageIndex(): Int {
return 1
}
}
- 使用MVVM的继承BaseVMActivity
- 不使用MVVM的继承BaseActivity'
使用示例
Title其他注解参数,请看下方注解详情
//设置标题的文字
@Title(R.string.child_title)
class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>()
如果标题栏文字要根据接口显示不同的文字,也有接口设置
class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>() {
@OnClickFirstDrawable(R.drawable.more)
fun clickFirstDrawable(v: View) {
updateTitle("更改标题")
}
}
使用示例
StatusBar其他注解参数,请看下方注解详情
//弃用注解
@StatusBar(hide = true)
class FullScreenActivity : BaseActivity<ActivityFullScreenBinding>()
使用示例
注解修饰的方法只能可以带View参数,也可以不带View参数,看自身的需求
@Title(R.string.child_title)
class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>() {
@OnClickFirstDrawable(R.drawable.more)
fun clickFirstDrawable(v: View) {
SnackbarManager.show(bodyBinding.root, "第一个图标按钮点击生效")
updateTitle("nihao")
}
@OnClickFirstText(R.string.more)
fun clickFirstText() {
SnackbarManager.show(bodyBinding.root, "第一个文字按钮点击生效")
updateTitle("12354")
}
@OnClickSecondDrawable(R.drawable.more)
fun clickSecondDrawable(v: View) {
SnackbarManager.show(bodyBinding.root, "第二个图标按钮点击生效")
updateTitle("nihao")
}
@OnClickSecondText(R.string.more)
fun clickSecondText() {
SnackbarManager.show(bodyBinding.root, "第二个文字按钮点击生效")
updateTitle("12354")
}
}
- Android 11 之后,Toast已经不支持自定义Toast,原生的Toast是很难看的
- 本框架使用SnackBar做提示框
使用示例
@OnClickSecondDrawable(R.drawable.more)
fun clickSecondDrawable(v: View) {
snackBar("第二个图标按钮点击生效")
}
- 使用MVVM的继承BaseVMFragment
- 不使用MVVM的继承BaseFragment
- Android 11 之后,Toast已经不支持自定义Toast,原生的Toast是很难看的
- 本框架使用SnackBar做提示框
使用示例
snackbar.setOnClickListener {
snackBar("提示框")
}
Adapter可以继承RecycleAdapter来使用,并在类上添加注解Adapter ,RecycleAdapter使用了ViewBanding,只需要实现以下一个方法
使用示例
@Adapter
class UserAdapter(iPageControl: IPageControl) :
RecyclerAdapter<User, ItemUserBinding>(iPageControl) {
override fun bindViewHolder(holder: CommonViewHolder<ItemUserBinding>, m: User, position: Int) {
holder.viewBanding {
name.text = m.name
}
}
}
5.刷新分页控件(RefreshRecyclerView)
- RefreshRecyclerView集成了RefreshLayoutWrapper+RecyclerView
不用关心分页的逻辑,分页的刷新逻辑实现都在RefreshLayoutWrapper
- 只需要设置LayoutManager和RecyclerAdapter,提供了setLayoutManager和setAdapter方法
- 在获取到数据的时候调用updateData方法
- 获取数据失败的时候调用updateError方法
- 如果使用了lifecycleFlowRefresh方法,updateData方法和updateError方法都不用关心
- 提供自定义属性recycler_background(设置RecyclerView的背景色)
<declare-styleable name="RefreshRecyclerView">
<attr name="recycler_background" format="color" />
</declare-styleable>
使用示例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="50dp"
android:background="@color/colorPrimary" android:gravity="center" android:text="文章"
android:textColor="@color/color_white" />
<com.catchpig.mvvm.widget.refresh.RefreshRecyclerView android:id="@+id/refresh"
android:layout_width="match_parent" android:layout_height="match_parent"
app:recycler_background="#445467">
</com.catchpig.mvvm.widget.refresh.RefreshRecyclerView>
</LinearLayout>
bodyBinding.refresh.run {
setOnRefreshLoadMoreListener { nextPageIndex ->
lifecycleFlowRefresh(viewModel.queryArticles(nextPageIndex), this)
}
}
6.1只需要是接口类上加上注解ServiceApi,并使用NetManager.getService()获取对应的接口类
使用示例
@ServiceApi(
baseUrl = "https://www.wanandroid.com/",
responseConverter = ResponseBodyConverter::class,
interceptors = [RequestInterceptor::class],
debugInterceptors = [OkHttpProfilerInterceptor::class]
)
interface WanAndroidService {
@GET("banner/json")
suspend fun banner(): List<Banner>
}
object WanAndroidRepository {
private val wanAndroidService = NetManager.getService(WanAndroidService::class.java)
fun getBanners(): Flow<MutableList<Banner>> {
//这里如果用flowOf的话,方法上面必须加上suspend关键字
return flow {
emit(wanAndroidService.queryBanner())
}
}
}
class IndexViewModel : BaseViewModel() {
fun queryBanners(): Flow<MutableList<Banner>> {
return WanAndroidRepository.getBanners()
}
}
//Activity或者Fragment
lifecycleFlowLoadingView(viewModel.queryBanners()) {
val images = mutableListOf<String>()
this.forEach {
images.add(it.imagePath)
}
bodyBinding.banner.run {
setImages(images)
start()
}
}
- 一般Response发返回结果会是如下
{
code:"SUCCESS",
errorMsg:"成功",
data:...
}
- 在code返回SUCEESSD的时候, 我们在Retrofit的Api接口里面只想拿到data的数据做返回,我们想在Converter里面处理掉code返回错误码的逻辑,就可以继承BaseResponseBodyConverter,内部已经实现了将response转化为data的逻辑
代码示例
class ResponseBodyConverter :
BaseResponseBodyConverter() {
override fun getResultClass(): KClass<out BaseResponseData<JsonElement>> {
return Result::class
}
override fun handlerErrorCode(errorCode: String, msg: String): Exception {
return NullPointerException()
}
}
- 再将实现了BaseResponseBodyConverter的类加到ServiceApi注解的responseConverter属性上
- 如果想直接拿response的结果作为网络请求的返回值,可以直接将SerializationResponseBodyConverter加到ServiceApi注解的responseConverter属性上
- lifecycleFlowRefresh(flow: Flow<MutableList>,refresh: RefreshRecyclerView) -刷新+RecycleView的网络请求封装
- lifecycleFlow(flow: Flow, callback: T.() -> Unit)-不带loading的网络请求封装
- lifecycleFlowLoadingView(flow: Flow, callback: T.() -> Unit)-带loadingView的网络请求封装
- lifecycleFlowLoadingDialog(flow: Flow, callback: T.() -> Unit)-带loadingDialog的网络请求封装
7.1 Title-标题
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | StringRes | 是 | 无 | 标题内容 |
backgroundColor | ColorRes | 否 | 全局标题背景色 | 标题背景色 |
textColor | ColorRes | 否 | 全局标题文字颜色 | 标题文字颜色 |
backIcon | DrawableRes | 否 | 全局标题返回按钮图标 | 标题返回按钮图标 |
7.2 OnClickFirstDrawable-标题上第一个图标按钮的点击事件
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | DrawableRes | 是 | 无 | 按钮图片资源 |
7.3 OnClickFirstText-标题上第一个文字按钮的点击事件
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | StringRes | 是 | 无 | 按钮文字内容 |
7.4 OnClickSecondDrawable-标题上第二个图标按钮的点击事件
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | DrawableRes | 是 | 无 | 按钮图片资源 |
7.5 OnClickSecondText-标题上第二个文字按钮的点击事件
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | StringRes | 是 | 无 | 按钮文字内容 |
7.6 StatusBar-状态栏
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
hide | boolean | 否 | false | 隐藏状态栏 |
enabled | boolean | 否 | false | 状态栏是否可用 |
transparent | boolean | 否 | false | 状态栏透明 |
7.7 Prefs-SharedPreferences注解生成器
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | String | 否 | "" | 别名 |
mode | PrefsMode | 否 | PrefsMode.MODE_PRIVATE | 模式,对应PreferencesMode |
7.8 PrefsField-SharedPreferences字段注解
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
value | String | 否 | "" | 字段别名,如果为空则取修饰字段的参数名称 |
7.9 FlowError-Activity和Fragment中的Flow的onError方法统一处理
7.10 Adapter-RecyclerAdapter的继承类注解,加上此注解之后可以自动找到对应的layout资源
7.11 GlobalConfig-全局参数配置
- 注解在IGlobalConfig接口的实现类上面
7.12 ServiceApi-网络请求接口注解类
属性 | 类型 | 必须 | 默认 | 说明 |
---|---|---|---|---|
baseUrl | String | 是 | 无 | retrofit的baseurl |
responseConverter | Converter | 是 | 无 | 接收数据转换器 |
connectTimeout | Long | 否 | 5000 | http的超时时间 |
readTimeout | Long | 否 | 5000 | http读取超时时间 |
interceptors | Interceptor | 否 | Interceptor | http拦截器 |
debugInterceptors | Interceptor | 否 | Interceptor | debug模式下的http拦截器,只有NetManager.setDebug(true),才会生效 |
8. 文件下载器(DownloadManager))
- 单文件下载方法download(DownloadCallback)
DownloadManager.download(downloadUrl, {
//下载完成回调
}, { downloadProgress ->
//下载进度回调
})
- DownloadCallback
interface DownloadCallback {
/**
* 开始下载
*/
fun onStart()
/**
* 下载成功
* @param path 本地保存的地址
*/
fun onSuccess(path:String)
/**
* 下载完成
*/
fun onComplete()
/**
* 下载进度
* @param downloadProgress DownloadProgress
*/
fun onProgress(downloadProgress: DownloadProgress)
/**
* 下载错误
* @param t 错误信息
*/
fun onError(t:Throwable)
}
- 多文件下载方法multiDownload(MultiDownloadCallback)
DownloadManager.multiDownload(downloadUrls, {
//下载完成回调
}, { downloadProgress ->
//下载进度回调
})
- MultiDownloadCallback
interface MultiDownloadCallback {
/**
* 开始下载
*/
fun onStart()
/**
* 下载成功
* @param paths 本地保存的地址集
*/
fun onSuccess(paths:MutableList<String>)
/**
* 文件下载进度
* @param downloadProgress DownloadProgress
*/
fun onProgress(downloadProgress: DownloadProgress)
/**
* 下载完成
*/
fun onComplete()
/**
* 下载错误
* @param t 错误信息
*/
fun onError(t:Throwable)
}
-keep class com.catchpig.annotation.enums.**
-keep class com.google.android.material.snackbar.Snackbar {*;}
-keep @com.catchpig.annotation.Adapter class * {*;}
-keep @com.catchpig.annotation.ServiceApi class * {*;}
-keep public class **.databinding.*Binding {*;}
-keep class **.*_Compiler {*;}
-keep class com.gyf.immersionbar.* {*;}
-dontwarn com.gyf.immersionbar.**
#序列化混淆
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <1>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault