教会你IDEA插件开发 - 高级篇

280 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

前面我们讲过了IDEA开发的基础篇和进阶篇,现在我们来看看高级篇。

进度条的使用

ProgressManager.getInstance().run(object : Task.Backgroundable(project, title) {
    override fun run(indicator: ProgressIndicator) {
        // 这里用indicator显示任务完成的进度
        // indicator.text = "进度显示的文本"
        // indicator.fraction = 0.5 进度的百分比
    }
}

有了进度条,我们就再也不用担心进度完成情况了。

action图标配置

<action icon="AllIcons.Actions.Compile"></action>

图标方便我们找到这个action。sdk内置了很多图标,我们可以就地取材。

action快捷键配置

<action>
<keyboard-shortcut
        keymap="$default"
        first-keystroke="control alt D"
        second-keystroke="E"/>
</action>

用来配置第一快捷键和第二快捷键。

action按钮位置配置

<action>
<add-to-group group-id="BuildMenu" anchor="last"/>
</action>

用来配置action按钮的位置。group-id表示我们要添加到哪个大的分组,而anchor代表是添加到这个分组的最前面还是最后面。

使用Android Studio的模板生成sdk

好戏现在开始了。我们不仅可以完成从零开发一个action,还可以基于Android Studio的组件模板sdk更方便地开发Activity和Fragment等组件的生成器。我们要先在Android Studio的安装目录找到wizard-template.jar。巧妇难为无米之炊,没有sdk怎么行?导入sdk,开始写代码。

image.png

首先,你需要定义一个WizardTemplateProvider,来作为模块生成的入口。这个类相当于一个模板清单,表示你开发了哪些模板生成功能。

class DoraTemplateWizardProvider: WizardTemplateProvider() {
    override fun getTemplates() = listOf(MVVMActivityTemplate, MVVMFragmentTemplate)
}

找到resources/META-INF/plugins.xml加上以下代码。

<extensions defaultExtensionNs="com.android">
    <tools.idea.wizard.template.wizardTemplateProvider
        implementation="com.yourdomain.templates.recipes.DoraTemplateWizardProvider"/>
</extensions>

然后,也看模板相关信息的类。

object MVVMActivityTemplate : Template {
    override val category: Category
        get() = Category.Activity
    override val constraints: Collection<TemplateConstraint>
        get() = emptyList()     //AndroidX, kotlin
    override val description: String
        get() = "创建一个dora.MVVMActivity,来自https://github.com/dora4/dora"
    override val documentationUrl: String?
        get() = null
    override val formFactor: FormFactor
        get() = FormFactor.Mobile
    override val minCompileSdk: Int
        get() = MIN_API
    override val minSdk: Int
        get() = MIN_API
    override val name: String
        get() = "MVVM Activity"
    override val recipe: Recipe
        get() = {
            mvvmActivityRecipe(
                    it as ModuleTemplateData,
                    activityClassInputParameter.value,
                    activityTitleInputParameter.value,
                    layoutNameInputParameter.value,
                    packageName.value
            )
        }
    override val revision: Int
        get() = 1
    override val uiContexts: Collection<WizardUiContext>
        get() = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)
    override val widgets: Collection<Widget<*>>
        get() = listOf(
                TextFieldWidget(activityTitleInputParameter),
                TextFieldWidget(activityClassInputParameter),
                TextFieldWidget(layoutNameInputParameter),
                PackageNameWidget(packageName),
                LanguageWidget()
        )

    override fun thumb(): Thumb {
        return Thumb { findResource(this.javaClass, File("template_mvvm_activity.png")) }
    }

    val activityClassInputParameter = stringParameter {
        name = "Activity Name"
        default = "MainActivity"
        help = "The name of the activity class to create"
        constraints = listOf(Constraint.CLASS, Constraint.UNIQUE, Constraint.NONEMPTY)
        suggest = { layoutToActivity(layoutNameInputParameter.value) }
    }

    var layoutNameInputParameter: StringParameter = stringParameter {
        name = "Layout Name"
        default = "activity_main"
        help = "The name of the layout to create for the activity"
        constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
        suggest = { activityToLayout(activityClassInputParameter.value) }
    }

    val activityTitleInputParameter = stringParameter {
        name = "Title"
        default = "Main"
        help = "The name of the activity. For launcher activities, the application title"
        visible = { false }
        constraints = listOf(Constraint.NONEMPTY)
        suggest = { buildClassNameWithoutSuffix(activityClassInputParameter.value, "Activity") }
    }
    val packageName = defaultPackageNameParameter
}

category:该模板放在哪个分类下面,比如activity则放在创建activity的菜单里面 constraint:需要依赖的Android特性 description:描述信息 documentationUrl:文档url formFactor:表单因子,mobile代表手机 minCompileSdk:最低编译sdk minSdk:最低sdk name:action中显示的名称 recipe:秘诀,就是我们要生成模板的过程

