1、概述
上一篇文章,已经初步对Android Studio的模板有了初步的介绍及使用,以及一些开源模板的推荐:
本文将对如何编写Template,进行详细的介绍(以activity模板为例)
2、模板的文件结构
学习编写模板最好的方式呢,就是参考IDE中已经提供的最简单的模板,那么在Android Studio中最简单的activity模板就是:Empty Activity
了,我们打开该模板文件,首先对文件结构有个直观的了解,如图:
可以看到每个插件对应一个文件夹,文件夹包含:
- template.xml
- recipe.xml.ftl
- globals.xml.ftl
- root
- 效果缩略图
下面我们逐一对上述每个文件的作用进行介绍
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个 parameter 标签-->
<!-- 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>
标签的name
属性,对应新建Activity时显示的名字 -
<category>
对应New的类的类别为Activity
剩下的,对应我们Android Studio新建Empty Activity
的界面就很好理解了,如图:
看到这个界面大部分属性都出来了,我们重点看parameter,界面上每个框出来的部分对应一个parameter
部分属性介绍:
- id:唯一标识,最终通过该属性的值,获取用户输入的值(文本框内容 || 是否选中)
- name:界面上类似Label的提示语
- type:输入值类型
- constraints:填写值的约束
- suggest:建议值,比如填写ActivityName的时候,会给出一个布局文件的建议值。
- default:默认值
- help:底部显示的提示语
这个部分对应界面还是非常好理解的,大家可以简单的修改一些字符串,或者添加一个<parameter>,重启AS,看看效果。
template.xml的最下面的部分引入了globals.xml.ftl和recipe.xml.ftl。
这两个我们会详细介绍。
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>
通过名称可以猜出它是用于定义一些全局的变量,可以看到其内部有<global>
标签分别定义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>
为了介绍,我们将几个重要标签都列出来
- include 此文件包含其它文件内容,和xml布局
include
作用一致 - merge 合并的意思,比如将我们使用到的strings.xml合并到我们的项目的stirngs.xml中
- open 在代码生成后,打开指定的文件,比如我们新建一个Activity后,默认就会将该Activity打开。
- instantiate 实例化,生成相应文件。可以看到上例试将ftl->java文件的,也就是说中间会通过一个步骤,将ftl中的变量都换成对应的值,那么完整的流程是
ftl->freemarker process -> java
在介绍instantiate时,涉及到了freemarker,不可避免的需要对它进行简单的介绍。
目前我们已经基本了解了一个模板其内部的文件结构了,以及每个文件大致包含的东西,我们简单做个总结:
- template 中parameter标签,主要用于提供参数
- global.xml.ftl 主要用于提供参数
- recipe.xml.ftl 主要用于生成我们实际需要的代码,资源文件等;例如,利用参数+MainActivity.java.ftl -> MainActivity.java;其实就是利用参数将ftl中的变量进行替换。
那么整体的关系类似下图:
3、简单的freemarker语法
上面我们已经基本了解模板生成的大致的流程以及涉及到的文件,大致了解了我们生成的源码或者xml文件,需要经过:
ftl -> freemarker process -> java/xml
这样的流程,那么我们必须对freemarker有个简单的了解。
- 一个简单的例子
比如我们有个变量user=art
有个ftl文件内容:helloL${user}
最后经过freemarker的输出结果即为 hello:art
- if语法
<# if generateLayout>
//生成Layout文件
</#if>
看一眼就知道大概的意思了~有一定的编程经验,即使不知道这个东西叫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>
}
}
可以看到其内部包含很多变量,这些变量的值一般来源于用户的输入和global.xml.ftl中预定义的值,经过recipe.xml.ftl中instantiate标签的处理,将变量换成实际的值,即可在我们的项目的指定位置,得到我们期望的Activity。
流程大致可用下图说明:
图片来源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
看到这,最起码理解了,当我们能选择创建不同的Activity类型,最终得到的不同的效果其中的原理原来在这。
4、具体的模板实例
了解了基本的理论之后,下面我们可以通过一个实例来将上面的知识点整合。
我们编写了一个Activity模板叫做:Splash Activity
,用于创建一个全屏自动finish的activity,效果如下:
当我们点击New | Activity | Fragment Activity 就可以完成上面的Activity的创建,而避免了编写布局文件,引入design库以及一些简单的编码。
是不是感觉还是不错的,大家可以针对自己的需求,按照规范的格式岁月指定模板。
建议大家copy一个现有的模板,再其基础上修改即可,比如本例是在Empty Activity基础上修改的。
下面我们看上栗的具体的实现。
4.1 template.xml的编写
通过上面的学习我们知道template.xml中可以定义我们创建面板的控件布局等,本例我们创建Activity的界面如下:
对应的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那里变化
经过前面的学习应该很好理解,每个parameter对应界面上的一个控件,控件的这个id最终可以得到用户输入值,后面会用于渲染ftl文件
4.2、用到的类
本例中最终要生成Activityu,也就是说对应会有一个ftl文件用于最终生成这个类。
// 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文件,可以看到上面的代码基础上和Java代码没什么区别,实际上就是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}">
<!-- 隐藏状态栏 -->
<<item name="android:windowFullscreen">true</item>
<!-- 隐藏标题栏 -->
<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>
发现和我们真正编写的Activity并无多大区别。
看完用到的类和布局文件的ftl,大家心里应该有个底了,这模板几乎就和我们平时写的java类一样,只是根据用户据在新建Activity界面所输入的参数进行换一些变量或者做一些简单的操作而已。
4.6、recipe.xml.ftl的编写
除了template.xml还有gobals.xml和recipe.xml.ftl,gobals.xml.ftl中基本上没有修改任何内容就不介绍了。
recipe.xml.ftl中定义的东西比较关键,例如将ftl->java,copy,merge资源文件等。
内容较长,我们拆开描述。
<?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库,我们需要在这里定义引入;
上例,转化了
${activityClass}.java
-
/layout/${layoutName}.xml
合并了 AndroidManifest.xml
styles.xml
剩下的是open标签,主要就是用于新建完成后,自动打开该文件。
ok,到这,我们整个模板的编写介绍就结束了。
5、总结
本文我们首先详细介绍了一个模板文件夹下各个文件以及其内部的标签的作用,然后通过一个具体的实例,来演示如何编写一个activity模板。
如果你看的足够仔细,再花点时间动手,根据需求编写几个模板应该不成问题。
当然,文中一些细节并没有谈到,对于这些不要担心,你有什么需求,你就想哪个内置模板好像有累死的需求了,看它的实现,copy它的相关代码改一改就好了,没有必要去各种文件的编写,这种东西copy修改就好了。
测试过程中,需要重启Android Studio,如果有问题,记得查看Event Log面板的信息。