Android Studio 代码模板插件实现

news2024/11/28 12:39:44

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
截屏2023-07-26 16.46.37.png

Android Studio Plugin

于是把目光投向了代码模板插件,搜索template,可以看到很多,比如这个Jetpack Compose UI Architecture Plugin

截屏2023-07-26 16.57.03.png

image.png

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

模板插件实现

Android Studio 是基于 IntelliJ IDEA开发的,Android Studio可以使用IntelliJ上丰富的插件。
IntelliJ提供了一个用于创建模板代码插件的模板项目,基于这个模板项目改造 。

最终效果

选择模板 -> 模板配置 -> 生成代码

选择模板
截屏2023-07-26 17.16.29.png
模板配置
截屏2023-07-26 17.18.19.png
生成代码
企业微信截图_86bca621-e6b9-4b7f-bd09-500cdf024a60.png

插件工程的创建与配置

工程创建

模板项目仓库地址:
https://github.com/JetBrains/intellij-platform-plugin-template

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

这是我生成的项目仓库地址:https://github.com/AlvinScrp/android-code-template

截屏2023-07-26 17.06.30.png

修改配置

用Android Studio打开这个项目,先修改一波基础配置,修改内容的commit:
https://github.com/AlvinScrp/android-code-template/commit/c0eaaa9c7a5451f29efb1b5f91eec131568d5f89

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

模板插件代码编写

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

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

企业微信截图_e0135067-0241-4a7e-ab7f-35643ec9d7ae.png

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)  
    )  
}

截屏2023-07-26 18.00.39.png

在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  
            )  
        }  
    }  
}

对应【模板配置】界面
截屏2023-07-26 17.18.19.png

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

截屏2023-07-26 18.59.58.png

如果可以正常编译,会自动打开一个IntelliJ IDEA窗口。

此时,可以 New Project 或者 Open 现有APP项目。

最后,按照【选择模板 -> 模板配置 -> 生成代码】的顺序,就可以生成代码了。

截屏2023-07-26 19.03.24.png

模板插件导出与安装

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

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

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

截屏2023-07-26 19.14.41.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/793371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始学习CTF

前言 CTF简介 中文一般译作夺旗赛&#xff0c;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式 CTF起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式 竞赛模式 解题模式&#xff1a; 在解题模式…

如何引导客户进行自助服务,提高员工工作效率?

搭建帮助中心是大多数企业都在尝试做的事情&#xff0c;它的重要性对于企业来说不言而喻。现在对于企业来说&#xff0c;搭建帮助中心或许不是什么难事&#xff0c;但是关于帮助中心&#xff0c;有几个问题需要思考清楚&#xff0c;才能让其发挥最大的价值。 一、如何让用户养成…

Android手机app页面布局方法

app页面布局方法 1. FrameLayout&#xff08;帧布局&#xff09; (1) FrameLayout是最简单的布局了。所有放在布局里的控件&#xff0c;都按照层次堆叠在屏幕的左上角。后加进来的控件覆盖前面的控件。 2. LinearLayout&#xff08;线性布局&#xff09; (1) LinearLayout按…

第九章 多组学简介

第一节 什么是多组学 多组学encode计划是一个大型国际合作计划&#xff0c;旨在研究各种生物体系中的功能元件和基因组功能。该计划的目标是建立一个全面的生物信息学数据库&#xff0c;包括人类和其他生物的基因组和表观基因组、转录组、蛋白质组、代谢组和表型组等。 多组学…

高等数学❤️第一章~第三节~极限❤️间断点及其分类

【精讲】高等数学中的间断点及其分类 博主&#xff1a;命运之光的主页 专栏&#xff1a;高等数学 目录 【精讲】高等数学中的间断点及其分类 导言 一、间断点的概念 二、间断点的分类 必需记忆知识点 知识点1 知识点2 例题&#xff08;用于熟悉高等数学中的间断点及其…

dp算法篇Day11

“哎呀&#xff0c;哎呀&#xff0c;流云开一朵&#xff0c;哟诶嘿哟&#xff0c;哟诶嘿哟~” 51、目标和 (1) 题目解析 包括之后的一些题目&#xff0c;乍一眼看可能你不会发现它与dp问题有何相连&#xff0c;可是按照按照题目又难以想出 思路、写出代码来。因此&#xff0c;…