接下来,我们可以定义模板生成的过程,主要定义要生成哪些文件。

fun RecipeExecutor.mvvmActivityRecipe(
        moduleData: ModuleTemplateData,
        activityClass: String,
        activityTitle: String,
        layoutName: String,
        packageName: String
) {
    val (projectData, srcOut, resOut) = moduleData


    generateManifest(
            moduleData = moduleData,
            activityClass = activityClass,
            activityTitle = activityTitle,
            packageName = packageName,
            isLauncher = false,
            hasNoActionBar = false,
            generateActivityTitle = false,

    )

    if (projectData.language.equals(Language.Kotlin)) {
        save(mvvmActivityKt(projectData.applicationPackage ?: packageName, packageName, activityClass,
                buildBindingName(layoutName), layoutName), srcOut.resolve("${activityClass}.${projectData.language.extension}"))
    }
    if (projectData.language.equals(Language.Java)) {
        save(mvvmActivity(projectData.applicationPackage ?: packageName, packageName, activityClass,
                buildBindingName(layoutName), layoutName), srcOut.resolve("${activityClass}.${projectData.language.extension}"))
    }
    save(mvvmActivityXml(packageName, activityClass), resOut.resolve("layout/${layoutName}.xml"))

    open(resOut.resolve("layout/${layoutName}.xml"))

}

fun RecipeExecutor.mvvmFragmentRecipe(
        moduleData: ModuleTemplateData,
        fragmentClass: String,
        layoutName: String,
        packageName: String
) {
    val (projectData, srcOut, resOut) = moduleData
    if (projectData.language.equals(Language.Kotlin)) {
        save(mvvmFragmentKt(projectData.applicationPackage ?: packageName, packageName, fragmentClass,
                buildBindingName(layoutName), layoutName), srcOut.resolve("${fragmentClass}.${projectData.language.extension}"))
    }
    if (projectData.language.equals(Language.Java)) {
        save(mvvmFragment(projectData.applicationPackage ?: packageName, packageName, fragmentClass,
                buildBindingName(layoutName), layoutName), srcOut.resolve("${fragmentClass}.${projectData.language.extension}"))
    }
    save(mvvmFragmentXml(packageName, fragmentClass), resOut.resolve("layout/${layoutName}.xml"))

    open(resOut.resolve("layout/${layoutName}.xml"))

}

val defaultPackageNameParameter
    get() = stringParameter {
        name = "Package Name"
        default = "com.mycompany.myapp"
        constraints = listOf(Constraint.UNIQUE, Constraint.NONEMPTY)
        suggest = { packageName }
    }

/**
 * 将类名去掉最后一个类型的单词。
 */
fun buildClassNameWithoutSuffix(className: String, classNameSuffix: String): String {
    if (className.endsWith(classNameSuffix)) {
        return className.subSequence(0, className.length - classNameSuffix.length).toString()
    }
    return className
}

fun buildBindingName(layoutName: String) : String {
    val builder = StringBuilder()
    if (layoutName.contains("_")) {
        for (section in layoutName.split("_")) {
            builder.append(section[0].toUpperCase()).append(section.subSequence(1, section.length))
        }
    }
    return builder.append("Binding").toString()
}

save:保存文件,将文件写入要生成的目录 open:打开文件,生成后是否打开预览

最后,写我们要生成的模板内容的代码,以Activity为例。这个就要用到kotlin的字符串模板特性了,对""" """不熟的同学可以复习下kotlin基础知识哦。

activity类生成

fun mvvmActivityKt(
        applicationPackage: String,
  packageName: String,
        activityClass: String,
  bindingName: String,
  layoutName: String,
) = """
package ${packageName}

import android.os.Bundle

import dora.BaseActivity

import ${applicationPackage}.R
import ${applicationPackage}.databinding.${bindingName}

class ${activityClass} : BaseActivity<${bindingName}>() {

   override fun getLayoutId(): Int {
          return R.layout.${layoutName}
   }

   override fun initData(savedInstanceState: Bundle?) {
          TODO("Not yet implemented")
   }
}
"""

fun mvvmActivity(
        applicationPackage: String,
        packageName: String,
        activityClass: String,
        bindingName: String,
        layoutName: String,
) = """
package ${packageName};

import android.os.Bundle;

import dora.BaseActivity;

import ${applicationPackage}.R;
import ${applicationPackage}.databinding.${bindingName};

public class ${activityClass} extends BaseActivity<${bindingName}> {

   @Override
    protected int getLayoutId() {
        return R.layout.${layoutName};
    }

   @Override
    public void initData(Bundle savedInstanceState) {
        // TODO: Not yet implemented
   }
}
"""

res文件生成

fun mvvmActivityXml(
        packageName: String,
  activityClass: String
) = """
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="${packageName}.${activityClass}">

    <data>
    
    </data>
    
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    </FrameLayout>

</layout>
"""