Android Studio的三种类型的模版(Templates)创建 如果说使用快捷键是程序员的刀🔪,那灵活的使用代码模版就应该是程序员的剑。
这里说的模版(Templates),是指在使用开发创建类文件,甚至是某些代码块时,IDE自动按照规定的格式创建出类或代码的功能。如果类中有大量相似代码,使用模版可以极大的提高开发效率,降低出错概率。
下面我们看一下如何在Android Studio上使用模版 ,我将讲述三种模版的创建,分别是:
使用Live Templates 创建代码块模版 
使用File and Code Templates 创建类模版 
基于FreeMarker 创建多文件模版 
1.代码块模版 java中经常需要定义这样的静态常量:
1 private  static  final  int  DEFAULT_VALUE  =  1 ;
使用Live Templates后,就可以直接输入const就可以快速定义静态常量:
其设置方法是,打开File->Setting(⌘+,),搜索“Live Templates”,打开如下的界面:
其中,1 指的是缩写和该缩写的描述;2 中输入的是模版的内容;3 中可以指定该模版应用生效的语言和场所,例如,可以限制该模版只应用到Java语言定义变量(declaration)的时候; 4 可以将模版中变化的部分定义为变量,如上图中的${name}和${value}     
上面例子中的const是默认的模版,你可以点击加号添加自己的模版,例如为kotlin定义一个tag常量的模版:
2.创建类模版 如果我经常创建Fragment,有些必填的步骤就可以放到模版里去
打开File->setting,找到File and Code Templates ,打开如下页面:
点击加号创建新的类模版 
在这填写模版的名字和生效的类型文件名 
模版的内容,可变的部分用变量代替 
 
举例一个简单的Fragment模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #if  (${PACKAGE_NAME} != "" )package  ${PACKAGE_NAME};#end import  android.os.Bundle;import  android.view.LayoutInflater;import  android.view.View;import  android.view.ViewGroup;import  androidx.annotation.NonNull;import  androidx.annotation.Nullable;import  androidx.fragment.app.Fragment;public  class  $ {NAME} extends  Fragment  {    private  static  final  String  TAG   =  "${NAME}" ;          public  static  ${NAME} newInstance() {         return  new  $ {NAME}();     }     @Nullable      @Override      public  View onCreateView (@NonNull  LayoutInflater inflater, @Nullable  ViewGroup container, @Nullable  Bundle savedInstanceState)  {         return  inflater.inflate(R.layout.${layout}, container, false );     } } 
其中${NAME}代表的是类名变量,${layout}代表了资源文件名变量,这些变量在创建文件时会要求手动填入。
使用时,在文件夹上点击右键 new 的时候,就可以看到自己定义的模版了:
点击后,填写一下自定义的变量,就可以生成模版文件了:
上方的layout就是在定义模版时定义的变量,而NAME变量是系统预留变量,会被影射成File name这个名字。关于定义类模版的具体用法,可以参考设置中其他的模版,或者参考文档:
https://www.jetbrains.com/help/idea/using-file-and-code-templates.html
一个小尾巴 
这里顺便说一下如何定义类的作者信息 模版:File and Code Template中有一个Includes标签,打开后是这个样子的:
这里填写一个模版,以后创建类的时候会自动把模版内容放在类名上方。一些像日期一样的变量可以在右下方查询。
3.创建多文件模版 上面两种方法都是可以在设置中搞定的,比较好理解,下面这种就稍微复杂一点了。
相信大家肯定用File -> new -> Activity -> EmptyActivity来创建一个新的页面,Android Studio会自动在manifest文件中注册Activity的名字,并创建好一个固定模版的Java和xml布局文件。
那么它是怎么实现的呢?我们可不可以像它一样也自定义创建多个不同类型的文件模版呢?答案是可以的。Android Studio使用的是Apache的FreeMarker模版引擎生成代码。 
Android Studio将所有的这种模版的配置文件放在下面路径:
Windows :{ANDROID_STUDIO_LOCATION}/plugins/android/lib/templates/
MacOS :Applications/Android Studio.app/Contents/plugins/android/lib/templates/
可以看下它的目录结构:
-activities
-gradle
-gradle-projects
-other
 
