第二十四章 Android 自定义模板

1. 概述

在读鸿洋大神的博客的时候,看到了自定义模板,然后网上查了一些资料,自己了解学习一下,希望和大家分享。我们在创建项目的时候,看到这样一个界面,这里的初始Activity就是模板中的Activity。


自定义模板

  其实模板不只是只有activity 还包括图片自由 布局文件 fragment service 以及一个类都可以制作成模板。慢慢了解,这里只介绍Activity。Android Studio 自定义模板位于 \plugins\android\lib\templates\activities目录下。我们创建Activity的时候,也可以将自己定义的模板放在哪个目录下,然后创建Activity的时候,就可以出现创建的Activity。

自定义模板

2. 自定义模板结构

学习自定义模板,我们需要参考IDE中的文件结构。Android Studio 中提供的最简单的Activity就是:Empty Activity 。


自定义模板结构

从图中可以看到每个文件夹的对应的插件:

  • root文件夹 存放对应源码的ftl文件,以及资源文件
  • globals.xml.ftl
  • recipe.xml.ftl
  • template.xml
  • template_blank_activity.png 效果缩略图

2.1 template.xml 中parameter标签,主要用于提供参数

打开文件

<?xml version="1.0"?>
<template
    format="5"
    revision="5"
    name="Empty Activity"
    minApi="9"
    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" />

    <parameter
        id="generateLayout"
        name="Generate Layout File"
        type="boolean"
        default="true"
        help="If true, a layout file will be generated" />

    <parameter
        id="layoutName"
        name="Layout Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${activityToLayout(activityClass)}"
        default="activity_main"
        visibility="generateLayout"
        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="backwardsCompatibility"
        name="Backwards Compatibility (AppCompat)"
        type="boolean"
        default="true"
        help="If false, this activity base class will be Activity instead of AppCompatActivity" />
    
    <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_blank_activity.png</thumb>
    </thumbs>

    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />

</template>
  • <template>中的name属性,对应新建Activity时显示的名字
  • <category>对应New的类别为Activity
  1. 最外层的template的属性有模板名,描述,api版本,格式大小
<template
    format="5"
    revision="5"
    name="Empty Activity"
    minApi="9"
    minBuildApi="14"
    description="Creates a new empty activity">
  1. parameter标签, 看到这个界面,大部分属性都应该能才出来了,我们重点看parameter,界面上每一个红色框出来的部分都对应一个parameter,部分属性介绍:
 <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" />
  • id :唯一标识,最终通过该属性的值,获取用户输入值(文本框内容,是否选中)
  • name:界面上的类似label的提示语
  • type : 输入值类型
  • constraints:填写值的约束
  • suggest:建议值,比如填写ActivityName的时候,会给出一个布局文件的建议值。
  • default:默认值
  • help:底部显示的提升语
  1. thumbs
    这个是显示预览效果图,可以更换

  2. 最后指定两个引用文件

<globals file="globals.xml.ftl" />
 <execute file="recipe.xml.ftl" />

2.2 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,默认值。
同理,我们可以通过id的值访问到该值,例如:
${hasNoActionBar}的值为false。

2.3 recipe.xml.ftl 主要用于生成我们实际需要的代码,资源文件等。例如,利用参数+MainActivity.java.ftl -> MainActivity.java;其实就是利用参数将ftl中的变量进行替换。

<?xml version="1.0"?> 
<recipe> 
//include语法,跟C中的include是一个意思,就是引用这个文件

 <#include "../common/recipe_manifest.xml.ftl" /> 

//if语法,这里代表了假如id为generateLayout的值为true,则往if里面走 

<#if generateLayout> 
 
    <#include "../common/recipe_simple.xml.ftl" /> 

//open语法,这里指打开${escapeXmlAttribute(resOut)}/layout/目录下的${layoutName}.xml文件,其中${escapeXmlAttribute(resOut)}/输出的目录就是项目中的res目录 
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" /> 

