[TOC]
今天是在项目的时候频繁创建mvp架构的类,突然想到之前用到的AndroidLiveTemplate模板,于是就想写一个模板一键创建, 于是乎就去安装目录下寻找相关的模板文件夹,找了半天发现原来的那套freemarker的那套模板引擎被和谐了,网上翻阅了资料才发现 从Android4.1开始,Android live template的方式已经被Google和谐了,最近刚好是项目有这个需要,所以从新整理一下官方文档,也参考了一些网友的总结,再结合我项目MVP的架构,写了一个插件
这是一个AndroidStudio的插件,使用的是intellij官方开源的intellij platform plugin template 我在这里就不对这个项目的结构以及详细的api进行讲解,如果有需要的小伙伴可以移步查询,本文档直接从项目源码进行改造成我们想要的template开始
首先我们下载了intellij官方模板之后,我们首先在项目的根目录下创建一个lib目录,然后在AndroidStudio的安装目录下面找到wizard-template.jar,windows电脑该文件在Android Studio\plugins\android\lib\ 目录下,MacOS系统的话在Applications/Android Studio.app/Contents/plugins/android/lib/目录下, 找到这个文件之后将其复制到新建的lib目录下面
添加依赖
dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.1")
compileOnly(files("lib/wizard-template.jar")) // 添加这一行
}
这里我们需要修改的地方分别是如下四处
字段 | 是否必须要修改 | 原始值 | 修改后的值 |
---|---|---|---|
pluginGroup | x | org.jetbrains.plugins.template | com.neo.mvp.template |
pluginName | x | IntelliJ Platform Plugin Template | MVP Template |
platformVersion | x | 0.10.1 | 0.0.1 |
platformPlugins | √ | Kotlin,com.intellij.java,org.jetbrains.android, android, org.jetbrains.kotlin |
这其中有三个不是必须要修改的,不该也不影响后续的开发,不多还是建议结合自己项目修改一下这些配置
rootProject.name = "MVP Template"
接下来对项目代码的编写,如果你修改了gradle.properties里的所pluginGroup属性的值,接下来你需要对 src/main/kotlin/ 目录下的包路径修改成对应的包名
管理着项目的生命周期的监听
internal class MyProjectManagerListener : ProjectManagerListener {
private var projectInstance: Project? = null
override fun projectOpened(project: Project) {
projectInstance = project
project.getService(MyProjectService::class.java)
}
override fun projectClosing(project: Project) {
projectInstance = null
super.projectClosing(project)
}
}
├──src
├ └──main
├ └──kotlin
├ └──other
├ └──mvp
├ └──SimplePluginTemplateProviderImpl.kt
├ └──activity
├ └──res.layout
├ └──mvpActivityXml.kt
├ └──src.app_package
├ └──contract
├ └──mvpContract.kt
├ └──p
├ └──mvpPresenter.kt
├ └──v
├ └──mvpActivity.kt
├ └──mvpBasePresenter.kt
├ └──mvpBaseView.kt
├ └──mvpActivityRecipe.kt
├ └──mvpActivityTemplate.kt
MVPTemplateProviderImpl.kt
package other.mvp
import com.android.tools.idea.wizard.template.Template
import com.android.tools.idea.wizard.template.WizardTemplateProvider
import other.mvp.activity.mvpActivityTemplate
/**
* @Author Neo
* @Date 2021/6/7
* @Env Viicare-Neo
* @Description SamplePluginTemplateProviderImpl
*/
class MVPTemplateProviderImpl: WizardTemplateProvider() {
override fun getTemplates(): List<Template> {
return listOf(
// activity 模板
mvpActivityTemplate
)
}
}
这个文件是设置在创建Activity的时候,输入的信息, 例如 ActivityName,layoutName, packageName
package other.mvp.activity
import com.android.tools.idea.wizard.template.*
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API
/**
* @Author Neo
* @Date 2021/6/7
* @Env Viicare-Neo
* @Description mvpActivityTemplate
* 这个文件是设置在创建Activity的时候,输入的信息,
* 例如 ActivityName,layoutName, packageName
*/
val mvpActivityTemplate
get() = template {
revision = 1
name = "MVP Activity"
description = "适用于MVP框架的Activity"
minApi = MIN_API
minBuildApi = MIN_API
category = Category.Other
formFactor = FormFactor.Mobile
screens = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)
lateinit var layoutName: StringParameter
val activityClass = stringParameter {
name = "Activity Name"
default = "Main"
help = "只输入名字,不要包含Activity"
constraints = listOf(Constraint.NONEMPTY)
}
layoutName = stringParameter {
name = "Layout Name"
default = "activity_main"
help = "请输入布局的名字"
constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
suggest = { activityToLayout(activityClass.value.toLowerCase()) }
}
val packageName = defaultPackageNameParameter
widgets(
TextFieldWidget(activityClass),
TextFieldWidget(layoutName),
PackageNameWidget(packageName)
)
// thumb { File("logo.png") }
recipe = { data: TemplateData ->
mvpActivityRecipe(
data as ModuleTemplateData,
activityClass.value,
layoutName.value,
packageName.value)
}
}
val defaultPackageNameParameter
get() = stringParameter {
name = "Package name"
visible = { !isNewModule }
default = "com.neo.myapp"
constraints = listOf(Constraint.PACKAGE)
suggest = { packageName }
}
这个类是处理按照模板创建项目文件并保存的,我们再创建other.mvp.*下面的文件路径是可以任意的, 实际创建的文件包路径都是在这个文件的save方法中决定
package other.mvp.activity
import com.android.tools.idea.wizard.template.ModuleTemplateData
import com.android.tools.idea.wizard.template.RecipeExecutor
import other.mvp.activity.res.layout.mvpActivityXml
import other.mvp.activity.src.app_package.contract.mvpContract
import other.mvp.activity.src.app_package.mvpBasePresenter
import other.mvp.activity.src.app_package.mvpBaseView
import other.mvp.activity.src.app_package.p.mvpPresenter
import other.mvp.activity.src.app_package.v.mvpActivityKt
/**
* @Author Neo
* @Date 2021/6/7
* @Env Viicare-Neo
* @Description mvpActivityRecipe 这个文件用于将创建的文件保存到文件夹中,例如Activity,布局文件等
*/
fun RecipeExecutor.mvpActivityRecipe(
moduleData: ModuleTemplateData,
activityClass: String,
layoutName: String,
packageName: String
) {
val (projectData, srcOut, resOut) = moduleData
val ktOrJavaExt = projectData.language.extension
val mvpActivity = mvpActivityKt(packageName, activityClass, layoutName, packageName)
// 保存Activity
save(mvpActivity, srcOut.resolve("v/${activityClass}Activity.${ktOrJavaExt}"))
// 保存xml
save(mvpActivityXml(packageName, activityClass), resOut.resolve("layout/${layoutName}.xml"))
try {
// 判断BaseView类是否存在,如果不存在则创建保存
val forName = Class.forName("${packageName}.BaseView")
} catch (e: Exception) {
save(mvpBaseView(packageName), srcOut.resolve("BaseView.${ktOrJavaExt}"))
}
try {
// 判断BasePresenter类是否存在,如果不存在则创建保存
val forName = Class.forName("${packageName}.BasePresenter")
} catch (e: Exception) {
save(mvpBasePresenter(packageName), srcOut.resolve("BasePresenter.${ktOrJavaExt}"))
}
// 保存Contract
save(mvpContract(packageName,activityClass), srcOut.resolve("contract/${activityClass}Contract.${ktOrJavaExt}"))
// 保存Presenter
save(mvpPresenter(packageName,packageName,activityClass), srcOut.resolve("p/${activityClass}Presenter.${ktOrJavaExt}"))
}
这个是我项目中mvp架构的view底层类
package other.mvp.activity.src.app_package
/**
* @Author Neo
* @Date 2021/6/8
* @Env Viicare-Neo
* @Description mvpBaseView
*/
fun mvpBaseView(
packageName: String
) = """
package $packageName
interface BaseView<P> {
fun setPresenter(p:P)
}
"""
这个是我项目中mvp架构的presenter底层类
package other.mvp.activity.src.app_package
/**
* @Author Neo
* @Date 2021/6/8
* @Env Viicare-Neo
* @Description mvpBasePresenter
*/
fun mvpBasePresenter(
packageName:String
)="""
package $packageName
interface BasePresenter {
fun subscribe()
fun unSubscribe()
}
"""
这个是我项目mvp架构中针对不同activity进行注册v,p两层处理方法的接口
package other.mvp.activity.src.app_package.contract
/**
* @Author Neo
* @Date 2021/6/8
* @Env Viicare-Neo
* @Description mvpContract
*/
fun mvpContract(
packageName:String,
activityName:String
)="""
package $packageName.contract
import ${packageName}.BaseView
import ${packageName}.BasePresenter
interface ${activityName}Contract {
interface View: BaseView<Presenter> {
}
interface Presenter: BasePresenter {
}
}
"""
这个是我项目mvp架构中针对不同activity进行P端代码的具体实现类
package other.mvp.activity.src.app_package.p
/**
* @Author Neo
* @Date 2021/6/8
* @Env Viicare-Neo
* @Description mvpPresenter
*/
fun mvpPresenter (
applicationPackage: String?,
packageName:String,
activityClass: String
)="""
package $packageName.p
import ${applicationPackage}.contract.${activityClass}Contract
class ${activityClass}Presenter(private val mView: ${activityClass}Contract.View): ${activityClass}Contract.Presenter {
init {
// TODO something
mView.setPresenter(this)
}
override fun subscribe() {
}
override fun unSubscribe() {
}
}
"""
这个是我项目中页面activity
package other.mvp.activity.src.app_package.v
/**
* @Author Neo
* @Date 2021/6/7
* @Env Viicare-Neo
* @Description mvpActivityKt
*/
fun mvpActivityKt (
applicationPackage: String?,
activityClass: String,
layoutName: String,
packageName: String
)="""
package $packageName.v
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ${applicationPackage}.p.${activityClass}Presenter
import ${applicationPackage}.contract.${activityClass}Contract
class ${activityClass}Activity : AppCompatActivity (),${activityClass}Contract.View {
private lateinit var mPresenter: ${activityClass}Contract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.${layoutName})
// TODO something
${activityClass}Presenter(this)
}
override fun setPresenter (p: ${activityClass}Contract.Presenter) {
this.mPresenter = p
this.mPresenter.subscribe()
}
}
"""
决定此布局文件的类是res/layout/mvpActivityXml.kt
package other.mvp.activity.res.layout
/**
* @Author Neo
* @Date 2021/6/7
* @Env Viicare-Neo
* @Description mvpActivityXml
*/
fun mvpActivityXml(packageName: String,
activityClass: String
)="""
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="19dp"
tools:context="${packageName}.v.${activityClass}Activity"
>
</androidx.constraintlayout.widget.ConstraintLayout>
"""
莫着急,还有最后一步
- 首先必须添加依赖
<depends>org.jetbrains.android</depends>
<depends>org.jetbrains.kotlin</depends>
<depends>com.intellij.modules.java</depends>
- 如果你在之前步骤中修改了报名,这还需要根据自己的需求进行等值的修改
- 接下来还需要替换我们创建的 applicationService、 projectService
- 修改/的值为我们之前创建的监听项目生命周期的listener文件的路径
- 最后添加的值为我们创建的MVPTemplateProviderImpl 直接上代码
<idea-plugin>
<id>com.neo.mvp.template</id>
<name>MVP Template</name>
<description>使用该模板创建基于MVP的Activity,layout,presenter,contract等文件</description>
<vendor>Neo</vendor>
<!-- Product and plugin compatibility requirements -->
<!-- https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
<depends>com.intellij.modules.platform</depends>
<depends>org.jetbrains.android</depends>
<depends>org.jetbrains.kotlin</depends>
<depends>com.intellij.modules.java</depends>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.neo.mvp.template.services.MyApplicationService"/>
<projectService serviceImplementation="com.neo.mvp.template.services.MyProjectService"/>
</extensions>
<applicationListeners>
<listener class="com.neo.mvp.template.listeners.MyProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
</applicationListeners>
<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
<wizardTemplateProvider implementation="other.mvp.MVPTemplateProviderImpl"/>
</extensions>
</idea-plugin>
Well,Well! 插件至此就算是开发完成了,接下来运行 Run Plugin,执行成功会在/build/libs/ 目录下生成我们想要的jar , 将此jar安装到AndroidStudio Plugin中即可, New->Other->xxx 效果如下图
- 目前创建Activity之后还没有完成对其在Manifest.xml中的注册,需要开发者手动自行注册,这个问题下个版本中完成