走馬觀花- AS 自定義模板

公司有自己的一套 MVP 架構,每次創建新的 MVP ActivityMVP Fragment 時,都需要寫相對應的 PresenterContract,想到 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/子目錄。下面是模板的示例目錄結構:

image
  • 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>
image

以下是 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、SpinnerCheckBox 類型

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 模板參數是 tabsname 模板“預覽”縮略圖將顯示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>

這個文件用于定義一些全局變量。

  1. <global> 定義一個全局變量
  2. id : 變量名稱
  3. type : 變量類型
  4. value : 默認值

訪問這些變量的方法:${變量id}。例如:

${hasNoActionBar};
recipe.xml.ftl

recipe.xml.ftl 包含從此模板生成代碼時應執行的各個指令。例如,您可以復制某些文件或目錄( copy 指令 ,可選地通過 FreeMarker 運行源文件( instantiate 指令,并在生成代碼( open 指令 后要求 ADTEclipse 中打開文件。

注意: 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&apos;s。特別是,它將轉義'"<

參數 :

str,要轉義的字符串。

  • string escapeXmlText(string)

作用 :

此函數用來轉義字符串,例如 A & B's 可以將其用作 XML 文本。這意味著它將轉義 <>,但不像 escapeXmlAttribute 它將不會轉義'"。在前面的示例中,它將轉義字符串為 A &amp; B\s。請注意,如果您想要使用 XML 文本作為 <string> 資源的值,您應該考慮使用 escapeXmlString,因為它執行額外的所需的字符串資源轉義

參數 :

str,轉義為正確XML文本的字符串。

  • string escapeXmlString(string)

作用 :

此函數用來轉義字符串,例如 A & B's ,它適合作為 XML 文本插入字符串資源文件中,例如 A &amp; B\s 。除了轉義 < 之類的 XML 字符外,它還執行其他Android 特定的轉義,例如使用反斜杠轉義撇號,等等

參數 :

str,例如,Activity's Title 以轉義為適當的資源 XML 值。

  • string extractLetters(string)

作用 :

此函數從字符串中提取所有字母,有效刪除任何標點符號和空白字符。

參數 :

str,從中提取字母的字符串。

  • string classToResource(string)

作用 :

此函數將 Android 類名稱 例如 FooActivityFooFragment)轉換為相應的資源標識符字符串,例如 foo ,刪除 activityfragment 后綴。目前刪除的后綴列在下面:

  • 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}" />

我們在布局中使用此屬性來維護到用于布局的活動活動的映射。是的,可以有多個,但是此屬性顯示您要編輯布局的活動上下文。例如,它將用于查找主題注冊 這是每個活動而不是每個布局 ) ) 在清單文件中,我們將來會將其用于其他功能 - 例如預覽操作欄,這也需要我們知道活動上下文。

具體參考

Android ADT Template Format Document

Android模板制作

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 學習編寫模板最好的方式呢,就是參考IDE中已經提供的最簡單的模板,那么在Android Studio中最簡單的ac...
    開心的鑼鼓閱讀 2,481評論 0 18
  • Android Studio自定義模板 寫頁面竟然可以如此輕松 1、概述 上一篇文章,已經初步對Android S...
    Art_Collector閱讀 1,458評論 0 5
  • FreeMarker的模板文件并不比HTML頁面復雜多少,FreeMarker模板文件主要由如下4個部分組成: 1...
    年輕小伙程序員閱讀 3,057評論 0 5
  • 陶罐里的白玫瑰終于還是枯敗了,邊緣是淡的黃褐色,開始呈現種像揉皺紙張的質感。一直狂咳的喉嚨也是如此,說話的聲音,像...
    mo清夜無塵閱讀 487評論 0 5
  • 散落的容顏,一個人孤獨,一個人悲傷,最后的唯美,是人生的在意,傷感了最后的風景,溫柔的慈悲,傷感了太多,只是那個憔...
    酒分閱讀 353評論 1 4