先引用网友的成果
1、概述
上一篇文章,已经初步对Android Studio的模板有了初步的介绍及使用,以及一些开源模板的推荐:
本文将对如何编写Template,进行详细的介绍(以activity模板为例)
2、模板的文件结构
学习编写模板最好的方式呢,就是参考IDE中已经提供的最简单的模板,那么在Android Studio中最简单的activity模板就是:
Empty Activity 文件结构Empty Activity
了,我们打开该模板文件,首先对文件结构有个直观的了解,如图:可以看到每个插件对应一个文件夹,文件夹包含:
template.xmlrecipe.xml.ftlglobals.xml.ftlroot效果缩略图下面我们逐一对上述每个文件的作用进行介绍
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新建
Configure ActivityEmpty Activity
的界面就很好理解了,如图:看到这个界面大部分属性都出来了,我们重点看parameter,界面上每个框出来的部分对应一个
id:唯一标识,最终通过该属性的值,获取用户输入的值(文本框内容 || 是否选中)name:界面上类似Label的提示语type:输入值类型constraints:填写值的约束suggest:建议值,比如填写ActivityName的时候,会给出一个布局文件的建议值。default:默认值help:底部显示的提示语parameter
部分属性介绍:这个部分对应界面还是非常好理解的,大家可以简单的修改一些字符串,或者添加一个<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}
的值为falserecipe.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中的变量进行替换。那么整体的关系类似下图:
Template Variable Dataflow3、简单的freemarker语法
上面我们已经基本了解模板生成的大致的流程以及涉及到的文件,大致了解了我们生成的源码或者xml文件,需要经过:
一个简单的例子比如我们有个变量user=art有个ftl文件内容:helloL${user}最后经过freemarker的输出结果即为 hello:artif语法<# if generateLayout> //生成Layout文件</#if>ftl -> freemarker process -> java/xml
这样的流程,那么我们必须对freemarker有个简单的了解。看一眼就知道大概的意思了~有一定的编程经验,即使不知道这个东西叫freemarker,对于这些简单的语法还是能看懂的。
我们最后以Empty Activity模板中的SimpleActivit为例:
// root/src/app_package/SimpleActivity.java.ftlpackage ${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模板叫做:
Paste_Image.pngSplash 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.ftlpackage ${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,到这,我们整个模板的编写介绍就结束了。
如何创建自己activity并直接继承的BaseActivity
1、修改....Activity.java.ftl文件创建的activity就是以此为模板的如:package ${packageName};import ${superClassFqcn};import android.os.Bundle;<#if includeCppSupport!false>import android.widget.TextView;</#if>public class ${activityClass} extends ${superClass} { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);<#if generateLayout> setContentView(R.layout.${layoutName});</#if><#include "../../../../common/jni_code_usage.java.ftl"> startRun(); }<#include "../../../../common/jni_code_snippet.java.ftl"> @Override public void findView() { } @Override public void initData() { } @Override public void initView() { } @Override public void initClicked() { } @Override public void updateUI() { }}2、修改template.xml文件 创建activity时的界面选项名由此配置3、修改globals.xml.ftl文件 创建activity是模板中的变量由此文件定义值如:<?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" /> <#assign theme=getApplicationTheme()!{ "name": "AppTheme", "isAppCompat": true }> <#assign themeName=theme.name!'AppTheme'> <#assign themeNameNoActionBar=theme.nameNoActionBar!'AppTheme.NoActionBar'> <#assign appCompat=backwardsCompatibility!(theme.isAppCompat)!false> <#assign baseActivity=appCompat && (buildApi gte 22)> <#assign espresso=hasDependency('com.android.support.test.espresso:espresso-core', 'androidTestCompile')> <#assign supportRunner=hasDependency('com.android.support.test:runner', 'androidTestCompile')> <#assign testSupportLib=espresso && supportRunner> <global id="themeName" type="string" value="${themeName}" /> <global id="implicitParentTheme" type="boolean" value="${(themeNameNoActionBar?starts_with(themeName+'.'))?string}" /> <global id="themeNameNoActionBar" type="string" value="${themeNameNoActionBar}" /> <global id="themeExistsNoActionBar" type="boolean" value="${(theme.existsNoActionBar!false)?string}" /> <global id="themeNameAppBarOverlay" type="string" value="${theme.nameAppBarOverlay!'AppTheme.AppBarOverlay'}" /> <global id="themeExistsAppBarOverlay" type="boolean" value="${(theme.existsAppBarOverlay!false)?string}" /> <global id="themeNamePopupOverlay" type="string" value="${theme.namePopupOverlay!'AppTheme.PopupOverlay'}" /> <global id="themeExistsPopupOverlay" type="boolean" value="${(theme.existsPopupOverlay!false)?string}" /> <global id="appCompat" type="boolean" value="${appCompat?string}" /> <global id="baseActivity" type ="boolean" value="${baseActivity?string}"/> <global id="hasAppBar" type="boolean" value="${baseActivity?string}" /> <global id="hasNoActionBar" type="boolean" value="${baseActivity?string}" /> <global id="testSupportLib" type="boolean" value="${testSupportLib?string}" /> <global id="manifestOut" value="${manifestDir}" /> <global id="buildVersion" value="${buildApi}" /><#if !appCompat> <global id="superClass" type="string" value="Activity"/> <global id="superClassFqcn" type="string" value="android.app.Activity"/> <global id="Support" value="" /> <global id="actionBarClassFqcn" type = "string" value="android.app.ActionBar" /><#elseif baseActivity> <global id="superClass" type="string" value="BaseActivity"/> <global id="superClassFqcn" type="string" value="com.zaxxkj.utils.base.BaseActivity"/> <global id="Support" value="Support" /> <global id="actionBarClassFqcn" type = "string" value="android.support.v7.app.ActionBar" /><#else> <global id="superClass" type="string" value="ActionBarActivity"/> <global id="superClassFqcn" type="string" value="android.support.v7.app.ActionBarActivity"/> <global id="Support" value="Support" /> <global id="actionBarClassFqcn" type = "string" value="android.support.v7.app.ActionBar" /></#if> <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" /> <global id="resOut" value="${resDir}" /> <global id="menuName" value="${classToResource(activityClass!'')}" /> <global id="simpleName" value="${activityToLayout(activityClass!'')}" /> <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" /></globals>
新闻热点
疑难解答