公司有自己的一套 MVP
架構,每次創建新的 MVP Activity
或 MVP Fragment
時,都需要寫相對應的 Presenter
和 Contract
,想到 AS
內嵌的 Activity、Fragment、AIDL
等模板,便捷好用。就想自己將這個 MVP
整成一個模板,這樣就能節省不少寫模板代碼的時間了。
FreeMarker Template Language(FTL)
FreeMarker
模板語言,是一臺基于模板和要改變的數據,并用來生成輸出文本的(HTML
網頁、電子郵件、配置文件、源代碼等 )
的通用的工具,意味著要準備數據在真實編程語言中來顯示, 之后模板顯示已經準備好的數據。在模板中,你可以專注于如何展現數據, 而在模板之外可以專注于要展示什么數據。
FTL 基本語法
- 標簽
FTL
標簽與 HTML
標簽有相似之處,但是不是從屬關系。FTL
標簽都是以 #
開頭的。用戶自定義 FTL
標簽需要使用 @
符號替代 #
。
<#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>
模板格式
按照慣例,模板目錄結構中由 FreeMarker
處理的任何文件都應具有 .ftl
文件擴展名。因此,如果你的一個源文件是 MyActivity.java
,并且它包含 FreeMarker
指令,那么它應該被命名為 MyActivity.java.ftl
。
目錄結構
模板是包含許多XML和FreeMarker文件的目錄。只有兩個必填文件是template.xml和recipe.xml.ftl。模板源文件(PNG文件,模板化Java和XML文件等)屬于root/子目錄。下面是模板的示例目錄結構:
root
存放我們的代碼模板文件和資源文件
globals.xml.ftl
定義全局變量
recipe.xml.ftl
配置需要應用的模板路徑和生成的文件的路徑
template.xml
定義模板參數。
template.xml
每個模板目錄必須包含一個 template.xml
文件。此 XML
文件包含有關模板的元數據,包括 IDE
將作為用戶選項顯示的名稱,描述,類別和用戶可見參數。XML
文件還指示 recipe.xml.ftl
的名稱,以及 globals.xml
文件
下面是一個 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" /> # 如同我們在創建module時所顯示的類型,如: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 -->
# 可選,用于創建模板時,在左邊顯示名為template_blank_activity的預覽圖片
<thumb>template_blank_activity.png</thumb>
</thumbs>
# 可選,將工程定義的全局變量包含進來
<globals file="globals.xml.ftl" />
# 開始執行模板渲染
<execute file="recipe.xml.ftl" />
</template>
以下是 template.xml
支持的標簽列表:
-
format
此模板遵循的模板格式版本 -
revision
此模板的整數版本(您可以在更新模板時遞增),可選 -
name
模板名稱。在AS
操作File
-->New
-->Activity
可找到對應的Activity
-
description
模板的描述。見圖 minApi
模板所需的最小 API
值,IDE
將確保在實例化模板之前,目標工程的 minSdkVersion
不低于這個值,可選
minBuildApi
此模板所需的最小構建目標 API
。在實例化模板之前,IDE
將確保目標項目的目標是大于或等于此值的 API
級別。這可確保模板可以安全地使用較新的 API(
可選擇由運行時 API
級別檢查保護 )
,而不會將編譯時錯誤引入目標項目,可選
<dependency>
表示模板要求目標項目中存在給定庫。如果不存在,IDE
將向項目添加依賴項。
name : 庫的名稱。目前接受的值有:
1、android-support-v4
2、android-support-v13
revision : 此模板所需的庫的最低版本。
例如:
<dependency name =“android-support-v4”revision =“8”/>
<category>
模板類型。此元素是可選的
value : 模板類型。應該是以下值之一:
1、Applications
2、Activities
3、UI Components
例 :
<category value =“Activities”/>
<parameter>
用戶可自定義的模板參數
id : 表示此變量的標識符在 FreeMarker 文件中作為全局變量提供。如果標識符是 foo,則參數值將在 FreeMarker 文件中通過 ${foo} 可得到
name : 模板參數的顯示名稱。假設 <category value = "Activities" />,則在 AS 中通過 File --> New --> Activity 可找到對應的 Activity
type : 參數的數據類型。要么string,boolean,enum,或 separator
help : 操作 <parameter/> 時,底部顯示的提示語
default : 參數的默認值
suggest : 建議值
visibility : 根據其他 View 的 ${id} 定義該 View 是應該可見還是消失
constraints : 屬性值約束
android:inputType : text|textEmailAddress|number|textPassword
type
定義了實際的視圖屬性,分別有
EditText、Spinner
和CheckBox
類型
string : 表示對應的實際視圖是一個EditText
enum : 表示對應的實際視圖是一個Spinner
boolean : 表示對應的實際視圖是一個 CheckBox
constraints
可選屬性。強加于參數值的約束。可以使用組合約束 |。有效的約束類型有:
class : 該值應表示有效的Java類名稱,例如(Activity、Fragment、Presenter、Model、Utility等類名)
nonempty : 該屬性字段不能為 null 或 empty。此約束僅在指定其他約束時才有意義,例如layout,這意味著該值不應表示現有布局資源名稱
unique : 這個確保包中不會存在相同的名稱。(也就是說,假設項目中已經存在 MainActivity,則通過顯示 Main2Activity 等簡單建議,避免使用重復的 Activity 名稱)
apilevel : 數字化的 API 級別
package : 有效的 java 類名
layout : 有效的 layout 名稱
drawable : 有效的 drawable 名稱
string : 有效的 string
id : 有效 id 資源名稱
exists : 值必須已經存在; 此約束僅在指定其他約束時才有意義,例如layout,這意味著該值應表示現有布局資源名稱
suggest
可選的。表示自動建議參數值的
FreeMarker
表達式(“
動態默認值”)
。當用戶修改其他參數值時,如果此參數的值未從其默認值更改,則該值將更改為此表達式的結果。這似乎是循環的,因為參數是可以與suggest
相互對照的值,但這些表達式僅針對未編輯的值進行更新,因此這種方法允許用戶編輯任一參數值,而另一個將自動更新為合理的默認值屬性的內置方法 :
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)
:這句話的意思是,當我們在創建該模板后,在 activityClass
對應的文本框中輸入某個值,比如:test
,它會直接在 layoutName
對應的文本框中顯示,即:test
,所以以完整的語句 suggest="${classToResource(activityClass)}_activity"
而言,此時 layoutName
對應的文本框中顯示的應該是 test_activity
。
<option>
類型的參數enum,表示值的選擇.
id : 如果選擇此選項,則設置的參數值
minApi : 可選的。選擇此選項時所需的最低API級別。minSdkVersion在實例化模板之前,IDE將確保目標項目的值不低于此值
[text] : 此元素的文本內容表示選擇的顯示值
<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>
元素應包含在 <thumbs>
元素內。此元素的文本內容表示縮略圖的路徑。如果此元素具有多個屬性,則它們將被視為參數值的選擇器。例如,如果有兩個縮略圖:
<thumbs>
<thumb>template.png</thumb>
<thumb navType="tabs">template_tabs.png</thumb>
</thumbs>
如果值 navType
模板參數是 tabs
,name
模板“預覽”縮略圖將顯示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>
這個文件用于定義一些全局變量。
-
<global>
定義一個全局變量 -
id
: 變量名稱 -
type
: 變量類型 -
value
: 默認值
訪問這些變量的方法:${
變量id}
。例如:
${hasNoActionBar};
recipe.xml.ftl
recipe.xml.ftl
包含從此模板生成代碼時應執行的各個指令。例如,您可以復制某些文件或目錄( copy
指令 )
,可選地通過 FreeMarker
運行源文件( 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
文件夾復制文件到目標文件
唯一必需的參數是 from
指定要在 root/
目錄下復制的源文件的位置。如果需要,將自動創建所有必需的祖先目錄。
默認目標位置是輸出目錄根目錄下的相同路徑(即目標項目的位置)。如果提供了可選 to
參數,則指定輸出目錄。請注意,如果 from
路徑以 ...
結尾 .ftl
,則會自動刪除它。例如 <instantiate from="res/values/strings.xml.ftl" />
是足夠的; 這將創建一個名為的文件 strings.xml
,而不是 strings.xml.ftl
。
此參數以遞歸方式工作,因此如果 from
是目錄,則以遞歸方式復制該目錄。
<instantiate>
將 .ftl
文件轉化成為 .java
或 .kt
文件
<merge>
用于合并文件,如將模板文件的 string.xml
合并到我們項目的 string.xml
<open>
在代碼生成后打開指定文件,例如:當我們創建一個 Activity
時,AS
會自動打開 Activity
以及布局文件
<#include>
導入另一個 ftl
文件
額外模板功能
FreeMarker
幾個重要函數 :
string activityToLayout(string)
作用:
此函數將類似
activity calss
的標識符字符串(
例如FooActivity)
轉換為對應的資源標識符字符串,例如activity_foo
。參數:
activityClass,活動類名稱,例如FooActivity重新格式化。
string camelCaseToUnderscore(string)
作用 :
此函數將
camel-case
標識符字符串(
例如FooBar)
轉換為其對應的下劃線分隔標識符字符串,例如foo_bar
。參數 :
camelStr
,駝峰式字符串,例如FooBar
轉換為下劃線分隔的字符串。
string escapeXmlAttribute(string)
作用 :
此函數用來轉義字符串,例如
Android's
,它可以用作XML
屬性值:Android's
。特別是,它將轉義'
,"
,<
和&
。參數 :
str
,要轉義的字符串。
string escapeXmlText(string)
作用 :
此函數用來轉義字符串,例如
A & B's
可以將其用作XML
文本。這意味著它將轉義<
和>
,但不像escapeXmlAttribute
它將不會轉義'
和"
。在前面的示例中,它將轉義字符串為A & B\s
。請注意,如果您想要使用XML
文本作為<string>
資源的值,您應該考慮使用escapeXmlString
,因為它執行額外的所需的字符串資源轉義參數 :
str
,轉義為正確XML文本的字符串。
string escapeXmlString(string)
作用 :
此函數用來轉義字符串,例如
A & B's
,它適合作為XML
文本插入字符串資源文件中,例如A & B\s
。除了轉義<
和&
之類的XML
字符外,它還執行其他Android
特定的轉義,例如使用反斜杠轉義撇號,等等參數 :
str
,例如,Activity's Title
以轉義為適當的資源XML
值。
string extractLetters(string)
作用 :
此函數從字符串中提取所有字母,有效刪除任何標點符號和空白字符。
參數 :
str
,從中提取字母的字符串。
string classToResource(string)
作用 :
此函數將
Android
類名稱(
例如FooActivity
或FooFragment)
轉換為相應的資源標識符字符串,例如foo
,刪除activity
或fragment
后綴。目前刪除的后綴列在下面:
Activity
Fragment
Provider
Service
參數 :
className
,類名,例如FooActivity
重新格式化為下劃線分隔的字符串,后綴已刪除。
string layoutToActivity(string)
作用 :
此函數將資源標識符字符串
(
例如activity_foo)
轉換為對應的Java
類標識符字符串,例如FooActivity
。參數 :
resourceName
,資源名稱,例如activity_foo
重新格式化。
string slashedPackageName(string)
作用 :
此函數將完整
Java
包名稱轉換為其對應的目錄路徑。例如,如果給定的參數是com.example.foo
,則返回值為com/example/foo
。參數 :
packageName
,要重新格式化的包名稱,例如com.example.foo
。
string underscoreToCamelCase(string)
作用 :
此函數將下劃線分隔的字符串
(
例如foo_bar)
轉換為其對應的駝峰字符串,例如FooBar
。參數 :
underStr
,下劃線分隔的字符串,例如foo_bar
轉換為駝峰字符串。
工具元數據
創建活動布局時,請確保在布局的根視圖中包含活動名稱作為 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}" />
我們在布局中使用此屬性來維護到用于布局的活動活動的映射。(
是的,可以有多個,但是此屬性顯示您要編輯布局的活動上下文。例如,它將用于查找主題注冊(
這是每個活動而不是每個布局 ) )
在清單文件中,我們將來會將其用于其他功能 - 例如預覽操作欄,這也需要我們知道活動上下文。
具體參考