Android Studio 自定义模板

1. 简介

  • Jetbrains 提供了3种插件创建的方式
  • Using GitHub Template
  • Using Gradle
  • Using DevKit

本文选择第一种方案实现
代码地址:https://github.com/sxfxwlh/hw_as_plugins
AS插件开发文档:https://plugins.jetbrains.com/docs/intellij/android-studio.html

2. 模板项目搭建和环境配置

2.1 获取模板项目

首先来到JetBrains的GitHub仓库,根据readme.md引导进行操作,获取到官方提供的template项目。

2.2 在本地运行起来获取到的项目

2.2.1 用AS在本地获取并打开自己fork到的项目。

2.2.2 修改配置文件,添加依赖

  • 修改文件settings.gradle.kts中的变量rootProject.name = "自定义插件名字"
  • 修改文件gradle.properties中的变量
pluginGroup = com.hw
pluginName =  hw_child
pluginVersion = 1.0.0
platformPlugins = android
  • 修改src/main/resources/META_INF/plugin.xml文件,增加以下依赖
    <depends>org.jetbrains.android</depends>
    <depends>com.intellij.modules.androidstudio</depends>

  • 拷贝AndroidStudio安装目录下模板jar包到项目根目录下的lib下,jar包文件路径:/Applications/Android Studio.app/Contents/plugins/android/lib/wizard-template.jar, 同时添加依赖到build.gradle.kts文件最底部,Sync Now
dependencies {
    compileOnly(files("lib/wizard-template.jar"))
}

2.2.3 注册插件入口

src/main/kotlin下新建other包,新建kotlin-class WizardTemplateProviderImpl继承自WizardTemplateProvider,实现抽象方法如下,

   override fun getTemplates(): List<Template> = listOf(
        //自定义模板就添加在此处
    )
    

注册插件到plugin.xml中,如下

<!-- android-plugins.xml文档地址如下 -->
<!--    https://plugins.jetbrains.com/docs/intellij/extension-point-list.html#android-plugin-->
<!--    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template">-->
<!--        <wizardTemplateProvider implementation="com.android.tools.idea.wizard.template.impl.WizardTemplateProviderImpl" />-->
<!--    </extensions>-->
    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
        <wizardTemplateProvider implementation="WizardTemplateProviderImpl" />
    </extensions>

3. 模板基础文件编写

3.1 在src/main/kotlin目录下新建包层级结构如下

plugin-4.png

3.2 activity插件主要文件编写

3.2.1 在activity/res/layout目录下新建createActivityXml文件

package other.activity.res.layout

import java.lang.StringBuilder
/**

 * @Author hubert

 * @Date 2022/5/1 12:51 下午

 */

fun createActivityXml(
    packageName: String,
    activityClass: String
):String{
    val sb = StringBuilder()
    sb.append( """
        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout
            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"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context="${packageName}.${activityClass}Activity">
        """)
    if(hasNavigation){
        sb.append("""
            <com.necer.basic2.view.Navigation
           android:id="@+id/navigation"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>
        """)
    }    
    sb.append("""
        </LinearLayout>
    """)
    return sb.toString().trim()
}

3.2.2 在activity/src/app_package目录下新建createActivityKt文件

package other.activity.src.app_package

import java.lang.StringBuilder

/**

 * @Author hubert

 * @Date 2022/5/1 12:57 下午

 */

fun createActivityKt(
    applicationPackage: String?,
    packageName: String,
    activityClass: String,
    layoutName: String,
    hasNavigation: Boolean
):String{
    val sb = StringBuilder()
    sb.append("""
package $packageName

import android.os.Bundle
import ${applicationPackage}.R
import com.necer.basic2.ui.BaseActivity
import kotlinx.android.synthetic.main.${layoutName}.*

class ${activityClass}Activity : BaseActivity() {
   

    override fun getLayoutId()=R.layout.${layoutName}

    override fun onCreatee(savedInstanceState: Bundle?) {
      """)
    if(hasNavigation){
        sb.append("""
             navigation.title("").left(true)
        """)
    }

    sb.append("""
        }
         override fun getNetData() {
        
         }
    }
    """)
    return sb.toString()
}

3.2.3 在activity/src目录下新建createActivityRecipe文件

package other.activity.src

import com.android.tools.idea.wizard.template.ModuleTemplateData
import com.android.tools.idea.wizard.template.RecipeExecutor
import com.android.tools.idea.wizard.template.impl.activities.common.generateManifest
import other.activity.res.layout.createActivityXml
import other.activity.src.app_package.createActivityKt