可以看出它大致对不同类型的模版进行了简单的分类,下面我们以就以activities为例,看一下它是怎么做的。
打开activities->EmptyActivity文件夹,可以看到创建模版所涉及的几个主要文件/夹:
1 2 3 4 5 6 7 8 9 ├── globals.xml.ftl ├── recipe.xml.ftl ├── root │   └── src │       └── app_package │           ├── SimpleActivity.java.ftl │           └── SimpleActivity.kt.ftl ├── template.xml └── template_blank_activity.png 
template.xml 在template.xml文件中,定义的是一些模版需要用到的变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?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"  />     ...// 省略一些参数// ...          <thumbs >                   <thumb > template_blank_activity.png</thumb >      </thumbs >      <globals  file ="globals.xml.ftl"  />      <execute  file ="recipe.xml.ftl"  />  </template > 
这里简单介绍几个重要的属性和标签:
template中的name属性 指定了该模版在Android Studio中显示的名字,如:File -> new -> Activity -> EmptyActivity category标签 指定了该模版放在Android Studio的那个分类中,如File -> new -> Activity  -> EmptyActivity。这里的分类名字可以自己指定。parameter标签 指定了该模版需要的参数,例如我在创建EmptyActivity时弹出的Wizard中的每一项其实都是在这里配置的(顺便说一下,标签配置的就是下图中的那个图片):  
globals标签和execute标签 分别制定了全局变量的配置文件和最核心的行为控制的文件(这里recipe.xml.ftl文件我也不知道应该叫什么,暂时这么称呼吧) 
recipe.xml.ftl 这是整个配置的核心文件,如果template.xml文件是Android工程中的Layout布局文件,那recipe.xml.ftl就是java文件,它告诉Android Studio需要按照一定的顺序做一些逻辑工作,例如,创建文件,在IDE中打开文件等。下面我们看一下其内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0"?> <#import "root://activities/common/kotlin_macros.ftl" as kt> <recipe >     <#include "../common/recipe_manifest.xml.ftl" />     <@kt.addAllKotlinDependencies /> <#if generateLayout>     <#in clude "../common/recipe_simple.xml.ftl" />     <open  file ="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"  />  </#if>     <instantiate  from ="root/src/app_package/SimpleActivity.${ktOrJavaExt}.ftl"                      to ="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}"  />     <open  file ="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}"  />  </recipe > 
上面的<#include/>标签主要做了一些复用的工作,例如<#include "../common/recipe_manifest.xml.ftl" />就是调用了recipe_manifest.xml.ftl文件在Manifest文件中插入了activity的信息。
我们主要关注下instantiate标签和open标签,这两句话翻译成自然语言就是:
根据模版文件:root/src/app_package/SimpleActivity.${ktOrJavaExt}.ftl生成类文件到${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}中去。
 
然后在Android Studio中打开文件:${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}
 
如果需要同时生成多个文件,就需要在这里用instantiate标签生成。那么生成文件的模版是在哪里定义的呢?下面说的root文件夹就是。
root文件夹(模版文件夹) root指的是工程的代码根目录,其内部是src、res甚至AndroidManifest.xml.ftl这样跟工程目录对应位置的模版文件。我们以root/src/app_package/SimpleActivity.java.ftl文件为例,看下其模版是怎么定义的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package  ${packageName};import  ${superClassFqcn};import  android.os.Bundle;<#if  (includeCppSupport!false ) && generateLayout> 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});        <#include "../../../../common/jni_code_usage.java.ftl" > <#elseif includeCppSupport!false >                  android.util.Log.d("${activityClass}" , stringFromJNI()); </#if >     } <#include "../../../../common/jni_code_snippet.java.ftl" > } 
可以看出,就是对一个Activity进行了模版化,其中很多变量提高了该模版的可扩展性,这里可以使用include引入其他模版的内容,或者使用if来进行条件判断,功能还是蛮强大的,更多的语法内容,可以参考FreeMarker模版引擎的文档:
https://freemarker.apache.org/
关于AndroidStudio的EmptyActivity模版创建分析就先到这里,下面我们实际的应用一下看看。
实战:创建列表Adapter模版 背景 目前做的工程中对RecycleView进行了封装,每次创建列表的Adapter时需要创建一系列文件才能开始写逻辑,这些文件包括:
列表的Adapter文件
数据Model的Pojo文件
列表中的View文件(ListItemView)
View文件的layout文件
 
