Android Studio 4.1中的模板插件

Android Studio 4.1中的模板插件

来自:http://tommwq.tech/blog/2021/02/28/312

原文:https://steewsc.medium.com/template-plugin-for-android-studio-4-1-92dcbc689d39

作者:Stevica Trajanovic

pic1.png

如果你在开发一个新项目,或者你想将旧项目迁移到新架构,你应该考虑建立一个模板,好省去编写样板代码的工作,把时间用到其他地方。

直到最近之前,要建立模板,只需要进入$ANDROID_STUDIO/plugins/android/lib/templates/文件夹寻找示例。但从Android Studio 4.1开始,这种方法不再奏效。就在我编写完模板,并想要升级Android Studio的时候,我才意识到这一点。

当然也有好的一面。现在你可以用Kotlin替代FTL,同时,模板也被JetBrains IntelliJ平台插件所替代。

首先打开 https://github.com/JetBrains/intellij-platform-plugin-template ,按照README的说明操作。(脱水版:点击“Use this template”绿色按钮)

pic2.png

按照向导指示执行完毕,你会得到一个代码仓库,用来保存插件代码。现在你需要通过克隆或下载来得到代码。

pic3.png

接着用Android Studio打开插件代码。现在可以做一些调整,让插件代码适配你的Android Studio版本。

请记住:我在这里使用某些类和包的名字只是作为参考,你可以放心的修改。

1 重新组织包

对于这个例子,我将自动生成的类移动到了我的基础包com.github.steewsc.mvisetup。

pic4.png

2 gradle.properties

对于gradle.properties文件,你要设置

  • pluginName。设置为mvi-setup。
  • pluginGroup。设置为com.github.steewsc.mvisetup。

pic5.png

pic6.png

3 plugin.xml

接下来在src/main/resources/META-INF目录下打开plugin.xml文件并设置:

  • id。设置为com.github.steewsc.mvisetup(即gradle.properties中的pluginGroup)。
  • name。设置为mvi-setup(即gradle.properties中的pluginName)。
  • vendor。设置为steewsc。

pic7.png

然后增加3个依赖项(依赖项com.intellij.modules.platform应该已经自动添加)

  • org.jetbrains.android
  • org.jetbrains.kotlin
  • com.intellij.modules.java

pic8.png

在所有部分中,将基础包名称设置为com.github.steewsc.mvisetup。

pic9.png

4 settings.gradle.kts

设置rootProject.name为mvi-setup(或你的插件的名字)。

执行gradle同步,这样我们就完成设置部分,可以开始编码。

为了让模板在菜单中可见,我们必须:

  • 从WizardTemplateProvider派生类
  • Template
  • Recipe
  • 模板文件

这里有些类似于我们在Android Studio 4.1之前需要做的工作,除了现在使用的是Kotlin。实际上我使用了旧模板(基于FTL)作为新插件的源代码。

我在编写插件时遇到一个问题。因为我在RecipeExecutor中使用了Project实例,不得不使用一些不好的编码技巧(我想尽快完成工作),直到我找到更合适的办法。

更新MyProjectManagerListener.kt来存储Project对象:

package com.github.steewsc.mvisetup.listeners

import com.intellij.openapi.project.Project

import com.intellij.openapi.project.ProjectManagerListener

import com.github.steewsc.mvisetup.services.MyProjectService

class MyProjectManagerListener : ProjectManagerListener {

override fun projectOpened(project: Project) {

projectInstance = project

project.getService(MyProjectService::class.java)

}

override fun projectClosing(project: Project) {

projectInstance = <strong>null</strong>

super.projectClosing(project)

}

companion object {

var projectInstance: Project? = <strong>null</strong>

}

}

这里我忽略Java版本。在配置完成后,你可以很容易的用Java编写插件。

5 模板文件

 ActivityAndLayout.kt

import com.android.tools.idea.wizard.template.ProjectTemplateData

import com.android.tools.idea.wizard.template.extractLetters

fun someActivity(

packageName: String,

entityName: String,

layoutName: String,

projectData: ProjectTemplateData

) = """

package $packageName

import androidx.appcompat.app.AppCompatActivity

import android.os.Bundle

import ${projectData.applicationPackage}.R;

class ${entityName}sActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.${extractLetters(layoutName.toLowerCase())})

}

}

"""

fun someActivityLayout(

packageName: String,

entityName: String) = """

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http:<em>//</em><em>schemas.android.com/apk/res/android"</em>

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}.${entityName}sActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

"""

recipe.kt

package other.mviSetup

import com.android.tools.idea.wizard.template.ModuleTemplateData

import com.android.tools.idea.wizard.template.RecipeExecutor

import com.android.tools.idea.wizard.template.activityToLayout

import com.android.tools.idea.wizard.template.extractLetters

import com.android.tools.idea.wizard.template.impl.activities.common.addAllKotlinDependencies

import com.github.steewsc.mvisetup.listeners.MyProjectManagerListener.Companion.projectInstance

import com.intellij.openapi.roots.ProjectRootManager

import com.intellij.psi.PsiDirectory

import com.intellij.psi.PsiFileFactory