</#if> 
//instantiae语法,这里是将root/src/app_package/目录下的SimpleActivity.java.ftl解析成项目中${escapeXmlAttribute(srcOut)}/${activityClass}.java,其中${escapeXmlAttribute(srcOut)}/输出的目录就是项目中的src目录 
<instantiate from="root/src/app_package/SimpleActivity.java.ftl" 
               to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

 <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" /> 

 </recipe>

为了介绍,我将该xml中比较重要的几个标签都列出来了:

  • copy :从root中copy文件到我们的目标目录,比如我们的模板Activity需要使用一些图标,那么可能就需要使用copy标签将这些图标拷贝到我们的项目对应文件夹。
  • merge : 合并的意思,比如将我们使用到的strings.xml合并到我们的项目的stirngs.xml中
  • instantiate : 和copy类似,但是可以看到上例试将ftl->java文件的,也就是说中间会通过一个步骤,将ftl中的变量都换成对应的值,那么完整的流程是ftl->freemarker process -> java。
  • open:在代码生成后,打开指定的文件,比如我们新建一个Activity后,默认就会将该Activity打开。


图片来源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

2.4 Root文件夹

里面都是加了ftl的java文件和XML文件,所以我们用if来判断生成的方式。
两个步骤:
取值--->判断-->生成

xml文件中:

<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_${activityToLayout(activityClass)}"
            </#if>
            >
            <#if isLauncher>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            </#if>
        </activity>
    </application>

</manifest>

  1. 获取值,包括设定值和全局变量:

    设定值:activityClass获取,${activityClass}
    全局变量:isNewProject,hasNoActionBar

  2. if判断
    设定值:activityClass获取,${activityClass}
    全局变量:isNewProject,hasNoActionBar

3. FreeMark语法

上面我们已经基本了解模板生成的大致的流程以及涉及到的文件,大致了解了我们生成的源码或者xml文件,需要经过:

  • ftl->freemarker process->java/xml

比如我们有个变量user=zhy;
有个ftl文件内容:helloL${user}
最后经过freemarker的输出结果即为 hello:zhy

  • if语法
<#if generateLayout>
    //生成layout文件
</#if>

以SimpleActivity.java.ftl

  • \root\src\app_package
    这内部包含很多变量,这些变量的值一般来源于用户输入和global.xml.ftl中预定义的值,经过recipe.xml.ftl中instantiate标签的处理,将变量换成实际的值,即可在我们的项目的指定位置,得到我们期望的Activity。
package ${packageName};

import ${superClassFqcn};
import android.os.Bundle;
<#if includeCppSupport!false>
import android.widget.TextView;
</#if>
<#if applicationPackage?? && generateLayout>
import ${applicationPackage}.R;
</#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">
    }
<#include "../../../../common/jni_code_snippet.java.ftl">
}

创建之后的java文件

package com.demo.mystyletemplate;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}

流程大致可用如下图所示:


图片来源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

4. 创建自己的模板

在创建自己模板的时候,我们最好从原来的Templates的activities中拷贝一个出来,然后在这个基础上更改。

实现效果
4.1 globals.xml,这个文件可以直接拷贝过来,这里一个id simpleLayoutName,是Activity的layoutName
<?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>
4.2 template.xml的编写

每个parameter对应界面上的一个控件,控件的这个id最终可以得到用户输入值,后面会用于渲染ftl文件

<?xml version="1.0"?>
<template
    format="5"
    revision="7"
    name="Tab With ViewPager Activity"
    minApi="7"
    minBuildApi="14"
    description="Creates a new activity with viewpager and tabs">

    <category value="Activity" />
    <formfactor value="Mobile" />

    <parameter
        id="activityClass"
        name="Activity Name"
        type="string"
        constraints="class|unique|nonempty"
        suggest="${layoutToActivity(activityLayoutName)}"
        default="MainActivity"
        help="The name of the activity class to create" />

    <parameter
        id="activityLayoutName"
        name="Layout Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${activityToLayout(activityClass)}"
        default="activity_main"
        help="The name of the layout to create for the activity" />
    
    <parameter
        id="tabCount"
        name="Tab Count"
        type="string"
        constraints="nonempty"
        default="4"
        help="The count of tabs for ViewPager" />

    <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_tab_with_vp_activity.png</thumb>
    </thumbs>

    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />

</template>

4.3 recipe.xml 中定义的东西比较关键,例如将ftl->java,copy、合并资源文件等。

引入依赖

<?xml version="1.0"?>
<recipe>
    <#include "../common/recipe_manifest.xml.ftl" />

    <#if !appCompat && !(hasDependency('com.android.support:support-v4'))>
            <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
     </#if>

    <#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
           <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+" />
    </#if>

    <#if (buildApi gte 22) && appCompat && !(hasDependency('com.android.support:design'))>
        <dependency mavenUrl="com.android.support:design:${buildApi}.+" />
    </#if>

//省略其他
</recipe>

这里的这部分直接拷贝过来,我们导入support-v4,-v7和design包

<?xml version="1.0"?>
<recipe>
 ···

    <instantiate from="root/src/app_package/MainActivity.java.ftl"
        to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
              
      
    <instantiate from="root/res/layout/activity_main.xml.ftl"
        to="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
        

    <instantiate from="root/src/app_package/SimpleFragment.java.ftl"
        to="${escapeXmlAttribute(srcOut)}/fragment/SimpleFragment.java" /> 

    
        
    <instantiate from="root/src/app_package/Fragment1.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/fragment/Fragment1.java" /> 
      
    <instantiate from="root/src/app_package/Fragment2.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/fragment/Fragment2.java" />  
      
      
      <instantiate from="root/src/app_package/Fragment3.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/fragment/Fragment3.java" />  
      
      <instantiate from="root/src/app_package/Fragment4.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/fragment/Fragment4.java" />  
     
        
    <open file="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml"/>        

    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
    

</recipe>

包含多个instantiate标签,该标签很明显是将我们内置的ftl转化为当前项目有中的java类。

4.4 接下来是编写类
  • root/src/app_package/MainActivity.java.ftl
    注意不是.java文件而是.ftl文件,可以看到上面的代码基础上和Java代码没什么区别,实际上就是Java代码,把可变的部分都换成了${变量名}的方式而已。
package ${packageName};

import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;

import ${packageName}.fragment.SimpleFragment;

public class ${activityClass} extends AppCompatActivity {


    private TabLayout mTabLayout;
    private ViewPager mViewPager;
    private int mTabCount = ${tabCount};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.${activityLayoutName});

        mTabLayout = (TabLayout) findViewById(R.id.id_tablayout);
        mViewPager = (ViewPager) findViewById(R.id.id_viewpager);

        mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                switch (position){
                    case 0:
                         return  new Fragment1();
                    case 1:
                        return  new Fragment2();
                    case 2:
                        return  new Fragment2();
                    case 3:
                        return  new Fragment3();
                }
              return  null;
            }

            @Override
            public int getCount() {
                return mTabCount;
            }

            @Override
            public CharSequence getPageTitle(int position) {
                switch (position){
                    case 0:
                        return "教育";

                    case 1:
                        return "科技";

                    case 2:
                        return  "文化";

                    case 3:
                        return  "军事";
                }
                return null;
            }
        });

        mTabLayout.setupWithViewPager(mViewPager);
    }
}


例如:类名是用户填写的,我们就使用${activityClass}替代,其他同理。

相应的编写四个fragment.

package ${packageName}.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class Fragment1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
                             
        return inflater.inflate(R.layout.${layoutName}, container, false);
    }

}
4.5 编写的布局文件
  • root/res/layout/activity_main.xml.ftl
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:orientation="vertical"
    tools:context="${packageName}.${activityClass}">

    <android.support.design.widget.TabLayout
        android:id="@+id/id_tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/id_viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
    />
</LinearLayout>

推荐一些模板:
https://github.com/kanytu/Android-studio-material-template
https://github.com/gabrielemariotti/AndroidStudioTemplate
https://github.com/intrications/material-design-icons-adt-template
https://github.com/WanAndroid/AndroidStudioTemplates

参考链接:
http://www.jianshu.com/p/c76facb61d69
http://blog.csdn.net/lmj623565791/article/details/51635533

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

推荐阅读更多精彩内容