为了减少创建列表Adapter的工作量,按照下面步骤为其创建一套类似于EmptyActivity的模版:
Step 1. 创建相关文件 在第三节提到的Templates路径中的other文件夹下新建一个文件夹,随意命名为“ListAdapter”,然后在内部创建下面的文件目录:
1 2 3 4 5 6 7 8 9 10 11 12 f├── globals.xml.ftl f├── recipe.xml.ftl d├── root d│   ├── res d│   │   └── layout f│   │       └── layout.xml.ftl d│   └── src d│       └── app_package f│           ├── Adapter.kt.ftl f│           ├── ItemView.kt.ftl f│           └── Model.kt.ftl f└── template.xml 
Step 2. 配置template.xml 要想灵活的填写创建Adapter时涉及到的文件的名字,就要将这些名字参数化,在创建之前让程序员填写,这就需要在template文件中配置相应的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?xml version="1.0"?> <template          format ="5"          revision ="1"          name ="Create RecycleView Adapter"          minApi ="9"          minBuildApi ="14"          description ="Create a RecycleView Adapter in DreamReader." >     <category  value ="DreamReader" />      <formfactor  value ="Mobile"  />           <parameter  id ="adapterName"       name ="Adapter Name"      type ="string"      constraints ="class|unique|nonempty"      default ="RecycleViewAdapter"      help ="The name of the adapter of RecycleView" />     <parameter  id ="layoutName"       name ="Layout Name"      type ="string"      constraints ="layout|nonempty|unique"      default ="view_list_item"      help ="The name of the layout file of RecycleView" />          <parameter  id ="itemViewName"       name ="Item View Name"      type ="string"      constraints ="class|unique|nonempty"      default ="ItemListView"      help ="The name of the item list view class of RecycleView" />          <parameter  id ="modelName"       name ="Model Name"      type ="string"      constraints ="class|unique|nonempty"      default ="ItemModel"      help ="The name of the data model class of RecycleView" />     <globals  file ="globals.xml.ftl" />      <execute  file ="recipe.xml.ftl" />  </template > 
从上面可以看出,我们自定义了一个名字为DreamReader的category,并将该模版的名字命名为:Create RecycleView Adapter.然后添加了四个参数:adapterName,layoutName,itemViewName,modelName。最后制定了global文件和execute文件的名字。
Step 3.配置global文件 global文件需要指定用到的全局变量,这里只需要用到src和res路径:
1 2 3 4 <globals >     <global  id ="srcOut"  value ="${srcDir}/${slashedPackageName(packageName)}"  />      <global  id ="resOut"  value ="${resDir}"  />  </globals > 
Step 4. 配置recipe文件 recipe文件中写明了具体要进行的操作,指定了具体模版文件的路径和名字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0"?> <#import "root://activities/common/kotlin_macros.ftl" as kt> <recipe >     <instantiate  from ="root/src/app_package/Adapter.kt.ftl"                      to ="${escapeXmlAttribute(srcOut)}/${adapterName}.kt"  />     <open  file ="${escapeXmlAttribute(srcOut)}/${adapterName}.kt"  />      <instantiate  from ="root/src/app_package/Model.kt.ftl"                      to ="${escapeXmlAttribute(srcOut)}/${modelName}.kt"  />     <open  file ="${escapeXmlAttribute(srcOut)}/${modelName}.kt"  />      <instantiate  from ="root/src/app_package/ItemView.kt.ftl"                      to ="${escapeXmlAttribute(srcOut)}/${itemViewName}.kt"  />     <open  file ="${escapeXmlAttribute(srcOut)}/${itemViewName}.kt"  />      <instantiate  from ="root/res/layout/layout.xml.ftl"                    to ="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"  />     <open  file ="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"  />  </recipe > 
上面代码生成(instantiate)了四个文件,并且让其全部在IDE中打开(open)。其中每个生成一个文件,都为其指定了具体的模版文件:Adapter.kt.ftl、Model.kt.ftl、ItemView.kt.ftl、layout.xml.ftl
Step 5. 编写具体的模版文件 下面贴出我写的具体的模版文件内容,由于我是用kotlin写的,所以后缀是kt.ftl,如果是java文件则应该是java.ftl后缀:
Adapter.kt.ftl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  ${escapeKotlinIdentifiers(packageName)}import  android.content.Contextimport  android.view.Viewimport  android.view.ViewGroupimport  com.tencent.news.pullrefreshrecyclerview.RecyclerViewAdapterEximport  com.tencent.news.pullrefreshrecyclerview.RecyclerViewHolderExclass  $val  context:Context) : RecyclerViewAdapterEx<${modelName}>() {    private  val  TYPE_DEFAULT = 0      override  fun  getNormalItemType (position: Int ) Int  {         return  TYPE_DEFAULT     }     override  fun  getLayoutViewByViewType (parent: ViewGroup ?, viewType: Int )          return  ${itemViewName}(context)     }     override  fun  bindData (holder: RecyclerViewHolderEx ?, data : ${modelName }?, dataPos: Int )          (holder?.itemView as ? ${itemViewName})?.setData(data , dataPos)     } } 
其中用到的变量名字都是在第二步中的template中定义的。
Model.kt.ftl
1 2 3 package  ${escapeKotlinIdentifiers(packageName)}data  class  $var  name:String)
ItemView.kt.ftl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package  ${escapeKotlinIdentifiers(packageName)}import  android.content.Contextimport  android.util.AttributeSetimport  android.view.LayoutInflaterimport  android.widget.RelativeLayout<#if  applicationPackage??> import  ${applicationPackage}.R</#if > import  kotlinx.android.synthetic.main.${layoutName}.*class  $@JvmOverloads  constructor (context: Context,                                                        attributeSet: AttributeSet? = null ,                                                         defStyleAttributeSet: Int  = 0 )     : RelativeLayout(context, attributeSet, defStyleAttributeSet) {     init  {         LayoutInflater.from(context).inflate(R.layout.${layoutName}, this , true )     }     fun  setData (data : ${modelName }?, index: Int )      } } 
layout.xml.ftl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8"?> <FrameLayout      xmlns:android ="http://schemas.android.com/apk/res/android"      xmlns:tools ="http://schemas.android.com/tools"      xmlns:app ="http://schemas.android.com/apk/res-auto"      android:layout_width ="match_parent"      android:layout_height ="match_parent" >     <TextView           android:id ="@+id/sample_text"          android:layout_width ="wrap_content"          android:layout_height ="wrap_content"          android:text ="Hello World!"          app:layout_constraintBottom_toBottomOf ="parent"          app:layout_constraintLeft_toLeftOf ="parent"          app:layout_constraintRight_toRightOf ="parent"          app:layout_constraintTop_toTopOf ="parent"  /> </FrameLayout > 
Step 6.使用 重启Android Studio,这样,再写列表时,就可以一键生成上面四个文件了,在New列表中可以看到我们定义的category和name:
点击后,弹出填写参数的窗口:
框中的内容都是我们自定义的内容,可以在这里填入想要的文件名,点击finish就生成了相应的文件:
总结 以上就是对Android Studio中模版用法的介绍和简单理解,可能有很多错误的地方,如有问题欢迎指正。本文主要讲了在Android Studio中创建Live Templates 代码块模版、File and Code Templates 类模版以及创建多文件模版,希望大家在工作中能巧用模版,提高效率。
除了Android Studio,其他JetBrain产品例如Intelli J、Clion、Pycharm等应该都是一样的,但是需要大家自己尝试一下,话说 Jetbrain真是在让程序员变懒这条路上一去不复返了😂。
参考 [1] https://medium.com/androidstarters/mastering-android-studio-templates-ed8fdd98cb78
[2] https://riggaroo.co.za/custom-file-template-group-android-studiointellij/
[3] https://www.jetbrains.com/help/idea/using-file-and-code-templates.html
[4] https://www.jetbrains.com/help/idea/using-live-templates.html
[5] https://freemarker.apache.org/