公司有自己的一套 MVP 架構(gòu),每次創(chuàng)建新的 MVP Activity 或 MVP Fragment 時(shí),都需要寫相對(duì)應(yīng)的 Presenter 和 Contract,想到 AS 內(nèi)嵌的 Activity、Fragment、AIDL 等模板,便捷好用。就想自己將這個(gè) MVP 整成一個(gè)模板,這樣就能節(jié)省不少寫模板代碼的時(shí)間了。
FreeMarker Template Language(FTL)
FreeMarker 模板語言,是一臺(tái)基于模板和要改變的數(shù)據(jù),并用來生成輸出文本的(HTML 網(wǎng)頁、電子郵件、配置文件、源代碼等 ) 的通用的工具,意味著要準(zhǔn)備數(shù)據(jù)在真實(shí)編程語言中來顯示, 之后模板顯示已經(jīng)準(zhǔn)備好的數(shù)據(jù)。在模板中,你可以專注于如何展現(xiàn)數(shù)據(jù), 而在模板之外可以專注于要展示什么數(shù)據(jù)。
FTL 基本語法
- 標(biāo)簽
FTL 標(biāo)簽與 HTML 標(biāo)簽有相似之處,但是不是從屬關(guān)系。FTL 標(biāo)簽都是以 # 開頭的。用戶自定義 FTL 標(biāo)簽需要使用 @ 符號(hào)替代 #。
<#include "xxx.tfl"/>
- 注釋
<#-- 這是注釋 -->
- if 、 else 指令
<#if 變量名>
......
<#elseif 變量名>
......
<#else>
......
</#if>
例如:
<#if generateKotlin>
<instantiate from="root/src/app_package/SimpleActivity.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<#else>
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>
- 訪問變量值
${變量名} 例如:${packageName}
- 引入其他文件
<#include "xxx.ftl"/>
例如:
<#include "../common/common_globals.xml.ftl" />
-
list變量
<#list variables as loopVariable>
repeatThis
</#list>
例:
<#list fruits as fruit>
<li>${fruit}
</#list>
模板格式
按照慣例,模板目錄結(jié)構(gòu)中由 FreeMarker 處理的任何文件都應(yīng)具有 .ftl 文件擴(kuò)展名。因此,如果你的一個(gè)源文件是 MyActivity.java ,并且它包含 FreeMarker 指令,那么它應(yīng)該被命名為 MyActivity.java.ftl。
目錄結(jié)構(gòu)
模板是包含許多XML和FreeMarker文件的目錄。只有兩個(gè)必填文件是template.xml和recipe.xml.ftl。模板源文件(PNG文件,模板化Java和XML文件等)屬于root/子目錄。下面是模板的示例目錄結(jié)構(gòu):

root
存放我們的代碼模板文件和資源文件
globals.xml.ftl
定義全局變量
recipe.xml.ftl
配置需要應(yīng)用的模板路徑和生成的文件的路徑
template.xml
定義模板參數(shù)。
template.xml
每個(gè)模板目錄必須包含一個(gè) template.xml 文件。此 XML 文件包含有關(guān)模板的元數(shù)據(jù),包括 IDE 將作為用戶選項(xiàng)顯示的名稱,描述,類別和用戶可見參數(shù)。XML 文件還指示 recipe.xml.ftl 的名稱,以及 globals.xml 文件
下面是一個(gè) template.xml 文件示例:
<?xml version="1.0"?>
<template
format="5"
revision="5"
name="MVP Activity"
minApi="9"
minBuildApi="14"
description="Creates a new MVP activity">
<category value="Activity" />
<formfactor value="Mobile" /> # 如同我們?cè)趧?chuàng)建module時(shí)所顯示的類型,如:Wear、TV等。
<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" />
<thumbs>
<!-- default thumbnail is required -->
# 可選,用于創(chuàng)建模板時(shí),在左邊顯示名為template_blank_activity的預(yù)覽圖片
<thumb>template_blank_activity.png</thumb>
</thumbs>
# 可選,將工程定義的全局變量包含進(jìn)來
<globals file="globals.xml.ftl" />
# 開始執(zhí)行模板渲染
<execute file="recipe.xml.ftl" />
</template>

