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。

pic13.png

重啓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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章