走马观花- 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模板制作

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • 学习编写模板最好的方式呢,就是参考IDE中已经提供的最简单的模板,那么在Android Studio中最简单的ac...
    开心的锣鼓阅读 2,429评论 0 18
  • Android Studio自定义模板 写页面竟然可以如此轻松 1、概述 上一篇文章,已经初步对Android S...
    Art_Collector阅读 1,377评论 0 5
  • FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主要由如下4个部分组成: 1...
    年轻小伙程序员阅读 2,743评论 0 5
  • 陶罐里的白玫瑰终于还是枯败了,边缘是淡的黄褐色,开始呈现种像揉皱纸张的质感。一直狂咳的喉咙也是如此,说话的声音,像...
    mo清夜无尘阅读 441评论 0 5
  • 散落的容颜,一个人孤独,一个人悲伤,最后的唯美,是人生的在意,伤感了最后的风景,温柔的慈悲,伤感了太多,只是那个憔...
    酒分阅读 322评论 1 4