/**

 * @Author hubert

 * @Date 2022/5/1 1:02 下午

 */

fun RecipeExecutor.createActivityRecipe(
    moduleData: ModuleTemplateData,
    packageName: String,
    activityClass: String,
    layoutName: String,
    hasNavigation:Boolean
) {
    val (projectData, srcOut, resOut) = moduleData
    val ktOrJavaExt = projectData.language.extension
//    generateManifest(moduleData: com.android.tools.idea.wizard.template.ModuleTemplateData, activityClass: kotlin.String, packageName: kotlin.String,
//    isLauncher: kotlin.Boolean,
//    hasNoActionBar: kotlin.Boolean,
//    activityThemeName: kotlin.String /* = compiled code */,
//    isNewModule: kotlin.Boolean /* = compiled code */,
//    isLibrary: kotlin.Boolean /* = compiled code */,
//    manifestOut: java.io.File /* = compiled code */,
//    baseFeatureResOut: java.io.File /* = compiled code */,
//    generateActivityTitle: kotlin.Boolean,
//    isResizeable: kotlin.Boolean /* = compiled code */): kotlin.Unit { /* compiled code */
//    }
    generateManifest(
        moduleData = moduleData,
        activityClass = "${activityClass}Activity",
        packageName = packageName,
        isLauncher = false,
        hasNoActionBar = false,
        isNewModule = false,
        isLibrary = false,
//        manifestOut = ,
//        baseFeatureResOut = ,
        generateActivityTitle = false,
        isResizeable = false,
    )

    val createActivity = createActivityKt(projectData.applicationPackage,packageName, activityClass, layoutName, hasNavigation)
    // 保存Activity
    save(createActivity, srcOut.resolve("${activityClass}Activity.${ktOrJavaExt}"))
    // 保存xml
    save(createActivityXml(packageName, activityClass,hasNavigation), resOut.resolve("layout/${layoutName}.xml"))


}

3.2.4 在activity/src目录下新建createActivityTemplate文件

package other.activity.src

import com.android.tools.idea.wizard.template.*
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API
import java.io.File

/**

 * @Author hubert

 * @Date 2022/5/1 1:42 下午

 */

val createActivityTemplate
    get() = template {
        name = "Child Activity"
        description = "继承自BaseActivity的Activity"
        minApi = MIN_API

        category = Category.Other
        formFactor = FormFactor.Mobile
        screens = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)

        val activityClass = stringParameter {
            name = "Activity Name"
            default = "Main"
            help = "只输入名字,不要包含Activity"
            constraints = listOf(Constraint.NONEMPTY)
        }

        val layoutName = stringParameter {
            name = "Layout Name"
            default = "activity_main"
            help = "请输入布局的名字"
            constraints = listOf(Constraint.LAYOUT,Constraint.UNIQUE,Constraint.NONEMPTY)
            suggest = { activityToLayout(activityClass.value.toLowerCase()) }
        }

        val hasNavigation = booleanParameter {
            name = "Has a Navigation"
            default = true
            help = "若勾选,自动添加导航栏"
        }

        val packageName =  stringParameter {
            name = "Package name"
            visible = { !isNewModule }
            default = "com.hw.lzjr"
            constraints = listOf(Constraint.PACKAGE)
            suggest = { packageName }
        }

        widgets(
            TextFieldWidget(activityClass),
            TextFieldWidget(layoutName),
            CheckBoxWidget(hasNavigation),
            PackageNameWidget(packageName)
        )

        recipe = { data:TemplateData ->
            createActivityRecipe(
                data as ModuleTemplateData,
                packageName.value,
                activityClass.value,
                layoutName.value,
                hasNavigation.value
            )
        }

        thumb { File("images/template_child_activity.png") }
    }

3.3 添加Activity模板WizardTemplateProviderImpl

class WizardTemplateProviderImpl: WizardTemplateProvider() {
    override fun getTemplates(): List<Template> = listOf(
        //自定义模板就添加在此处
        createActivityTemplate
    )
}

4 生成插件,安装使用

4.1 执行gradle任务获取插件

plugin-5.png

执行Run Plugin 后,在build/libs目录下即可获取生成的pluginhw-child-1.0.0.jar,如下图

plugin-6.png

4.2 安装插件

plugin-7.png

plugin-8.png

4.3 使用插件新建Activity

plugin-9.png

plugin-10.png

【注】 插件缩略图展示为no_activity.png 未按照配置展示,原因不明。

  • 联系方式:微信账号:sxfxwlh
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容