import com.intellij.psi.PsiManager

import someActivity

import someActivityLayout

fun RecipeExecutor.mviSetup(

moduleData: ModuleTemplateData,

packageName: String,

entityName: String,

layoutName: String

) {

val (projectData) = moduleData

val project = projectInstance ?: return

addAllKotlinDependencies(moduleData)

val virtualFiles = ProjectRootManager.getInstance(project).contentSourceRoots

val virtSrc = virtualFiles.first { it.path.contains("src") }

val virtRes = virtualFiles.first { it.path.contains("res") }

val directorySrc = PsiManager.getInstance(project).findDirectory(virtSrc)!!

val directoryRes = PsiManager.getInstance(project).findDirectory(virtRes)!!

someActivity(packageName, entityName, layoutName, projectData)

.save(directorySrc, packageName, "${entityName}sActivity.kt")

someActivityLayout(packageName, entityName)

.save(directoryRes, "layout", "${layoutName}.xml")

}

fun String.save(srcDir: PsiDirectory, subDirPath: String, fileName: String) {

try {

val destDir = subDirPath.split(".").toDir(srcDir)

val psiFile = PsiFileFactory

.getInstance(srcDir.project)

.createFileFromText(fileName, KotlinLanguage.INSTANCE, this)

destDir.add(psiFile)

}catch (exc: Exception) {

exc.printStackTrace()

}

}

fun List<String>.toDir(srcDir: PsiDirectory): PsiDirectory {

var result = srcDir

forEach {

result = result.findSubdirectory(it) ?: result.createSubdirectory(it)

}

return result

}

Template.kt

package other.mviSetup

import com.android.tools.idea.wizard.template.*

import java.io.File

import mviSetup

val mviSetupTemplate

get() = template {

revision = 2

name = "MY Setup with Activity"

description = "Creates a new activity along layout file."

minApi = 16

minBuildApi = 16

category = Category.Other <em>// </em><em>Check other categories</em>

formFactor = FormFactor.Mobile

screens = listOf(WizardUiContext.FragmentGallery, WizardUiContext.MenuEntry,

WizardUiContext.NewProject, WizardUiContext.NewModule)

val packageNameParam = defaultPackageNameParameter

val entityName = stringParameter {

name = "Entity Name"

default = "Wurst"

help = "The name of the entity class to create and use in Activity"

constraints = listOf(Constraint.NONEMPTY)

}

val layoutName = stringParameter {

name = "Layout Name"

default = "my_act"

help = "The name of the layout to create for the activity"

constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)

suggest = { "${activityToLayout(entityName.value.toLowerCase())}s" }

}

widgets(

TextFieldWidget(entityName),

TextFieldWidget(layoutName),

PackageNameWidget(packageNameParam)

)

recipe = { data: TemplateData ->

mviSetup(

data as ModuleTemplateData,

packageNameParam.value,

entityName.value,

layoutName.value

)

}

}

val defaultPackageNameParameter get() = stringParameter {

name = "Package name"

visible = { !isNewModule }

default = "com.mycompany.myapp"

constraints = listOf(Constraint.PACKAGE)

suggest = { packageName }

}

WizardTemplateProviderImpl.kt

package other

import com.android.tools.idea.wizard.template.Template

import com.android.tools.idea.wizard.template.WizardTemplateProvider

import other.mviSetup.mviSetupTemplate

class WizardTemplateProviderImpl : WizardTemplateProvider() {

override fun getTemplates(): List<Template> = listOf(mviSetupTemplate)

}

现在回到plugin.xml,添加你的导模板。

<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">

<wizardTemplateProvider implementation="other.WizardTemplateProviderImpl" />

</extensions>

pic10.png

打开Gradle标签,运行buildPlugin。

pic11.png

如果一切正常,你将会看到插件已经被保存到YOUR_PROJECT_DIR\build\libs\my-setup-0.1.0.jar。

pic12.png

你可以将插件拖拽到Android Studio中,或者通过菜单Settings->Plugins->Install Plugin from Disk来安装插件jar。

重启IDE,尝试运行插件。右键点击项目中的某个包,在弹出菜单中依次选择New->Other->MY Setup with Activity,顺利的话你会看到向导屏幕。当你点击Next/Finish按钮,插件生成文件。

这只是一个基本Activity创建模板,你可以将它作为MVI或其他模式的基础样板,并让重构旧项目轻而易举。

pic14.png

Figure 14: 屏幕1:在新建菜单中点击MVI Setup with Fragment后

pic15.png

Figure 15: 屏幕2:“MVI Setup with Fragment”向导生成的文件(红色部分)

如果你遇到任何问题,你可以在idea.log文件中看到堆栈跟踪(Android Studio->Help->Show log In Explorer/Finder..)。

如果你在新建菜单中看不到Activity/Fragment和其他常见的子菜单,只需卸载插件(Settings->Plugins->YourPlugin->Uninstall),这些子菜单会在IDE重启后出现。检查idea.log中的错误,修复插件源代码,增加插件版本,构建并重新安装插件。

示例代码在https://github.com/steewsc/template

猜你喜欢

转载自blog.csdn.net/tq1086/article/details/114222246