Skip to content

DL-ZhangTeng/BaseLibraryTemplatePlugin

Repository files navigation

BaseLibraryTemplatePlugin

AndroidStudio 4.X+ 编写自定义模板

  • AndroidStudio 自定义模板主要区分在AS 4.0版本
    • AndroidStudio 4.0版本之前,编写自定义模板 使用FreeMarket进行编写,并存放在 AS 的plugin - template 文件夹下即可。可参考 鸿洋 文章 AndroidStudio自定义模板 进行学习。
    • AndroidStudio 4.0版本之后,AS 没有了 plugin 文件夹,但提供了新的方式:使用Kotlin 编写 template,以jar包形式使用。

开始


Use this template

我们需要在官方的template模板上进行编写,官方模板地址为:intellij-platform-plugin-template,打开模板仓库后,点击Use this template 会提示你 Create a new repository from intellij-platform-plugin-template 类似fork,创建好仓库后,git clone 到本地,并使用AS 打开

添加依赖

  • 添加wizard-template.jar
    • 项目根目录下创建lib文件夹
    • 添加AndroidStudio目录下的wizard-template.jar,具体路径为:/Android Studio/plugins/android/lib/
    • 打开项目build.gradle.kts文件,添加代码如下:
dependencies {
    compileOnly(files("lib/wizard-template.jar"))
}
  • 添加plugins
    • 打开项目build.gradle.kts文件,在plugins下添加detektktlint插件
    • 添加detekt依赖代码如下:
    plugins {
    ...
    // detekt linter - read more: https://detekt.github.io/detekt/gradle.html
    id("io.gitlab.arturbosch.detekt") version "1.16.0"
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
    id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
}


dependencies {
    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.18.1")
}
  • 添加过依赖后build.gradle.ktspluginsdependencies代码:
plugins {
    // Java support
    id("java")
    // Kotlin support
    id("org.jetbrains.kotlin.jvm") version "1.7.10"
    // Gradle IntelliJ Plugin
    id("org.jetbrains.intellij") version "1.8.0"
    // Gradle Changelog Plugin
    id("org.jetbrains.changelog") version "1.3.1"
    // Gradle Qodana Plugin
    id("org.jetbrains.qodana") version "0.1.13"
    // detekt linter - read more: https://detekt.github.io/detekt/gradle.html
    id("io.gitlab.arturbosch.detekt") version "1.16.0"
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
    id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
}


dependencies {
    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.18.1")
    compileOnly(files("lib/wizard-template.jar"))
}

修改gradle.properties

  • 修改gradle.properties文件下相关配置 具体意义查看Gradle properties
    • pluginGroup
    • pluginName
    • pluginVersion : 编译后生成jar版本号就是这里控制的
    • pluginSinceBuild
    • pluginUntilBuild
    • pluginVerifierIdeVersions
    • platformType
    • platformVersion
    • platformPlugins
  • 此处需注意的是 pluginSinceBuild pluginUntilBuild pluginVerifierIdeVersions
    • pluginSinceBuild:表示插件适配的最低版本
    • pluginUntilBuild:表示插件适配的最高版本
    • pluginVerifierIdeVersions: 4.X版本AS 使用模板会有此字段,Fox版本没有,文档也没有显示,个人还是添加上了
    • 关于最低 最高版本 就是AS 版本信息中build 信息,本人使用的是AndroidStudio Fox 2020.3.1 Patch 3 Build,build 信息为203.xxxx

picture1

  • gradle.properties文件内容如下:
# IntelliJ Platform Artifacts Repositories
# -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html

pluginGroup = com.github.dlzhangteng.baselibrarytemplateplugin
pluginName = BaseLibraryTemplatePlugin
# SemVer format -> https://semver.org
pluginVersion = 2.0.1

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
pluginSinceBuild = 213
pluginUntilBuild = 222.*

# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
platformType = IC
platformVersion = 2021.3.3

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins =

# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
javaVersion = 11

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 7.3

# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
# suppress inspection "UnusedProperty"
kotlin.stdlib.default.dependency = false

修改包名以及创建Template生成类

  • 修改项目包名
    • AS 打开项目后,可以看到src - kotlin - 包名 - listeners services ...
    • 修改包名,个人修改结果如下
    • 具体为src - kotlin - 包名 com.github.dlzhangteng.baselibrarytemplateplugin - 三个文件夹,两个存放 项目原本文件 listeners services,一个存放自定义模板代码 template

picture2

  • 修改MyProjectManagerListener
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
    }
}
  • 创建Template入口生成类 SamplePluginTemplateProviderImpl
    • template文件夹下创建 SamplePluginTemplateProviderImpl
    • WizardTemplateProvider的是 lib 下 wizard-template.jar的抽象类
    • baseActivityTemplate.kt为具体生成Template对象
