Android Studio 自定義模板

1. 簡(jiǎn)介

  • Jetbrains 提供了3種插件創(chuàng)建的方式
  • Using GitHub Template
  • Using Gradle
  • Using DevKit

本文選擇第一種方案實(shí)現(xiàn)
代碼地址:https://github.com/sxfxwlh/hw_as_plugins
AS插件開發(fā)文檔:https://plugins.jetbrains.com/docs/intellij/android-studio.html

2. 模板項(xiàng)目搭建和環(huán)境配置

2.1 獲取模板項(xiàng)目

首先來(lái)到JetBrains的GitHub倉(cāng)庫(kù),根據(jù)readme.md引導(dǎo)進(jìn)行操作,獲取到官方提供的template項(xiàng)目恬惯。

2.2 在本地運(yùn)行起來(lái)獲取到的項(xiàng)目

2.2.1 用AS在本地獲取并打開自己fork到的項(xiàng)目参咙。

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包到項(xiàng)目根目錄下的lib下麻顶,jar包文件路徑:/Applications/Android Studio.app/Contents/plugins/android/lib/wizard-template.jar, 同時(shí)添加依賴到build.gradle.kts文件最底部崖咨,Sync Now
dependencies {
    compileOnly(files("lib/wizard-template.jar"))
}

2.2.3 注冊(cè)插件入口

src/main/kotlin下新建other包,新建kotlin-class WizardTemplateProviderImpl繼承自WizardTemplateProvider,實(shí)現(xiàn)抽象方法如下击蹲,

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

注冊(cè)插件到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. 模板基礎(chǔ)文件編寫

3.1 在src/main/kotlin目錄下新建包層級(jí)結(jié)構(gòu)如下

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 = "請(qǐng)輸入布局的名字"
            constraints = listOf(Constraint.LAYOUT,Constraint.UNIQUE,Constraint.NONEMPTY)
            suggest = { activityToLayout(activityClass.value.toLowerCase()) }
        }

        val hasNavigation = booleanParameter {
            name = "Has a Navigation"
            default = true
            help = "若勾選,自動(dòng)添加導(dǎo)航欄"
        }

        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 執(zhí)行g(shù)radle任務(wù)獲取插件

plugin-5.png

執(zhí)行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 未按照配置展示萧锉,原因不明。

  • 聯(lián)系方式:微信賬號(hào):sxfxwlh
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叶洞,一起剝皮案震驚了整個(gè)濱河市禀崖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艺晴,老刑警劉巖掸屡,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狈究,居然都是意外死亡盏求,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門碎罚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人还蹲,你說(shuō)我怎么就攤上這事耙考√妒蓿” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵鞋邑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我枚碗,道長(zhǎng),這世上最難降的妖魔是什么遵堵? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任怨规,我火速辦了婚禮波丰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掰烟。我一直安慰自己,他們只是感情好霜瘪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布惧磺。 她就那樣靜靜地躺著,像睡著了一般缤底。 火紅的嫁衣襯著肌膚如雪番捂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天徙歼,我揣著相機(jī)與錄音鳖枕,去河邊找鬼。 笑死酿秸,一個(gè)胖子當(dāng)著我的面吹牛魏烫,可吹牛的內(nèi)容都是我干的肝箱。 我是一名探鬼主播稀蟋,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼骏融!你這毒婦竟也來(lái)了井辜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窃肠,失蹤者是張志新(化名)和其女友劉穎刷允,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纤怒,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡天通,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年像寒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺祸。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筷笨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胃夏,到底是詐尸還是另有隱情,我是刑警寧澤侮叮,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布悼瘾,位于F島的核電站,受9級(jí)特大地震影響卸勺,放射性物質(zhì)發(fā)生泄漏烫扼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一悟狱、第九天 我趴在偏房一處隱蔽的房頂上張望堰氓。 院中可真熱鬧,春花似錦双絮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至榛泛,卻和暖如春胚委,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亩冬。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工硅急, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撒顿。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓荚板,卻偏偏與公主長(zhǎng)得像吩屹,于是被迫代替她去往敵國(guó)和親拧抖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容