Android Studio自定義模板

Android Studio自定義模板 寫頁面竟然可以如此輕松

1头朱、概述

上一篇文章斩芭,已經(jīng)初步對Android Studio的模板有了初步的介紹及使用,以及一些開源模板的推薦:

神奇的Android Studio Template

本文將對如何編寫Template,進(jìn)行詳細(xì)的介紹(以activity模板為例)

2、模板的文件結(jié)構(gòu)

學(xué)習(xí)編寫模板最好的方式呢只锻,就是參考IDE中已經(jīng)提供的最簡單的模板,那么在Android Studio中最簡單的activity模板就是:Empty Activity
了紫谷,我們打開該模板文件齐饮,首先對文件結(jié)構(gòu)有個(gè)直觀的了解捐寥,如圖:

Empty Activity 文件結(jié)構(gòu)

可以看到每個(gè)插件對應(yīng)一個(gè)文件夾,文件夾包含:

  • template.xml
  • recipe.xml.ftl
  • globals.xml.ftl
  • root
  • 效果縮略圖

下面我們逐一對上述每個(gè)文件的作用進(jìn)行介紹

template.xml

首先看源碼

<?xml version="1.0"?>
<template
    format="5"
    revision="5"
    name="Empty Activity"
    minApi="7"
    minBuildApi="14"
    description="Creates a new empty activity">

    <category value="Activity" />
    <formfactor value="Mobile" />

    <parameter
        id="activityClass"
        name="Activity Name"
        type="string"
        constraints="class|unique|nonempty"
        suggest="${layoutToActivity(layoutName)}"
        default="MainActivity"
        help="The name of the activity class to create" />

    <!-- 省略N個(gè) parameter 標(biāo)簽-->

    <!-- 128x128 thumbnails relative to template.xml -->
    <thumbs>
        <!-- default thumbnail is required -->
        <thumb>template_blank_activity.png</thumb>
    </thumbs>

    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />

</template>

其中

  • <template> 標(biāo)簽的name屬性祖驱,對應(yīng)新建Activity時(shí)顯示的名字
  • <category> 對應(yīng)New的類的類別為Activity

剩下的握恳,對應(yīng)我們Android Studio新建Empty Activity的界面就很好理解了,如圖:

Configure Activity

看到這個(gè)界面大部分屬性都出來了捺僻,我們重點(diǎn)看parameter乡洼,界面上每個(gè)框出來的部分對應(yīng)一個(gè)parameter部分屬性介紹:

  • id:唯一標(biāo)識,最終通過該屬性的值匕坯,獲取用戶輸入的值(文本框內(nèi)容 || 是否選中)
  • name:界面上類似Label的提示語
  • type:輸入值類型
  • constraints:填寫值的約束
  • suggest:建議值束昵,比如填寫ActivityName的時(shí)候,會給出一個(gè)布局文件的建議值葛峻。
  • default:默認(rèn)值
  • help:底部顯示的提示語

這個(gè)部分對應(yīng)界面還是非常好理解的妻怎,大家可以簡單的修改一些字符串,或者添加一個(gè)<parameter>泞歉,重啟AS逼侦,看看效果。

template.xml的最下面的部分引入了globals.xml.ftl和recipe.xml.ftl腰耙。
這兩個(gè)我們會詳細(xì)介紹榛丢。

globals.xml.ftl

<?xml version="1.0"?>
<globals>
    <global id="hasNoActionBar" type="boolean" value="false" />
    <global id="parentActivityClass" value="" />
    <global id="simpleLayoutName" value="${layoutName}" />
    <global id="excludeMenu" type="boolean" value="true" />
    <global id="generateActivityTitle" type="boolean" value="false" />
    <#include "../common/common_globals.xml.ftl" />
</globals>

通過名稱可以猜出它是用于定義一些全局的變量,可以看到其內(nèi)部有<global>標(biāo)簽分別定義id挺庞,type晰赞,value。
同理选侨,我們可以通過id訪問到該值掖鱼,例如:
${hasNoactionBar} 的值為false

recipe.xml.ftl

<!-- recipe.xml.ftl -->
<?xml version="1.0"?>
<recipe>
    <#include "../common/recipe_manifest.xml.ftl" />

<#if generateLayout>
    <#include "../common/recipe_simple.xml.ftl" />
    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</#if>

    <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</recipe>
