Android Studio自定义模板

Android Studio自定义模板 写页面竟然可以如此轻松

1、概述

上一篇文章,已经初步对Android Studio的模板有了初步的介绍及使用,以及一些开源模板的推荐:

神奇的Android Studio Template

本文将对如何编写Template,进行详细的介绍(以activity模板为例)

2、模板的文件结构

学习编写模板最好的方式呢,就是参考IDE中已经提供的最简单的模板,那么在Android Studio中最简单的activity模板就是:Empty 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的界面就很好理解了,如图:

Configure 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中的变量进行替换。

那么整体的关系类似下图:


Template Variable Dataflow

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,效果如下:

Paste_Image.png

当我们点击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面板的信息。

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

推荐阅读更多精彩内容