以下是 template.xml 支持的標(biāo)簽列表:
-
format此模板遵循的模板格式版本 -
revision此模板的整數(shù)版本(您可以在更新模板時(shí)遞增),可選 -
name模板名稱。在AS操作File-->New-->Activity可找到對(duì)應(yīng)的Activity -
description模板的描述。見圖 minApi
模板所需的最小 API 值,IDE 將確保在實(shí)例化模板之前,目標(biāo)工程的 minSdkVersion 不低于這個(gè)值,可選
minBuildApi
此模板所需的最小構(gòu)建目標(biāo) API。在實(shí)例化模板之前,IDE 將確保目標(biāo)項(xiàng)目的目標(biāo)是大于或等于此值的 API 級(jí)別。這可確保模板可以安全地使用較新的 API( 可選擇由運(yùn)行時(shí) API 級(jí)別檢查保護(hù) ),而不會(huì)將編譯時(shí)錯(cuò)誤引入目標(biāo)項(xiàng)目,可選
<dependency>
表示模板要求目標(biāo)項(xiàng)目中存在給定庫。如果不存在,IDE 將向項(xiàng)目添加依賴項(xiàng)。
name : 庫的名稱。目前接受的值有:
1、android-support-v4
2、android-support-v13
revision : 此模板所需的庫的最低版本。
例如:
<dependency name =“android-support-v4”revision =“8”/>
<category>
模板類型。此元素是可選的
value : 模板類型。應(yīng)該是以下值之一:
1、Applications
2、Activities
3、UI Components
例 :
<category value =“Activities”/>
<parameter>
用戶可自定義的模板參數(shù)
id : 表示此變量的標(biāo)識(shí)符在 FreeMarker 文件中作為全局變量提供。如果標(biāo)識(shí)符是 foo,則參數(shù)值將在 FreeMarker 文件中通過 ${foo} 可得到
name : 模板參數(shù)的顯示名稱。假設(shè) <category value = "Activities" />,則在 AS 中通過 File --> New --> Activity 可找到對(duì)應(yīng)的 Activity
type : 參數(shù)的數(shù)據(jù)類型。要么string,boolean,enum,或 separator
help : 操作 <parameter/> 時(shí),底部顯示的提示語
default : 參數(shù)的默認(rèn)值
suggest : 建議值
visibility : 根據(jù)其他 View 的 ${id} 定義該 View 是應(yīng)該可見還是消失
constraints : 屬性值約束
android:inputType : text|textEmailAddress|number|textPassword
type
定義了實(shí)際的視圖屬性,分別有
EditText、Spinner和CheckBox類型
string : 表示對(duì)應(yīng)的實(shí)際視圖是一個(gè)EditText
enum : 表示對(duì)應(yīng)的實(shí)際視圖是一個(gè)Spinner
boolean : 表示對(duì)應(yīng)的實(shí)際視圖是一個(gè) CheckBox
constraints
可選屬性。強(qiáng)加于參數(shù)值的約束??梢允褂媒M合約束 |。有效的約束類型有:
class : 該值應(yīng)表示有效的Java類名稱,例如(Activity、Fragment、Presenter、Model、Utility等類名)
nonempty : 該屬性字段不能為 null 或 empty。此約束僅在指定其他約束時(shí)才有意義,例如layout,這意味著該值不應(yīng)表示現(xiàn)有布局資源名稱
unique : 這個(gè)確保包中不會(huì)存在相同的名稱。(也就是說,假設(shè)項(xiàng)目中已經(jīng)存在 MainActivity,則通過顯示 Main2Activity 等簡(jiǎn)單建議,避免使用重復(fù)的 Activity 名稱)
apilevel : 數(shù)字化的 API 級(jí)別
package : 有效的 java 類名
layout : 有效的 layout 名稱
drawable : 有效的 drawable 名稱
string : 有效的 string
id : 有效 id 資源名稱
exists : 值必須已經(jīng)存在; 此約束僅在指定其他約束時(shí)才有意義,例如layout,這意味著該值應(yīng)表示現(xiàn)有布局資源名稱
suggest
可選的。表示自動(dòng)建議參數(shù)值的
FreeMarker表達(dá)式(“動(dòng)態(tài)默認(rèn)值”)。當(dāng)用戶修改其他參數(shù)值時(shí),如果此參數(shù)的值未從其默認(rèn)值更改,則該值將更改為此表達(dá)式的結(jié)果。這似乎是循環(huán)的,因?yàn)閰?shù)是可以與suggest相互對(duì)照的值,但這些表達(dá)式僅針對(duì)未編輯的值進(jìn)行更新,因此這種方法允許用戶編輯任一參數(shù)值,而另一個(gè)將自動(dòng)更新為合理的默認(rèn)值屬性的內(nèi)置方法 :
1、suggest="${layoutToActivity(layoutName)}"
layoutName="activity_main" --> MainActivity
layoutName="main" --> MainActivity
2、suggest="${activityToLayout(activityClass)}"
activityClass=“MainActivity” --> activity_main
activityClass=“Main” --> activity_main
3、suggest="${underscoreToCamelCase(classToResource(activityClass))}Adapter" //首字母是大寫
activityClass=“MainActivity” --> MainAdapter
activityClass=“Main” --> MainAdapter
4、suggest="item_${classToResource(activityClass)}" //首字母變成了小寫
activityClass=“MainActivity” --> item_main
activityClass=“Main” --> item_main
classToResource(activityClass):這句話的意思是,當(dāng)我們?cè)趧?chuàng)建該模板后,在 activityClass 對(duì)應(yīng)的文本框中輸入某個(gè)值,比如:test,它會(huì)直接在 layoutName 對(duì)應(yīng)的文本框中顯示,即:test,所以以完整的語句 suggest="${classToResource(activityClass)}_activity" 而言,此時(shí) layoutName 對(duì)應(yīng)的文本框中顯示的應(yīng)該是 test_activity 。
<option>
類型的參數(shù)enum,表示值的選擇.
id : 如果選擇此選項(xiàng),則設(shè)置的參數(shù)值
minApi : 可選的。選擇此選項(xiàng)時(shí)所需的最低API級(jí)別。minSdkVersion在實(shí)例化模板之前,IDE將確保目標(biāo)項(xiàng)目的值不低于此值
[text] : 此元素的文本內(nèi)容表示選擇的顯示值
<parameter
id="navType"
name="Navigation Type"
type="enum"
default="none">
<option id="none" default="true">None</option>
<option id="tabs" minApi="11">Tabs</option>
<option id="pager" minApi="11">Swipe Views</option>
<option id="dropdown" minApi="11">Dropdown</option>
</parameter>
<thumb>
表示模板的縮略圖。<thumb> 元素應(yīng)包含在 <thumbs> 元素內(nèi)。此元素的文本內(nèi)容表示縮略圖的路徑。如果此元素具有多個(gè)屬性,則它們將被視為參數(shù)值的選擇器。例如,如果有兩個(gè)縮略圖:
<thumbs>
<thumb>template.png</thumb>
<thumb navType="tabs">template_tabs.png</thumb>
</thumbs>
如果值 navType 模板參數(shù)是 tabs,name 模板“預(yù)覽”縮略圖將顯示template_tabs.png ,否則顯示 template.png。
global.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>
這個(gè)文件用于定義一些全局變量。
-
<global>定義一個(gè)全局變量 -
id: 變量名稱 -
type: 變量類型 -
value: 默認(rèn)值
訪問這些變量的方法:${變量id}。例如:
${hasNoActionBar};
recipe.xml.ftl
recipe.xml.ftl 包含從此模板生成代碼時(shí)應(yīng)執(zhí)行的各個(gè)指令。例如,您可以復(fù)制某些文件或目錄( copy 指令 ),可選地通過 FreeMarker 運(yùn)行源文件( instantiate 指令),并在生成代碼( open 指令 )后要求 ADT 在 Eclipse 中打開文件。
注意: recipe.xml.ftl 的名稱可自定義,但必須在 template.xml 聲明。但按照慣例,最好稱之為 recipe.xml.ftl。
注意:全局變量 globals.xml.ftl 可用于recipe.xml.ftl。
<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
<#include "../common/recipe_manifest.xml.ftl" />
<@kt.addAllKotlinDependencies />
<#if generateLayout>
<#include "../common/recipe_simple.xml.ftl" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</#if>
<#if generateKotlin>
<instantiate from="root/src/app_package/SimpleActivity.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<#else>
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>
</recipe>
該文件用于定義如何生成文件和代碼。
<copy>
用于從 root 文件夾復(fù)制文件到目標(biāo)文件
唯一必需的參數(shù)是 from 指定要在 root/ 目錄下復(fù)制的源文件的位置。如果需要,將自動(dòng)創(chuàng)建所有必需的祖先目錄。
默認(rèn)目標(biāo)位置是輸出目錄根目錄下的相同路徑(即目標(biāo)項(xiàng)目的位置)。如果提供了可選 to 參數(shù),則指定輸出目錄。請(qǐng)注意,如果 from 路徑以 ... 結(jié)尾 .ftl ,則會(huì)自動(dòng)刪除它。例如 <instantiate from="res/values/strings.xml.ftl" /> 是足夠的; 這將創(chuàng)建一個(gè)名為的文件 strings.xml,而不是 strings.xml.ftl 。
此參數(shù)以遞歸方式工作,因此如果 from 是目錄,則以遞歸方式復(fù)制該目錄。
<instantiate>
將 .ftl 文件轉(zhuǎn)化成為 .java 或 .kt 文件
<merge>
用于合并文件,如將模板文件的 string.xml 合并到我們項(xiàng)目的 string.xml
<open>
在代碼生成后打開指定文件,例如:當(dāng)我們創(chuàng)建一個(gè) Activity 時(shí),AS 會(huì)自動(dòng)打開 Activity 以及布局文件
<#include>
導(dǎo)入另一個(gè) ftl 文件
額外模板功能
FreeMarker 幾個(gè)重要函數(shù) :
string activityToLayout(string)
作用:
此函數(shù)將類似
activity calss的標(biāo)識(shí)符字符串(例如FooActivity)轉(zhuǎn)換為對(duì)應(yīng)的資源標(biāo)識(shí)符字符串,例如activity_foo。參數(shù):
activityClass,活動(dòng)類名稱,例如FooActivity重新格式化。
string camelCaseToUnderscore(string)
作用 :
此函數(shù)將
camel-case標(biāo)識(shí)符字符串(例如FooBar)轉(zhuǎn)換為其對(duì)應(yīng)的下劃線分隔標(biāo)識(shí)符字符串,例如foo_bar。參數(shù) :
camelStr,駝峰式字符串,例如FooBar轉(zhuǎn)換為下劃線分隔的字符串。
string escapeXmlAttribute(string)
作用 :
此函數(shù)用來轉(zhuǎn)義字符串,例如
Android's,它可以用作XML屬性值:Android's。特別是,它將轉(zhuǎn)義',",<和&。參數(shù) :
str,要轉(zhuǎn)義的字符串。
string escapeXmlText(string)
作用 :
此函數(shù)用來轉(zhuǎn)義字符串,例如
A & B's可以將其用作XML文本。這意味著它將轉(zhuǎn)義<和>,但不像escapeXmlAttribute它將不會(huì)轉(zhuǎn)義'和"。在前面的示例中,它將轉(zhuǎn)義字符串為A & B\s。請(qǐng)注意,如果您想要使用XML文本作為<string>資源的值,您應(yīng)該考慮使用escapeXmlString,因?yàn)樗鼒?zhí)行額外的所需的字符串資源轉(zhuǎn)義參數(shù) :
str,轉(zhuǎn)義為正確XML文本的字符串。
string escapeXmlString(string)
作用 :
此函數(shù)用來轉(zhuǎn)義字符串,例如
A & B's,它適合作為XML文本插入字符串資源文件中,例如A & B\s。除了轉(zhuǎn)義<和&之類的XML字符外,它還執(zhí)行其他Android特定的轉(zhuǎn)義,例如使用反斜杠轉(zhuǎn)義撇號(hào),等等參數(shù) :
str,例如,Activity's Title以轉(zhuǎn)義為適當(dāng)?shù)馁Y源XML值。
string extractLetters(string)
作用 :
此函數(shù)從字符串中提取所有字母,有效刪除任何標(biāo)點(diǎn)符號(hào)和空白字符。
參數(shù) :
str,從中提取字母的字符串。
string classToResource(string)
作用 :
此函數(shù)將
Android類名稱(例如FooActivity或FooFragment)轉(zhuǎn)換為相應(yīng)的資源標(biāo)識(shí)符字符串,例如foo,刪除activity或fragment后綴。目前刪除的后綴列在下面:
ActivityFragmentProviderService參數(shù) :
className,類名,例如FooActivity重新格式化為下劃線分隔的字符串,后綴已刪除。
string layoutToActivity(string)
作用 :
此函數(shù)將資源標(biāo)識(shí)符字符串
(例如activity_foo)轉(zhuǎn)換為對(duì)應(yīng)的Java類標(biāo)識(shí)符字符串,例如FooActivity。參數(shù) :
resourceName,資源名稱,例如activity_foo重新格式化。
string slashedPackageName(string)
作用 :
此函數(shù)將完整
Java包名稱轉(zhuǎn)換為其對(duì)應(yīng)的目錄路徑。例如,如果給定的參數(shù)是com.example.foo,則返回值為com/example/foo。參數(shù) :
packageName,要重新格式化的包名稱,例如com.example.foo。
string underscoreToCamelCase(string)
作用 :
此函數(shù)將下劃線分隔的字符串
(例如foo_bar)轉(zhuǎn)換為其對(duì)應(yīng)的駝峰字符串,例如FooBar。參數(shù) :
underStr,下劃線分隔的字符串,例如foo_bar轉(zhuǎn)換為駝峰字符串。
工具元數(shù)據(jù)
創(chuàng)建活動(dòng)布局時(shí),請(qǐng)確保在布局的根視圖中包含活動(dòng)名稱作為 tools 命名空間的一部分,如以下示例所示
<TextView 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:gravity="center"
android:text="@string/hello_world"
android:padding="@dimen/padding_medium"
tools:context=".${activityClass}" />
我們?cè)诓季种惺褂么藢傩詠砭S護(hù)到用于布局的活動(dòng)活動(dòng)的映射。(是的,可以有多個(gè),但是此屬性顯示您要編輯布局的活動(dòng)上下文。例如,它將用于查找主題注冊(cè)( 這是每個(gè)活動(dòng)而不是每個(gè)布局 ) ) 在清單文件中,我們將來會(huì)將其用于其他功能 - 例如預(yù)覽操作欄,這也需要我們知道活動(dòng)上下文。
具體參考