开启生长之旅!这是我参加「日新方案 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包容易出现问题。

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

创立完成后,运用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"

基础目录便是这样:

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

内部修正的代码其实都是照猫画虎,我们就看看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
            )
        }
    }

也便是创立时需求填写的一些页面信息,需求的就勾上,不需求的则不勾选

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

在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包。

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

这个时分复制出这个Jar包,接下来便是Android开发了解的环节了,按下图装置完成重启AS

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

Android:基于intellij-platform-plugin-template实现自定义页面模板插件

此刻的你就能够原地起飞,创立一个页面只需一键式操作,大大节省开发时刻。模板内容能够依据自己喜爱增加,AS更新需求慎重,有或许更新后插件无法运用,但依据报错信息和AS自带模板比照修正问题应该不大,所以如非必要尽量不要更新AS。

总结

整体完成下来也不是很难,但很容易出现编译失利等问题,还有便是插件装置后无法一键创立,或许创立出来的文件和预期不符的状况。很正常,这都是一个必经之路,仔细检查报错原因和代码,再依据官方文档(英文哦)提示,根本都能克服。无非便是一些版别原因导致,但插件成功了,在今后的开发中,尤其是初期开发,效率是大大提高,何乐而不为呢?

好了,这篇文章就讲解到这儿,希望对我们有所帮助。

开启生长之旅!这是我参加「日新方案 2 月更文应战」的第 21 天,点击检查活动概况