PostgreSQL——编码“GBK“的字符0x0xa8 0x27在编码“UTF8“没有相对应值`

问题&#xff1a;编码"GBK"的字符0x0xa8 0x27在编码"UTF8"没有相对应值 原因&#xff1a;客户端编码与服务端编码不一致 select name,setting,context from pg_settings where name like %encoding%; 解决方案&#xff1a;修改客户端编码方式和服务端一致…

Jmeter接口测试工具的一些使用小技巧

如何使用英文界面的JMeter Jmeter启动时会自动判断操作系统的locale 并选择合适的语言启动&#xff0c;所以&#xff0c;我们启动jmeter后&#xff0c;其会出现一个倍感亲切的中文界面。但由于jmeter本身的汉化工作做得不好&#xff0c;你会看到有未被汉化的选项及元件的参数。…

脸书营销,跨境电商不能忽视的营销新趋势

Facebook营销就是在Facebook上通过有针对性的广告、商业品牌群组等地方推广您的业务。随着社交媒体继续成为我们日常生活中不可或缺的一部分&#xff0c;Facebook产品在社交商务方面的扩展使该平台成为吸引新客户的重要渠道。 您应该注意到&#xff0c;很多企业都通过Facebook…

3个能免费使用的AI绘画软件,效果精致

通过AI绘画软件&#xff0c;设计小白也能轻松创作出精美的图画创作。本文将为大家介绍3款能免费使用的AI绘画软件&#xff0c;它们能帮助设计小白或者经验丰富的设计师快速设计出精美的图画作品&#xff0c;一起来看看吧&#xff01; 1、即时灵感 即时灵感是国产的AI绘画软件…

【复习45-51题】【每天40分钟,我们一起用50天刷完 (剑指Offer)】第三十七天 37/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

VRRP技术和浮动路由(第二十六课)

VRRP技术和浮动路由(第二十六课) 一、浮动路由 1、浮动路由概述 1&#xff09;浮动路由是什么 -浮动路由又称为路由备份&#xff0c;由两条或多条链路组成浮动路由 -浮动路由指配置两条静态路由&#xff0c;这两条静态路由的目的地址相同&#xff0c;但是下一跳地址不同两…

CPM-Bee-5B微调实记

CPM-Bee-5B微调实记 1. 准备工作 &#xff08;1&#xff09;用PyCharm打开CPM-Bee文件夹。 &#xff08;2&#xff09;设置成自己的环境&#xff1a;nlp &#xff08;3&#xff09;将src文件夹在终端打开 &#xff08;4&#xff09;激活自己的环境&#xff1a;nlp 2. 准备…

web自动化测试进阶篇05 ——— 界面交互场景测试

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

数据结构day6(2023.7.20)

一、Xmind整理&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;个栈的入栈次序ABCDE&#xff0c;则栈的不可能的输出序列是&#xff08;D&#xff09; A.ABCDE B.DECBA C.EDCBA D.DCEAB 栈的特点是先进后出&#xff0c;后进先出&#xf…

Electron 系统通知 Notification 实践指南

系统通知是桌面应用的常见功能&#xff0c;用于给用户发送提醒&#xff08;刷下存在感 &#x1f642;&#xff09;&#xff0c;还能帮定点击事件以便后续的操作。 Electron 自带通知模块&#xff0c;下方代码是一个简单的示例 const { Notification } require(electron)cons…

Postman学习之常用断言

什么是断言&#xff1f; 断言——就是结果中的特定属性或值与预期做对比&#xff0c;如果一致&#xff0c;则用例通过&#xff0c;如果不一致&#xff0c;断言失败&#xff0c;用例失败。断言&#xff0c;是一个完整测试用例所不可或缺的一部分&#xff0c;没有断言的测试用例…

SZ:zip/内部函数外部函数/VGG模型/nn

zip&#xff1a; -r recursion-d delete-m move (move隐藏的意思是&#xff0c;原文件会消失&#xff09;想增加文件&#xff0c;不需要加参数什么参数也没有。如果zip压缩文件不存在&#xff0c;执行以上命令将会创建一个新的zip文件并将指定的文件添加进去。如果zip压缩文件…

SpringBoot+Jpa+Thymeleaf实现增删改查

SpringBootJpaThymeleaf实现增删改查 这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例。 1、pom依赖 pom 包里面添加Jpa 和 Thymeleaf 的相关包引用 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.…

网络层IP协议的基本原理 数据链路层ARP协议 域名解析以及一些重要技术

目录 1 网络层IP协议协议头格式网段划分DHCPCIDR&#xff1a;基于子网掩码的划分方式特殊的IP号IP地址的数量限制私有IP地址和公网IP地址路由路由表 2 数据链路层 — 局域网的转发问题以太网认识以太网以太网帧格式局域网通信原理 MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对…