Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

该项目是Android Studio的一个插件,用于一键创建Android的MVP模板的activity,layout等文件

License

Notifications You must be signed in to change notification settings

NeoPi/Android-MVP-Template

Repository files navigation

[TOC]

Android MVP Template

今天是在项目的时候频繁创建mvp架构的类,突然想到之前用到的AndroidLiveTemplate模板,于是就想写一个模板一键创建, 于是乎就去安装目录下寻找相关的模板文件夹,找了半天发现原来的那套freemarker的那套模板引擎被和谐了,网上翻阅了资料才发现 从Android4.1开始,Android live template的方式已经被Google和谐了,最近刚好是项目有这个需要,所以从新整理一下官方文档,也参考了一些网友的总结,再结合我项目MVP的架构,写了一个插件

这是一个AndroidStudio的插件,使用的是intellij官方开源的intellij platform plugin template 我在这里就不对这个项目的结构以及详细的api进行讲解,如果有需要的小伙伴可以移步查询,本文档直接从项目源码进行改造成我们想要的template开始

添加wizard-template.jar

首先我们下载了intellij官方模板之后,我们首先在项目的根目录下创建一个lib目录,然后在AndroidStudio的安装目录下面找到wizard-template.jar,windows电脑该文件在Android Studio\plugins\android\lib\ 目录下,MacOS系统的话在Applications/Android Studio.app/Contents/plugins/android/lib/目录下, 找到这个文件之后将其复制到新建的lib目录下面

修改build.gradle.kts

添加依赖

dependencies {
 detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.1")
 compileOnly(files("lib/wizard-template.jar")) // 添加这一行
}

修改gradle.properties

这里我们需要修改的地方分别是如下四处

字段 是否必须要修改 原始值 修改后的值
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

这其中有三个不是必须要修改的,不该也不影响后续的开发,不多还是建议结合自己项目修改一下这些配置

修改setting.gradle.kts

rootProject.name = "MVP Template"

接下来对项目代码的编写,如果你修改了gradle.properties里的所pluginGroup属性的值,接下来你需要对 src/main/kotlin/ 目录下的包路径修改成对应的包名

改造 listeners/MyProjectManagerListener.kt

管理着项目的生命周期的监听

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,其他包和文件的目录结构如下

├──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

以Activity为例,为创建的文件注入灵魂

实现 WizardTemplateProvider

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
 )
 }
}

为mvpActivityTemplate.kt 注入灵魂

这个文件是设置在创建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 }
 }

为mvpActivityRecipe.kt 注入灵魂

这个类是处理按照模板创建项目文件并保存的,我们再创建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}"))
}

为mvpBaseView.kt注入灵魂

这个是我项目中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)
}

"""

为mvpBasePresenter.kt注入灵魂

这个是我项目中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()
}
"""

为mvpContract.kt注入灵魂

这个是我项目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 {

 }
}

"""

为mvpPresenter.kt注入灵魂

这个是我项目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() {

 }
} 

"""

为mvpActivity.kt注入灵魂

这个是我项目中页面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()
 }
}
"""

接下来就是为布局文件layout注入灵魂

决定此布局文件的类是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> 
"""

莫着急,还有最后一步

修改 src/main/resources/META-INF/plugin.xml

  • 首先必须添加依赖
 <depends>org.jetbrains.android</depends>
 <depends>org.jetbrains.kotlin</depends>
 <depends>com.intellij.modules.java</depends>
  • 如果你在之前步骤中修改了报名,这还需要根据自己的需求进行等值的修改
  • 接下来还需要替换我们创建的 applicationServiceprojectService
  • 修改/的值为我们之前创建的监听项目生命周期的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 效果如下图 image

遗留问题

  • 目前创建Activity之后还没有完成对其在Manifest.xml中的注册,需要开发者手动自行注册,这个问题下个版本中完成

About

该项目是Android Studio的一个插件,用于一键创建Android的MVP模板的activity,layout等文件

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /