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面板的信息。