Android Studio 代码模板插件

布景

能够越过布景和简述,从模板插件实现开端看.

开发新页面时,原先需要写一堆模板代码。比方用Databinding写列表结构的页面,需要手写以下文件:

  • XxActivity.kt
  • XxFragment.kt
  • XxViewModel.kt
  • XxListAdapter.kt
  • XxListItemModel.kt(UI数据结构
  • XxBean.kt(接口数据结构)
  • XxBeanModelConvert.kt
  • XxRetrofitApi.kt
  • XxRetrofitRepository.kt
  • yymoudle_xx_layout_activity.xml
  • yymoudle_xx_layout_fragment.xml
  • yymoudle_xx_layout_list_item.xml(列表item)

并且类文件间还有彼此的引用关系。

假如能有一套代码模板,能够一键生成最小单元功能代码,确实能进步新开发页面的功率。(加快1~2个小时不为过吧?)

简述

Android Studio Editor

Android Studio 自带了两种代码模板(进口为Settings -> Edit):

  • File and Code Templates : 倾向生成单个文件
  • Live Templates :在单个文件中方便生成代码,例如logd生成Log.d(TAG, String)

File and Code Templates 和Live Templates

Android Studio 代码模板插件实现

Android Studio Plugin

所以把目光投向了代码模板插件,搜索template,能够看到许多,比方这个Jetpack Compose UI Architecture Plugin

Android Studio 代码模板插件实现

Android Studio 代码模板插件实现

如何我实现自己的代码模板插件?

模板插件实现

Android Studio 是依据 IntelliJ IDEA开发的,Android Studio能够使用IntelliJ上丰富的插件。 IntelliJ供给了一个用于创立模板代码插件的模板项目,依据这个模板项目改造 。

最终作用

挑选模板 -> 模板装备 -> 生成代码

挑选模板

Android Studio 代码模板插件实现
模板装备
Android Studio 代码模板插件实现
生成代码
Android Studio 代码模板插件实现

插件工程的创立与装备

工程创立

模板项目仓库地址: github.com/JetBrains/i…

依照步骤 Use this template-> Create a new repository 在自己的github下生成仓库。

这是我生成的项目仓库地址:github.com/AlvinScrp/a…

Android Studio 代码模板插件实现

修正装备

用Android Studio翻开这个项目,先修正一波基础装备,修正内容的commit: github.com/AlvinScrp/a…

留意:要导入Android代码模板需要的 wizard-template.jar, 其实是从Android Studio目录/plugins/android/lib中复制过来的。

模板插件代码编写

generator包下都是咱们新写的代码,代码调用顺序为

PluginGeneratorProvider.kt -> Generator.kt -> Recipe.kt

Android Studio 代码模板插件实现

PluginGeneratorProvider

对应【挑选模板】界面

package com.github.alvinscrp.androidcodetemplate.generator
import com.android.tools.idea.wizard.template.Template  
import com.android.tools.idea.wizard.template.WizardTemplateProvider  
import com.github.alvinscrp.androidcodetemplate.generator.mvvm.jlMvvmGenerator  
import com.github.alvinscrp.androidcodetemplate.generator.util.AppType  
class PluginGeneratorProvider : WizardTemplateProvider() {  
    override fun getTemplates(): List<Template> = listOf(  
        //这儿建了三套模板
        jlMvvmGenerator(AppType.FXJ),  
        jlMvvmGenerator(AppType.HYK),  
        jlMvvmGenerator(AppType.MC)  
    )  
}

Android Studio 代码模板插件实现

在plugin.xml中注册该provider

<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
<wizardTemplateProvider implementation="com.github.alvinscrp.androidcodetemplate.generator.PluginGeneratorProvider" />  
</extensions>

Generator.kt

package com.github.alvinscrp.androidcodetemplate.generator.mvvm
import com.android.tools.idea.wizard.template.*  
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API  
import com.github.alvinscrp.androidcodetemplate.generator.util.AppType  
/**  
* 模板装备需要的参数,依据你的需要,在这儿添加  
*/
fun jlMvvmGenerator(appType: AppType): Template {  
    return template {  
        name = "DataBinding Mvvm Temp Code - ${appType.key}"  
        description =  
    "生成一套依据DataBinding的MVVM代码,包括:Activity、Fragment、ViewModel、ListAdapter、 ListItemModel、BeanModelConvert、Bean、 Retrofit Api、 Repository"  
        minApi = MIN_API  
        category = Category.Other  
        formFactor = FormFactor.Mobile  
        screens = listOf(  
            WizardUiContext.ActivityGallery,  
            WizardUiContext.MenuEntry,  
            WizardUiContext.NewProject,  
            WizardUiContext.NewModule)  
        val bizNameParameter = stringParameter {  
            name = "Business Name:英文,小写最初,camel命名,能够多单词"  
            default = "template"  
            help = "业务名称:英文,能够多单词,camel命名,用来作为生成的各种文件的前缀"  
            constraints = listOf(Constraint.NONEMPTY)  
        }  
        val classPackageNameParameter = stringParameter {  
            name = "Class Package Name: 这个不要改它"  
            help = "文件名称:生成文件的存放位置,不是APP包名"  
            default = "com.github.alvinscrp"  
            constraints = listOf(Constraint.PACKAGE)  
            suggest = { packageName }  
        }  
        val isCreateActivityParameter = booleanParameter {  
            name = "生成Activity,需手动加入清单文件"  
            help = ""  
            default = false  
        }  
       widgets(  
            TextFieldWidget(bizNameParameter),  
            TextFieldWidget(classPackageNameParameter),    
            CheckBoxWidget(isCreateActivityParameter)  
        )  
        recipe = {  
            mvvmRecipe(  
                it as ModuleTemplateData,  
                bizNameParameter.value,  
                classPackageNameParameter.value,   
                appType,  
                isCreateActivityParameter.value  
            )  
        }  
    }  
}

对应【模板装备】界面

Android Studio 代码模板插件实现

Recipe.kt

package com.github.alvinscrp.androidcodetemplate.generator.mvvm
import com.android.tools.idea.wizard.template.ModuleTemplateData  
import ... 
/**  
* 模板代码文件的创立与保存  
* 这儿有几个变量需要留意下:  
* ```  
* //当时批量生成类文件地点目录 com.example.x.y  
* classPackageName : String  
*  
* //模块名,例如 user  
* val moduleName = moduleData.rootDir.name.toLowerCaseAsciiOnly()  
*  
* //模块包名,例如com.example.user , 在模块AndroidManifest.xml中装备的那个,必定要留意  
* val modulePackageName = projectData.applicationPackage  
* ```  
*/  
fun RecipeExecutor.mvvmRecipe(  
    moduleData: ModuleTemplateData,  
    bizName: String,  
    classPackageName: String,  
    appType: AppType,  
    isCreateActivity: Boolean  
) {  
    val (projectData, srcOut, resOut) = moduleData  
    val moduleName = moduleData.rootDir.name.toLowerCaseAsciiOnly()  
    val modulePackageName = projectData.applicationPackage ?: ""  
    // println("---->${projectData.rootDir},${projectData.applicationPackage},${moduleData.rootDir.name},${moduleData.packageName}")  
    if(isCreateActivity) {  
        save(  
            mvvmActivityTemp(appType, modulePackageName, classPackageName, moduleName, bizName),  
        srcOut.resolve("${bizName}/ui/${firstUppercase(bizName)}Activity.kt")  
        )
    //刺进Manifest ,这个代码运转报错,反正我也用不到,就不管了
    // generateManifest(  
        // moduleData = moduleData,  
        // activityClass = "${firstUppercase(bizName)}Activity",  
        // packageName = "${classPackageName}.${bizName}.ui",  
        // isLauncher = false,  
        // hasNoActionBar = false,  
        // isNewModule = false,  
        // isLibrary = false,  
        // generateActivityTitle = false  
    // )
    }  
    save(  
        mvvmFragmentTemp(appType, modulePackageName, classPackageName,moduleName, bizName),  
        srcOut.resolve("${bizName}/ui/${firstUppercase(bizName)}Fragment.kt")  
    )  
    ......代码较多,省略 
    save(  
        fragmentLayoutTemp(appType,classPackageName, bizName),  
        resOut.resolve("layout/${fragmentLayoutName(moduleName, bizName)}.xml")  
    )  
}

xxTemp.kt

每个temp function都对应一个目标代码文件。 咱们能够先在业务项目里,写一套可运转的Template代码。 插件能够依据这套Template代码修正。 以ActivityTemp.kt举例

package com.github.alvinscrp.androidcodetemplate.generator.mvvm.temp
import com.github.alvinscrp.androidcodetemplate.generator.util.AppType  
import com.github.alvinscrp.androidcodetemplate.generator.util.activityLayoutName  
import com.github.alvinscrp.androidcodetemplate.generator.util.firstUppercase  
import com.github.alvinscrp.androidcodetemplate.generator.util.fragmentClassName  
/**  
* 生成XxActivity文件的内容,你的项目里是啥,便是啥,不要用我这个模板  
*/  
fun mvvmActivityTemp(  
    appType: AppType,  
    modulePackageName: String,  
    classPackageName: String,  
    moduleName: String,  
    bizName: String  
): String {  
return """  
    package ${classPackageName}.${bizName}.ui  
    import android.os.Bundle  
    import ${appType.fullBaseActivity()}  
    import ${modulePackageName}.R  
    class ${firstUppercase(bizName)}Activity : ${appType.simpleBaseActivity()}() {  
        override fun onCreate(savedInstanceState: Bundle?) {  
            super.onCreate(savedInstanceState)  
            setContentView(R.layout.${activityLayoutName(moduleName, bizName)})  
            replaceFragment(R.id.fragment_container, ${fragmentClassName(bizName)}.newInstance(123), false)  
        }  
    }  
""".trimIndent()  
}

TemplUtils.kt

在编写Temp代码时,发现有些地方还是要留意的

  • class名:大写最初
  • layout.xml文件名:小写字母+下划线
  • xxDataBinding,通过layout.xml文件名来转化,更方便.

wizard-template.jar供给了许多的方法给咱们,例如:camelCaseToUnderlines()underscoreToLowerCamelCase()underscoreToCamelCase()等等

package com.github.alvinscrp.androidcodetemplate.generator.util
import com.android.tools.idea.wizard.template.camelCaseToUnderlines  
import com.android.tools.idea.wizard.template.underscoreToCamelCase  
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly  
/**  
* 方式 ab_cd_ef 有必要都是小写,以下划线连接  
*/  
fun layoutPrefix(moduleName: String, bizName: String): String {  
    return "${moduleName.toLowerCaseAsciiOnly()}_${camelCaseToUnderlines(bizName).toLowerCaseAsciiOnly()}_template"  
}     
fun fragmentLayoutName(moduleName: String, bizName: String): String {  
    return "${layoutPrefix(moduleName,bizName)}_fragment"  
}    
/**  
* moduleName 或许出现的方式 user、 User 、 UserCenter 、User_Center 、 UserCenter_kkk  
* 因为要作为布局文件名的前缀,有必要都转成小写  
*/  
fun fragmentDataBindingName(moduleName: String, bizName: String): String {  
    val layoutPrefix = layoutPrefix(moduleName, bizName)  
    //sd_te --> SdTe  
    var camelCaseName = underscoreToCamelCase(layoutPrefix)  
    return "${camelCaseName}FragmentBinding"  
}  
fun firstUppercase(param: String): String {  
    return param.replaceFirstChar { it.uppercase() }  
}

模板插件测验

代码写完,就能够测验插件作用了,AndroidStudio工具栏运转Run Plugin

Android Studio 代码模板插件实现
假如没有Run Plugin,需要新增装备下:

Android Studio 代码模板插件实现
假如能够正常编译,会主动翻开一个IntelliJ IDEA窗口。

此时,能够 New Project 或许 Open 现有APP项目。

最终,依照【挑选模板 -> 模板装备 -> 生成代码】的顺序,就能够生成代码了。

Android Studio 代码模板插件实现

模板插件导出与装置

通过上面的测验,你发现,写的插件很好用, “赋能”项目,如下步骤:

  • 导出插件jar:位置 build/libs/android-code-template-0.0.1.jar。

  • 导入到Android Studio的plugins:Settings -> Plugins -> Install Plugin from Disk…

Android Studio 代码模板插件实现