开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情
大家平时开发使用的框架是自己搭建的吗?可能对于大公司而言,都有专业负责管理框架的大神,根本轮不到自己出场。但对于一些中小型公司来说,往往都是自己孤身奋战,独立开发,这时都是使用自己搭建的框架,时间充裕的时候还会制造一些有意思的插件,以提升平时的开发效率。今天咱们就来说说关于Android端页面级模板插件的自定义。
时间回退三四年,那时还主流MVP模式,当时项目使用的便是组长封装的 MVPArms 框架,自开始使用便根据文档安装了与之配套的页面级(MVPArmsTemplate)和模板级(MVPArms-Module-Template)插件,文档写的非常仔细,一看就会,平时开发起来相当节约时间,一个页面、一个模块只需一键即可生成。奈何AndroidStudio几乎每次更新,内部的模板就会更改,所以自定义的模板也就面临更改,由于之前框架一直再维护,所以模块也会跟着维护。
如今,MVP已不再是主流,MVPArms内使用的技术也已过时,所以这个框架注定被淘汰。相信大家都已经跟随谷歌爸爸的脚步基于Jetpack自己搭建了MVVM或者最新的MVI模式框架吧。不得不说,现在的框架和以前的框架相比,确实优化了很多不足,使用起来也变得简单,主要还是看自己的代码风格和喜好。
有了自己的框架,不自定义一个模板总感觉少了点什么,所以我们就来讲讲AndroidStudio如何自定义模板吧:
大概是在AS 4.1版本开始,以往的自定义模板方法已经不可用了,就连对应的templates文件夹都不存在。最后才发现,只能使用官方提供的intellij-platform-plugin-template 库进行插件自定义,生成Jar包供AS安装。但其实内部的样板代码是差不多的,主要就是配置环境和生成Jar包容易出现问题。
创建完成后,使用AS打开,注意这里还需要依赖一个Jar包,可以理解为AS的基础模板Jar包,位置在AS安装目录下/Applications/Android/Studio.app/Contents/plugins/android/lib/目录下。
在build.gradle.kts中添加依赖
dependencies {
compileOnly(files("lib/wizard-template.jar"))
}
在gradle.properties中修改对应信息
pluginGroup = com.enample.mvvm.mvvmtemplatenew //插件路径
pluginName_ = MVVMTemplateNew //插件名
pluginVersion = 1.0.4 //插件版本
platformVersion = 2021.1.3 //ide版本
platformPlugins = com.intellij.java, org.jetbrains.kotlin //编译语言
listeners目录下新建类
internal class MyProjectManagerListener : ProjectManagerListener {
override fun projectOpened(project: Project) {
project.service<MyProjectService>()
}
}
在settings.properties中修改模板名
rootProject.name = "MVVMTemplateNew"
基础目录就是这样:
内部修改的代码其实都是照猫画虎,咱们就看看mvvmRecipe和mvvmTemplate两个工具类的部分方法:
val MVVMTemplate
get() = template {
name = "MVVM Template"
description = "一键创建 MVVM 单个页面所需要的全部组件"
minApi = 19
category = Category.Other
formFactor = FormFactor.Mobile
screens = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)
val pageName = stringParameter {
name = "Page Name"
default = "Main"
help = "请填写页面名,如填写 Main,会自动生成 MainActivity, MainViewModel 等文件"
constraints = listOf(Constraint.NONEMPTY, Constraint.UNIQUE)
}
val packageName = stringParameter {
name = "Root Package Name"
default = "com.mycompany.myapp"
constraints = listOf(Constraint.PACKAGE)
help = "请填写你的项目包名,请认真核实此包名是否是正确的项目包名,不能包含子包,正确的格式如:com.exanmple.app"
}
//是否需要生成Activity
val needActivity = booleanParameter {
name = "Generate Activity"
default = true
help = "是否需要生成 Activity ? 不勾选则不生成"
}
//布局名
val activityLayoutName = stringParameter {
name = "Activity Layout Name"
default = "activity_main"
visible = { needActivity.value }
help = "Activity 创建之前需要填写 Activity 的布局名,若布局已创建就直接填写此布局名,若还没创建此布局,请勾选下面的单选框"
constraints = listOf(Constraint.LAYOUT, Constraint.NONEMPTY)
suggest = { "${activityToLayout(pageName.value.upperCase())}" }
}
//是否需要Activity的布局
val generateActivityLayout = booleanParameter {
name = "Generate Activity Layout"
default = true
visible = { needActivity.value }
help = "是否需要给 Activity 生成布局? 若勾选,则使用上面的布局名给此 Activity 创建默认的布局"
}
val activityPackageName = stringParameter {
name = "Activity Package Name"
default = "Activity Package Name"
visible = { needActivity.value }
help = "Activity 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
constraints = listOf(Constraint.PACKAGE)
suggest = { "${packageName.value}.${pageName.value.toLowerCase()}" }
}
//Fragment
//是否需要生成Fragment
val needFragment = booleanParameter {
name = "Generate Fragment"
default = false
help = "是否需要生成 Fragment ? 不勾选则不生成"
}
//布局名
val fragmentLayoutName = stringParameter {
name = "Fragment Layout Name"
default = "fragment_main"
visible = { needFragment.value }
help = "Fragment 创建之前需要填写 Fragment 的布局名,若布局已创建就直接填写此布局名,若还没创建此布局,请勾选下面的单选框"
constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
suggest = { "${fragmentToLayout(pageName.value.upperCase())}" }
}
//是否需要Fragment的布局
val generateFragmentLayout = booleanParameter {
name = "Generate Fragment Layout"
default = true
visible = { needFragment.value }
help = "是否需要给 Fragment 生成布局? 若勾选,则使用上面的布局名给此 Fragment 创建默认的布局"
}
val fragmentPackageName = stringParameter {
name = "Fragment Package Name"
default = "function Package Name"
constraints = listOf(Constraint.PACKAGE)
visible = { needFragment.value }
help = "Fragment 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
suggest = {"${packageName.value}.${pageName.value.toLowerCase()}"}
}
val needRepository = booleanParameter {
name = "Generate Repository"
default = true
help = "是否需要生成 Repository ? 不勾选则不生成"
}
val repositoryPackageName = stringParameter {
name = "Repository Package Name"
default = "Repository Package Name"
constraints = listOf(Constraint.PACKAGE)
visible = { needRepository.value }
help = "Repository 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
suggest = {"${packageName.value}.${pageName.value.toLowerCase()}"}
}
val needViewModel = booleanParameter {
name = "Generate ViewModel"
default = true
help = "是否需要生成 ViewModel ? 不勾选则不生成"
}
val viewModelPackageName = stringParameter {
name = "ViewModel Package Name"
default = "ViewModel Package Name"
constraints = listOf(Constraint.PACKAGE)
visible = { needViewModel.value }
help = "ViewModel 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
suggest = {"${packageName.value}.${pageName.value.toLowerCase()}"}
}
widgets(
TextFieldWidget(pageName),
PackageNameWidget(packageName),
CheckBoxWidget(needActivity),
TextFieldWidget(activityLayoutName),
CheckBoxWidget(generateActivityLayout),
TextFieldWidget(activityPackageName),
CheckBoxWidget(needFragment),
TextFieldWidget(fragmentLayoutName),
CheckBoxWidget(generateFragmentLayout),
TextFieldWidget(fragmentPackageName),
CheckBoxWidget(needRepository),
TextFieldWidget(repositoryPackageName),
CheckBoxWidget(needViewModel),
TextFieldWidget(viewModelPackageName),
LanguageWidget()
)
thumb { File("template_blank_activity.png") }
recipe = { data: TemplateData ->
mvvmRecipe(
data as ModuleTemplateData,
pageName.value,
packageName.value,
needActivity.value,
activityLayoutName.value,
generateActivityLayout.value,
activityPackageName.value,
needFragment.value,
fragmentLayoutName.value,
generateFragmentLayout.value,
fragmentPackageName.value,
needRepository.value,
needViewModel.value,
repositoryPackageName.value,
viewModelPackageName.value
)
}
}
也就是创建时需要填写的一些页面信息,需要的就勾上,不需要的则不勾选
在mvvmRecipe中根据勾选的文件执行相应操作
if (needActivity) {
mergeXml(
manifestTemplateXml(packageRealName, activityPackageName, "${pageName}Activity"),
manifestOut.resolve("AndroidManifest.xml")
)
}
if (needActivity) {
save(
mvvmActivityKt(
packageRealName,
pageName,
activityPackageName,
activityLayoutName,
needViewModel
), srcOut.resolve("${pageName.toLowerCase()}/${pageName}Activity.${ktOrJavaExt}")
)
}
这里还差一步,把刚刚mvvmTemplate中的MVVMTemplate配置到SamplePluginTemplateProviderImpl中
class SamplePluginTemplateProviderImpl : WizardTemplateProvider(){
override fun getTemplates(): List<Template> = listOf(
// activity的模板
MVVMTemplate
)
}
到此就可以点击AS中的Run Plugin进行编译,这里有个奇怪的现象是AS会下载配置中对应的IntelLiJ IDEA,下载完后会自动启动。此时在IntelLiJ IDEA编译成功无误后,两个编译器都可点击Run Plugin生成Jar包,运行成功后会在其根目录下/bulid/libs/中查看到对应Jar包。
这个时候拷贝出这个Jar包,接下来就是Android开发熟悉的环节了,按下图安装完成重启AS
此时的你就可以原地起飞,创建一个页面只需一键式操作,大大节约开发时间。模板内容可以根据自己喜好添加,AS更新需要慎重,有可能更新后插件无法使用,但根据报错信息和AS自带模板对比修改问题应该不大,所以如非必要尽量不要更新AS。
总结
整体实现下来也不是很难,但很容易出现编译失败等问题,还有就是插件安装后无法一键创建,或者创建出来的文件和预期不符的情况。很正常,这都是一个必经之路,仔细查看报错原因和代码,再根据官方文档(英文哦)提示,基本都能克服。无非就是一些版本原因导致,但插件成功了,在今后的开发中,尤其是初期开发,效率是大大提升,何乐而不为呢?
好了,这篇文章就讲解到这里,希望对大家有所帮助。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情