# 插件制作者/所属组织
pluginGroup = lx8421bcd
# 插件名称
pluginName = quickdevtemplates
# 插件版本
pluginVersion = 1.0.0
修改项目包名, 将src/main
里的代码包名从org.jetbrains.plugin.template
改成你想要的包名(比如我为QuickDevFramework改为com.lx8421bcd.qdftemplate
)。然后修改src/main/resources/META-INF/plugin.xml
文件的配置
<idea-plugin>
<id>com.lx8421bcd.qdftemplates</id>
<name>QDFTemplate</name>
<vendor>lx8421bcd</vendor>
......
</idea-plugin>
从Android Studio安装目录中把wizard_template.jar
复制出来,如果用的是mac,以下命令供参考:
cp /Applications/Android\ Studio.app/Contents/plugins/android/lib/wizard-template.jar ~/Desktop
在插件项目根目录中创建一个lib
文件夹,把wizard_template.jar
放进去。
编辑项目的build.gradle.kts
配置文件,加入依赖
dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.1")
// 添加依赖库
compileOnly(files("lib/wizard-template.jar"))
在plugin.xml
中添加依赖项
<idea-plugin>
......
<!-- 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>
</idea-plugin>
sync project
修改插件项目功能入口
编辑MyProjectManagerListener.kt
,配置插件在项目初始化时的操作
internal class MyProjectManagerListener : ProjectManagerListener {
override fun projectOpened(project: Project) {
projectInstance = project
project.getService(MyProjectService::class.java)
override fun projectClosing(project: Project) {
projectInstance = null
super.projectClosing(project)
companion object {
var projectInstance: Project? = null
在与listener
同级的package下创建一个other
package
--- com.lx8421bcd.qdftemplate
|--- listener
|--- services
|--- other
创建TemplateProvider继承实现,用于让IDE查找本插件项目提供的模板
package other
import com.android.tools.idea.wizard.template.Template
import com.android.tools.idea.wizard.template.WizardTemplateProvider
import other.activity.SimpleViewBindingActivityTemplate
import other.fragment.SimpleViewBindingFragmentTemplate
class QDFPluginTemplateProviderImpl : WizardTemplateProvider() {
override fun getTemplates(): List<Template> = listOf(
// 在项目中创建的模板需要在这个列表内声明
SimpleViewBindingActivityTemplate,
SimpleViewBindingFragmentTemplate,
修改plugin.xml
,添加对TemplateProviderImpl的声明,完整plugin.xml
如下:
<idea-plugin>
<id>com.lx8421bcd.qdftemplates</id>
<name>QDFTemplate</name>
<vendor>lx8421bcd</vendor>
<!-- Product and plugin compatibility requirements -->
<!-- https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
<depends>org.jetbrains.android</depends>
<depends>org.jetbrains.kotlin</depends>
<depends>com.intellij.modules.platform</depends>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.lx8421bcd.qdftemplates.services.MyApplicationService"/>
<projectService serviceImplementation="com.lx8421bcd.qdftemplates.services.MyProjectService"/>
</extensions>
<applicationListeners>
<listener class="com.lx8421bcd.qdftemplates.listeners.MyProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
</applicationListeners>
<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
<wizardTemplateProvider implementation="other.QDFPluginTemplateProviderImpl" />
</extensions>
</idea-plugin>
import com.android.tools.idea.wizard.template.*
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API
import com.android.tools.idea.wizard.template.impl.activities.common.generateManifest
import com.intellij.util.xml.DomManager
//用于提供默认PackageName
val defaultPackageNameParameter
get() = stringParameter {
name = "Package name"
visible = { !isNewModule }
default = "com.lx8421bcd.example"
constraints = listOf(Constraint.PACKAGE)
suggest = { packageName }
// Activity模板Builder
val SimpleViewBindingActivityTemplate
get() = template {
revision = 1
name = "Simple ViewBinding Activity"
description = "基于ViewBinding基类的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(不包含\"Activity\")"
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)
recipe = { data: TemplateData ->
simpleViewBindingActivityRecipe(
data as ModuleTemplateData,
activityClass.value,
layoutName.value,
packageName.value)
// 用于向项目写入模板文件的方法
fun RecipeExecutor.simpleViewBindingActivityRecipe(
moduleData: ModuleTemplateData,
activityClass: String,
layoutName: String,
packageName: String
val (projectData, srcOut, resOut) = moduleData
val ktOrJavaExt = projectData.language.extension
// 插入manifest声明
generateManifest(
moduleData = moduleData,
activityClass = "${activityClass}Activity",
activityTitle = activityClass,
packageName = packageName,
isLauncher = false,
hasNoActionBar = false,
generateActivityTitle = false,
// 生成activity文件
val activityFile = simpleViewBindingActivityKt(projectData.applicationPackage, activityClass, packageName)
save(activityFile, srcOut.resolve("${activityClass}Activity.${ktOrJavaExt}"))
// 生成xml布局文件
val xmlFile = simpleViewBindingActivityXml(packageName, activityClass)
save(xmlFile, resOut.resolve("layout/${layoutName}.xml"))
/*-------------------- activity code generate function ----------------------*/
fun simpleViewBindingActivityKt(
applicationPackage:String?,
activityClass:String,
packageName:String
)="""
package $packageName
import android.os.Bundle
import com.linxiao.framework.architecture.SimpleViewBindingActivity
import ${applicationPackage}.R
import ${applicationPackage}.databinding.Activity${activityClass}Binding
class ${activityClass}Activity : SimpleViewBindingActivity<Activity${activityClass}Binding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initView()
private fun initView() {
/*-------------------- layout xml code generate function ----------------------*/
fun simpleViewBindingActivityXml(
packageName: String,
activityClass: String
) = """
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
tools:context="${packageName}.${activityClass}Activity">
</RelativeLayout>
完整的项目结构和代码,个人为QuickDevFramework做的模板插件项目QDFTemplates仅供参考
导出插件、安装
在Android Studio(Intellij ieda亦可)工具栏的Run上选择“Run Plugin”,build完成后会在项目的build/libs/
目录下生成插件Jar包。
安装插件:
打开Android Studio设置
在Plugins一栏点击齿轮图标,选择“Install Plugin from Disk”
选择生成的Jar包安装
重启Android Studio,完成安装
本来自定义开发模板的本质也就是根据模板指引面板的输入,将一堆字符串改改保存到文件中存到指定位置,用不上太复杂的功能。 由于是使用wizard_template.jar
包内提供的API直接编写kotlin代码,加之有IDE环境下,开发起来还是要方便不少,最起码不会在文本编辑器上抓瞎编写。其实这个模板不仅限于生成Activity、Fragment这些Android组件,也可以用于配置更复杂模板,例如MVVM架构下配套的ViewModel、带有列表的页面等等,只要参照生成文件的方法写好,保存到对应路径就行。
需要注意的一点是,由于编辑Android组件模板内容其实就是编辑字符串,所以错误提醒是不存在的。咱们用模板就是图个一步到位,模板生成代码一片红看着都烦死。外加现在每次更新模板插件都需要重新打包,重新安装,重启IDE,非常麻烦,所以建议在编写复杂模板的同时最好开着一个Android项目用于测试模板代码,在项目环境下没问题了,再复制到模板中去。
另外一点是,不建议搞巨无霸模板,一般来说,一套模板插件针对一个项目,或者使用相同基础库逻辑类似的项目(比如一个项目组所负责的多个项目)。还是那句话,谁都不喜欢模板生成个代码一片红,如果要针对差异化很大的多个项目开发通用模板,那就有更多内容需要靠模板引导页面灵活配置,如果最后用模板生成个页面整的像填报表一样的,那估计这模板也没谁想用吧……