class SamplePluginTemplateProviderImpl : WizardTemplateProvider() {
    override fun getTemplates(): List<Template> = listOf(
        baseActivityTemplate
    )
}
  • 修改plugin.xml
    • 创建好SamplePluginTemplateProviderImpl类后,打开resources-META-INF-plugin.xml文件
    • 修改id
    • 修改name:此处名字是AS plugin安装后显示的名字
    • 修改vendor:此处为AS plugin 安装后显示的作者名字
    • 添加三个 depends
    • 添加extensions 指定defaultExtensionNscom.android.tools.idea.wizard.templatewizardTemplateProvider 为创建的SamplePluginTemplateProviderImpl
    • 此处注意,一定要写全路径,包名一定一定一定要写上,4.X版本编写的时候 网上的文章都没有写,个人不清楚4.X版本是否真的不用写全路径
<idea-plugin>
    <id>com.github.dlzhangteng.baselibrarytemplateplugin</id>
    <name>BaseLibraryTemplatePlugin</name>
    <vendor>dl-zhangteng</vendor>

    <depends>org.jetbrains.android</depends>
    <depends>org.jetbrains.kotlin</depends>
    <depends>com.intellij.modules.platform</depends>

    <extensions defaultExtensionNs="com.intellij">
        <applicationService
            serviceImplementation="com.github.dlzhangteng.baselibrarytemplateplugin.services.MyApplicationService" />
        <projectService
            serviceImplementation="com.github.dlzhangteng.baselibrarytemplateplugin.services.MyProjectService" />
    </extensions>

    <applicationListeners>
        <listener
            class="com.github.dlzhangteng.baselibrarytemplateplugin.listeners.MyProjectManagerListener"
            topic="com.intellij.openapi.project.ProjectManagerListener" />
    </applicationListeners>
    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
        <wizardTemplateProvider
            implementation="com.github.dlzhangteng.baselibrarytemplateplugin.template.SamplePluginTemplateProviderImpl" />
    </extensions>
</idea-plugin>
  • 创建Template具体对象baseActivityTemplate
    • template 文件夹下创建了activity/base文件夹,并在其下创建名为baseActivityTemplate的kt文件
    • 字段含义
      • revision :低版本的时候用于说明版本,此次fox编译的时候 revision会报错,遂注释
      • name : 编译好Jar包使用时,显示的模板名字
      • description : 使用模板时,顶部简介
      • minApi : 低版本4.0以下编写的时候 使用的min_api minBuildApi,用于说明编译版本,此次fox编写时,minBuildApi会报错,遂也改minApi ,MIN_API为lib 下 wizard-template.jar中字段
      • category : 定义为other
      • formFactor : 定义为 mobile
      • screens : 枚举WizardUiContext,表示模板在何处显示;NewProject:新建项目时网格显示Template按钮,NewModule:新建Module时网格显示Template按钮,ActivityGallery:新建Activity时网格显示Template按钮,FragmentGallery:新建Fragment时网格显示Template按钮,MenuEntry:右键新建Activity菜单中显示Template按钮。
      • name:界面上的类似label的提示语,constraints:填写值的约束,suggest:建议值,比如填写ActivityName的时候,会给出一个布局文件的建议值,default:默认值,help:底部显示的提示语
      • widgets:将编写的Template对象放入。
      • recipe :设置具体替换对象。此处在activity文件夹下创建baseActivityRecipe的kt文件,将需要的值通过参数传入

val baseActivityTemplate
    get() = template {
//        revision = 1
        name = "ZTBaseActivity"
        description = "一键创建 BaseActivity "
        minApi = MIN_API
        category = Category.Activity
        formFactor = FormFactor.Mobile
        screens = listOf(
            WizardUiContext.ActivityGallery,
            WizardUiContext.MenuEntry,
            WizardUiContext.NewProject,
            WizardUiContext.NewModule
        )

        val mActivityPackageName = stringParameter {
            name = "Root Package Name"
            constraints = listOf(Constraint.PACKAGE, Constraint.NONEMPTY)
            default = "com.zhangteng.baselibrary"
            visible = { !isNewModule }
            help = "Activity 包名"
        }

        val mPageName = stringParameter {
            name = "Create Page Name"
            constraints = listOf(Constraint.UNIQUE, Constraint.NONEMPTY)
            default = "Main"
            help = "需要生成页面的名字,不需要再写 名字后缀:如Activity、Fragment,会自动生成,以及对应文件名后缀"
        }

        val mIsGenerateActivityLayout = booleanParameter {
            name = "Generate Activity Layout"
            default = true
            help = "默认勾选,如果使用已存在布局 则无需勾选,若不勾选,创建后记得修改Act或 Fragment 绑定的视图文件!!!"
        }

        val mActivityLayoutName = stringParameter {
            name = "Activity Layout Name"
            default = "activity_main"
            visible = { mIsGenerateActivityLayout.value }
            constraints = listOf(Constraint.LAYOUT, Constraint.NONEMPTY)
            suggest = { "activity${getLayoutName(mPageName.value)}" }
        }

        thumb { File("template_empty_activity.png") }

        widgets(
            TextFieldWidget(mPageName),
            TextFieldWidget(mActivityLayoutName),
            CheckBoxWidget(mIsGenerateActivityLayout),
            PackageNameWidget(mActivityPackageName),
        )

        recipe = { data: TemplateData ->
            baseActivityRecipe(
                data as ModuleTemplateData,
                mPageName.value,
                mActivityLayoutName.value,
                mIsGenerateActivityLayout.value,
                mActivityPackageName.value
            )
        }

    }