<!-- recipe_manifest.xml.ftl -->
<recipe folder="root://activities/common">

    <merge from="root/AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
    <merge from="root/res/values/manifest_strings.xml.ftl"
             to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

</recipe>

為了介紹,我們將幾個(gè)重要標(biāo)簽都列出來

  • include 此文件包含其它文件內(nèi)容援制,和xml布局include作用一致
  • merge 合并的意思戏挡,比如將我們使用到的strings.xml合并到我們的項(xiàng)目的stirngs.xml中
  • open 在代碼生成后,打開指定的文件晨仑,比如我們新建一個(gè)Activity后褐墅,默認(rèn)就會將該Activity打開。
  • instantiate 實(shí)例化洪己,生成相應(yīng)文件妥凳。可以看到上例試將ftl->java文件的答捕,也就是說中間會通過一個(gè)步驟逝钥,將ftl中的變量都換成對應(yīng)的值,那么完整的流程是ftl->freemarker process -> java

在介紹instantiate時(shí)拱镐,涉及到了freemarker艘款,不可避免的需要對它進(jìn)行簡單的介紹持际。
目前我們已經(jīng)基本了解了一個(gè)模板其內(nèi)部的文件結(jié)構(gòu)了,以及每個(gè)文件大致包含的東西磷箕,我們簡單做個(gè)總結(jié):

  • template 中parameter標(biāo)簽选酗,主要用于提供參數(shù)
  • global.xml.ftl 主要用于提供參數(shù)
  • recipe.xml.ftl 主要用于生成我們實(shí)際需要的代碼佛致,資源文件等饺律;例如,利用參數(shù)+MainActivity.java.ftl -> MainActivity.java妓蛮;其實(shí)就是利用參數(shù)將ftl中的變量進(jìn)行替換空繁。

那么整體的關(guān)系類似下圖:


Template Variable Dataflow

3殿衰、簡單的freemarker語法

上面我們已經(jīng)基本了解模板生成的大致的流程以及涉及到的文件,大致了解了我們生成的源碼或者xml文件盛泡,需要經(jīng)過:
ftl -> freemarker process -> java/xml
這樣的流程闷祥,那么我們必須對freemarker有個(gè)簡單的了解。

  • 一個(gè)簡單的例子
比如我們有個(gè)變量user=art
有個(gè)ftl文件內(nèi)容:helloL${user}
最后經(jīng)過freemarker的輸出結(jié)果即為 hello:art
  • if語法
<# if generateLayout>
    //生成Layout文件
</#if>

看一眼就知道大概的意思了~有一定的編程經(jīng)驗(yàn)傲诵,即使不知道這個(gè)東西叫freemarker凯砍,對于這些簡單的語法還是能看懂的。

我們最后以Empty Activity模板中的SimpleActivit為例:

// root\src\app_package\SimpleActivity.java.ftl
package ${packageName};

import ${superClassFqcn};
import android.os.Bundle;

public class ${activityClass} extends ${superClass} {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
<#if generateLayout>
        setContentView(R.layout.${layoutName});
</#if>
    }
}

可以看到其內(nèi)部包含很多變量拴竹,這些變量的值一般來源于用戶的輸入和global.xml.ftl中預(yù)定義的值悟衩,經(jīng)過recipe.xml.ftl中instantiate標(biāo)簽的處理,將變量換成實(shí)際的值栓拜,即可在我們的項(xiàng)目的指定位置座泳,得到我們期望的Activity。

流程大致可用下圖說明:

圖片來源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

看到這幕与,最起碼理解了挑势,當(dāng)我們能選擇創(chuàng)建不同的Activity類型,最終得到的不同的效果其中的原理原來在這啦鸣。

4潮饱、具體的模板實(shí)例

了解了基本的理論之后,下面我們可以通過一個(gè)實(shí)例來將上面的知識點(diǎn)整合赏陵。

我們編寫了一個(gè)Activity模板叫做:Splash Activity饼齿,用于創(chuàng)建一個(gè)全屏自動(dòng)finish的activity,效果如下:

Paste_Image.png

當(dāng)我們點(diǎn)擊New | Activity | Fragment Activity 就可以完成上面的Activity的創(chuàng)建蝙搔,而避免了編寫布局文件,引入design庫以及一些簡單的編碼考传。

是不是感覺還是不錯(cuò)的吃型,大家可以針對自己的需求,按照規(guī)范的格式歲月指定模板僚楞。

建議大家copy一個(gè)現(xiàn)有的模板勤晚,再其基礎(chǔ)上修改即可枉层,比如本例是在Empty Activity基礎(chǔ)上修改的。

下面我們看上栗的具體的實(shí)現(xiàn)赐写。

4.1 template.xml的編寫

通過上面的學(xué)習(xí)我們知道template.xml中可以定義我們創(chuàng)建面板的控件布局等鸟蜡,本例我們創(chuàng)建Activity的界面如下:

對應(yīng)的template.xml如下:

<?xml version="1.0"?>
<template
    format="5"
    revision="5"
    name="Splash Activity"
    requireAppTheme="true"
    minApi="7"
    minBuildApi="14"
    description="Creates a new Splash activity">

    <category value="Activity" />
    <formfactor value="Mobile" />

    <parameter
        id="activityClass"
        name="Activity Name"
        type="string"
        constraints="class|unique|nonempty"
        default="SplashActivity"
        help="The name of the activity class to create" />

    <parameter
        id="layoutName"
        name="Layout Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${activityToLayout(activityClass)}"
        default="activity_splash"
        help="The name of the layout to create for the activity" />

    <parameter
        id="isLauncher"
        name="Launcher Activity"
        type="boolean"
        default="false"
        help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />

    <parameter
        id="packageName"
        name="Package name"
        type="string"
        constraints="package"
        default="com.mycompany.myapp" />

    <!-- 128x128 thumbnails relative to template.xml -->
    <thumbs>
        <!-- default thumbnail is required -->
        <thumb>template_splash_activity.png</thumb>
    </thumbs>

    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />

</template>

PS:注意Activity Name那里變化

經(jīng)過前面的學(xué)習(xí)應(yīng)該很好理解,每個(gè)parameter對應(yīng)界面上的一個(gè)控件挺邀,控件的這個(gè)id最終可以得到用戶輸入值揉忘,后面會用于渲染ftl文件

4.2、用到的類

本例中最終要生成Activityu端铛,也就是說對應(yīng)會有一個(gè)ftl文件用于最終生成這個(gè)類泣矛。

// root\src\app_package\SplashActivity.java.ftl
package ${packageName};

import ${superClassFqcn};
import android.os.Bundle;
import android.os.Handler;
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>

public class ${activityClass} extends ${superClass} {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.${layoutName});
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        delayedHide(2000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHideHandler.removeCallbacks(mHideRunnable);
    }

    private void hide() {
        finish();
    }

    private final Handler mHideHandler = new Handler();
    private final Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            hide();
        }
    };

    /**
     * Schedules a call to hide() in [delay] milliseconds, canceling any
     * previously scheduled calls.
     */
    private void delayedHide(int delayMillis) {
        mHideHandler.removeCallbacks(mHideRunnable);
        mHideHandler.postDelayed(mHideRunnable, delayMillis);
    }

    @Override
    public void finish() {
        super.finish();
        mHideHandler.removeCallbacks(mHideRunnable);
        overridePendingTransition(0, 0);
    }
}

注意不是.java文件而是.ftl文件,可以看到上面的代碼基礎(chǔ)上和Java代碼沒什么區(qū)別禾蚕,實(shí)際上就是Java代碼您朽,把可變的部分編程了 ${變量名}的方式而已。
例如:類名是用戶填寫的换淆,我們就使用${activityClass}替代哗总,其它同理。

4.3倍试、 用到的布局文件

//root\res\layout\activity_splash.xml.ftl
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context="${relativePackage}.${activityClass}">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/ic_launcher"/>

</FrameLayout>

4.4讯屈、用到的AndroidManifest.xml.ftl

//root\AndroidManifest.xml.ftl
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

    <application>
        <activity android:name="${relativePackage}.${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            <#else>
            android:label="@string/title_${simpleName}"
            </#if>
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:theme="@style/SplashTheme">

            <#if isLauncher && !(isLibraryProject!false)>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            </#if>
        </activity>
    </application>

</manifest>

4.5、用到的values

//root\res\values\styles.xml.ftl
<resources>

   <style name="SplashTheme" parent="${themeName}">
        <!-- 隱藏狀態(tài)欄 -->
        <<item name="android:windowFullscreen">true</item>
        <!-- 隱藏標(biāo)題欄 -->
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@null</item>
    </style>

</resources>
// root\res\values\strings.xml.ftl
<resources>

    <#if !isNewProject>
    <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
    </#if>

</resources>

發(fā)現(xiàn)和我們真正編寫的Activity并無多大區(qū)別易猫。

看完用到的類和布局文件的ftl耻煤,大家心里應(yīng)該有個(gè)底了,這模板幾乎就和我們平時(shí)寫的java類一樣准颓,只是根據(jù)用戶據(jù)在新建Activity界面所輸入的參數(shù)進(jìn)行換一些變量或者做一些簡單的操作而已哈蝇。

4.6、recipe.xml.ftl的編寫

除了template.xml還有g(shù)obals.xml和recipe.xml.ftl攘已,gobals.xml.ftl中基本上沒有修改任何內(nèi)容就不介紹了炮赦。

recipe.xml.ftl中定義的東西比較關(guān)鍵,例如將ftl->java,copy,merge資源文件等样勃。
內(nèi)容較長吠勘,我們拆開描述。

<?xml version="1.0"?>
<recipe>
    <#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
           <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+" />
    </#if>

    <merge from="root/AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />

    <merge from="root/res/values/styles.xml.ftl"
              to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
    <instantiate from="root/res/layout/activity_splash.xml.ftl"
                   to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

    <instantiate from="root/src/app_package/SplashActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>

本例依賴v7庫峡眶,我們需要在這里定義引入剧防;
上例,轉(zhuǎn)化了

  • ${activityClass}.java
  • /layout/${layoutName}.xml
    合并了
  • AndroidManifest.xml
  • styles.xml

剩下的是open標(biāo)簽辫樱,主要就是用于新建完成后峭拘,自動(dòng)打開該文件。

ok,到這鸡挠,我們整個(gè)模板的編寫介紹就結(jié)束了辉饱。

5、總結(jié)

本文我們首先詳細(xì)介紹了一個(gè)模板文件夾下各個(gè)文件以及其內(nèi)部的標(biāo)簽的作用拣展,然后通過一個(gè)具體的實(shí)例彭沼,來演示如何編寫一個(gè)activity模板。
如果你看的足夠仔細(xì)备埃,再花點(diǎn)時(shí)間動(dòng)手姓惑,根據(jù)需求編寫幾個(gè)模板應(yīng)該不成問題。

當(dāng)然瓜喇,文中一些細(xì)節(jié)并沒有談到挺益,對于這些不要擔(dān)心,你有什么需求乘寒,你就想哪個(gè)內(nèi)置模板好像有累死的需求了望众,看它的實(shí)現(xiàn),copy它的相關(guān)代碼改一改就好了伞辛,沒有必要去各種文件的編寫烂翰,這種東西copy修改就好了。

測試過程中蚤氏,需要重啟Android Studio甘耿,如果有問題,記得查看Event Log面板的信息竿滨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佳恬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子于游,更是在濱河造成了極大的恐慌毁葱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贰剥,死亡現(xiàn)場離奇詭異倾剿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚌成,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門前痘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人担忧,你說我怎么就攤上這事芹缔。” “怎么了瓶盛?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵乖菱,是天一觀的道長坡锡。 經(jīng)常有香客問我蓬网,道長窒所,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任帆锋,我火速辦了婚禮吵取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锯厢。我一直安慰自己皮官,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布实辑。 她就那樣靜靜地躺著捺氢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剪撬。 梳的紋絲不亂的頭發(fā)上摄乒,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音残黑,去河邊找鬼馍佑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梨水,可吹牛的內(nèi)容都是我干的拭荤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疫诽,長吁一口氣:“原來是場噩夢啊……” “哼舅世!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奇徒,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雏亚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逼龟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體评凝,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年腺律,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奕短。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匀钧,死狀恐怖翎碑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情之斯,我是刑警寧澤日杈,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響莉擒,放射性物質(zhì)發(fā)生泄漏酿炸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一涨冀、第九天 我趴在偏房一處隱蔽的房頂上張望填硕。 院中可真熱鬧,春花似錦鹿鳖、人聲如沸扁眯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姻檀。三九已至,卻和暖如春涝滴,著一層夾襖步出監(jiān)牢的瞬間绣版,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工狭莱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僵娃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓腋妙,卻偏偏與公主長得像默怨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子骤素,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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