/**
 * 获取layoutName
 */
fun getLayoutName(pageName: String): String {
    val stringBuilder: StringBuilder = StringBuilder()
    val activityChildNames: ArrayList<String> =
        splitByUpperCase(
            pageName
        )
    activityChildNames.forEach {
        stringBuilder.append("_").append(it.toLowerCase())
    }
    return stringBuilder.toString()
}

/**
 * 根据大写字母拆分数组
 */
fun splitByUpperCase(str: String): ArrayList<String> {
    val rs = ArrayList<String>()
    var index = 0
    val len = str.length
    for (i in 1 until len) {
        if (Character.isUpperCase(str[i])) {
            rs.add(str.substring(index, i))
            index = i
        }
    }
    rs.add(str.substring(index, len))
    return rs
}

baseActivityRecipe.kt :

fun RecipeExecutor.baseActivityRecipe(
    moduleTemplateData: ModuleTemplateData,
    mPageName: String,
    mActivityLayoutName: String,
    mIsGenerateActivityLayout: Boolean,
    mActivityPackageName: String,
) {
    addActivityToManifest(
        this,
        moduleTemplateData,
        "${packageNameStr}.${mPageName}Activity".substring(1)
    )
    val packageNameStr =
        if (moduleTemplateData.projectTemplateData.applicationPackage == null) ""
        else mActivityPackageName
            .replace(moduleTemplateData.projectTemplateData.applicationPackage.toString(), "")
    val rootPath =
        if (packageNameStr.isNotEmpty()) moduleTemplateData.projectTemplateData.applicationPackage.toString()
        else mActivityPackageName
    val baseActivity = baseActivity(
        rootPath,
        packageNameStr,
        mPageName,
        mActivityLayoutName
    )
    // 保存Activity

    save(
        baseActivity,
        moduleTemplateData.srcDir.resolve("${mPageName}Activity.kt")
    )
    if (mIsGenerateActivityLayout) {
        // 保存xml
        save(baseXml(), moduleTemplateData.resDir.resolve("layout/${mActivityLayoutName}.xml"))
    }
}

baseActivity.kt:

fun baseActivity(
    mRootPackageName: String?,
    mActivityPackageName: String,
    mPageName: String,
    mActivityLayoutName: String
) = """
package ${mRootPackageName}${mActivityPackageName.ifEmpty { "" }}

import android.os.Bundle

import com.zhangteng.base.base.BaseActivity
import ${mRootPackageName}.R

class ${mPageName}Activity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.${mActivityLayoutName})
})
    }

    override fun initView() {

    }

    override fun initData() {

    }
}
"""

编译

编写好后 点击 Gradle - Task - Build - jar,编译好后 会在 项目根目录-build-libs文件夹下生成jar包,名字为 项目名-版本号.jar

可能你的Gradle 页面是这样的:

picture3

点击Task list not built..

去掉Gradle 下第一个 Do not build Gradle task list ..的勾选

拿着编译好的jar包,打开AS Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... > RESTART IDE

鸣谢

JiaYang627 : TinMVVM

鸿洋 : Android Studio自定义模板 写页面竟然可以如此轻松

Build Version Downloads

Template ToDo list

This Fancy IntelliJ Platform Plugin is going to be your implementation of the brilliant ideas that you have.

This specific section is a source for the plugin.xml file which will be extracted by the Gradle during the build process.

To keep everything working, do not remove <!-- ... --> sections.

Installation

  • Using IDE built-in plugin system:

    Settings/Preferences > Plugins > Marketplace > Search for "BaseLibraryTemplatePlugin" > Install Plugin

  • Manually:

    Download the latest release and install it manually using Settings/Preferences > Plugins > ⚙️ > Install plugin from disk...


Plugin based on the IntelliJ Platform Plugin Template.

About

为BaseLibrary提供的插件

Resources

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages