在上一篇講到的自定義AndroidStudio文件模板的方法,但這種方法雖然簡單卻只能一次生成一個文件。有沒有方法能一鍵生成一套文件呢?其實AndroidStudio已經提供了這樣的功能,而且我們也經常在用。
如圖紅框上的文件就是AndroidStudio已經提供的模板文件組,最常用的就是Activity了。但我們使用這裏的模板新建一個Activity,AndroidStudio就會自動爲我們新建一個Activity的java類文件、和Layout佈局xml文件,同時還會貼心地爲我們在Manifest文件中註冊我們的Activity。這一切是怎麼做到的呢?
溯源
我想,這些模板肯定是被放在某些地方的文件集,說不定文件夾的名字就對應這裏模板的名字呢!用強大的文件搜索軟件EveryThing搜一下這裏的其中一個模板的名字,就會發現,這些模板文件就存在我們AndroidStudio(IDEA)的安裝目錄中。
一下是在Windows下存放文件模板的目錄:
<AndroidStudio安裝目錄>\plugins\android\lib\templates
在activitys目錄下就存放着相應的Activity模板:
探祕
好奇心是學習的動力,我迫不及待地打開BasicActivity目錄:
發現有一個root目錄,若干.ftl文件和若干圖片。
這些文件怎麼關聯在一起,怎麼發揮作用,怎麼接受用戶輸入並生成文件的呢?
經過Google後,我發現了這張圖:
原來如此,FreeMarker接受用戶輸入的參數和兩種.ftl文件,輸出生成的代碼文件。問題來了,what is FreeMarker?能吃嗎?
FreeMarker是一款模板引擎: 即一種基於模板和要改變的數據, 並用來生成輸出文本(HTML網頁、電子郵件、配置文件、源代碼等)的通用工具。--源自百度百科
FreeMarker很符合我們的需求,看來這個解析器是集成在AndroidStudio中的。有人可能會吐槽,搞個模板還要專門跑去學FreeMarker嗎?其實,就算不懂FreeMarker語法(最好有一丁點了解)也可以輕鬆搞一個簡單的文件模板,因爲AndroidStudio提供很多現成的Demo是最好的學習資料,創造往往從模仿開始!
先從這個template.xml文件開始:
<?xml version="1.0"?>
<template
format="5"
revision="6"
name="Basic Activity"
minApi="7"
minBuildApi="14"
requireAppTheme="true"
description="Creates a new basic activity with an app bar.">
<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/>標籤-------------------------------
<parameter
id="useFragment"
name="Use a Fragment"
type="boolean"
default="false"
help="If true, the content will be a fragment"/>
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_basic_activity.png</thumb>
<!-- attributes act as selectors based on chosen parameters -->
<thumb useFragment="true">template_basic_activity_fragment.png</thumb>
</thumbs>
<globals file="globals.xml.ftl"/>
<execute file="recipe.xml.ftl"/>
</template>
這個文件在AndroidStudio啓動時加載的,所以要修改這個文件需要重啓AndroidStudio才能生效。
< template>可以配置模板的名稱、 描述等信息;
其子標籤內:
<category value="Activity"/>
用於將模板分類<parameter>
用於添加用戶輸入參數,這個標籤可以配置以下屬性:
id
給參數設置一個唯一的標示,用於在後面編寫單個文件模板時結合${}
填充拼接到代碼中。name
和help
給用戶輸入參數時起到提示的作用default
給參數設置默認值type
當值爲string時該參數在輸入對話框對應爲一個文本輸入框,當值爲boolean時該參數在輸入對話框對應一個勾選框。visibility
設置參數什麼時候可見,什麼時候不可見。比如我想id爲a的參數勾選之後該參數才顯示出來,就可以設置成visibility="a"
<thumbs>
是爲模板設置圖片的<globals file="globals.xml.ftl"/>
爲這個模板設置一些全局參數<execute file="recipe.xml.ftl"/>
最關鍵的是這個標籤,當用戶輸入完參數後點擊OK後,第一時間就會觸發這個文件。
你看,根據這個文件,AndroidStudio對應就會生成一個這樣的對話框:
recipe.xml.ftl
<?xml version="1.0"?>
<recipe>
<#include "../common/recipe_manifest.xml.ftl" />
<#if useFragment>
<#include "recipe_fragment.xml.ftl" />
<#else>
<#include "../common/recipe_simple.xml.ftl" />
</#if>
<#if hasAppBar>
<#include "../common/recipe_app_bar.xml.ftl" />
</#if>
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
</recipe>
這個文件可以理解成一個執行單元。
- 可以通過
<#include>
請其他執行單元來幫忙,共同完成生成模板的工作。 <#if booleanParam>
和<#else>
<#elseif>
可以通過前面用戶輸入參數的布爾值動態調整文件的輸出。<instantiate from="" to="" />
給單個文件模板與實際生成文件建立一對一的映射<open file="" />
生成文件後,AndroidStudio會自動打開這些文件。
root目錄
root目錄下存放着單個模板文件,這些文件其實也是一個執行單元,這不過這些執行單元的產物只有一個文件。
比如這個SimpleActivity.java.ftl:
package ${packageName};
import android.os.Bundle;
<#if hasAppBar>
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
<#else>
import ${superClassFqcn};
</#if>
<#if isNewProject>
import android.view.Menu;
import android.view.MenuItem;
</#if>
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>
public class ${activityClass} extends ${superClass} {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
<#if hasAppBar>
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
</#if>
<#if parentActivityClass != "">
get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
</#if>
}
<#if isNewProject>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.${menuName}, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
</#if>
}
這裏也有一些<#if>
等條件判斷標籤用於動態生成代碼,${param}
這些參數就來自於前面提到的template.xml和globals.xml.ftl文件中。
瞭解到這個程度,我們就可搞一個簡單實用的模板了!
實踐
需求:自定義一個文件模板,一鍵生成ListView或RecyclerView的Adapter。
我將這個模板歸位Other類,所以在Other目錄下,新建一個Adapter目錄:
AndroidStudio的安裝目錄\plugins\android\lib\templates\other\Adapter
新建template.xml,設置用戶輸入的參數:
<?xml version="1.0"?>
<template
format="5"
revision="5"
name="Adapter"
description="Creates Adapter">
<category value="Other" />
<parameter
id="adapterName"
name="Adapter Name"
type="string"
constraints="unique|nonempty"
default="Main"
help="The name should'n contain 'Adapter'" />
<parameter
id="layoutId"
name="Layout id"
type="string"
help="this id should'n contain 'R.layout'" />
<parameter
id="itemType"
name="Data type of item"
type="string"
constraints="unique|nonempty"
default="String"
help="The data type of each item in adapter." />
<parameter
id="forRecyclerView"
name="For RecyclerView"
type="boolean"
default="true"
help="If true, it will generate adapter for RecyclerView."/>
<parameter
id="rvNative"
name="generate Native Adapter"
type="boolean"
default="true"
visibility="forRecyclerView"
help="If true, the Native Adapter for RecyclerView are generated automatically."/>
<parameter
id="rvWrap"
name="generate wrapped Adapter"
type="boolean"
default="false"
visibility="forRecyclerView"
help="If true, the wrapped Adapter for RecyclerView are generated automatically."/>
<parameter
id="rvWithBase"
name="With BaseAdapter"
type="boolean"
default="false"
visibility="rvWrap"
help="If true, it will generate BaseAdapter for RecyclerView."/>
<parameter
id="forListView"
name="For ListView"
type="boolean"
default="false"
help="If true, it will generate adapter for ListView."/>
<parameter
id="lvNative"
name="generate Native Adapter"
type="boolean"
default="true"
visibility="forListView"
help="If true, the Native Adapter for ListView are generated automatically."/>
<parameter
id="lvWrap"
name="generate wrapped Adapter"
type="boolean"
default="false"
visibility="forListView"
help="If true, the wrapped Adapter for ListView are generated automatically."/>
<parameter
id="lvWithBase"
name="With BaseAdapter"
type="boolean"
default="false"
visibility="lvWrap"
help="If true, it will generate BaseAdapter for ListView."/>
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_adapter.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
效果圖:
recipe.xml.ftl:
<?xml version="1.0"?>
<recipe>
<#if forListView>
<#include "recipe_listview.xml.ftl" />
</#if>
<#if forRecyclerView>
<#include "recipe_recyclerview.xml.ftl" />
</#if>
<open file="${escapeXmlAttribute(srcOut)}/${adapterName}Adapter.java" />
</recipe>
globals.xml.ftl:
<?xml version="1.0"?>
<globals>
<global id="resOut" value="${resDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
在recipe_listview.xml.ftl和recipe_recyclervewview.xml.ftl文件中,分別生成ListView和RecyclerView的Adapter。
在root目錄中
一切就緒後,重啓AndroidStudio後,在工程目錄右鍵-> new :
具體代碼已上傳到Github。
總結
本文實現的Adapter只是拋磚引玉而已,AndroidStudio這個自定義文件模板的功能是十分強大又實用的,我們平時開發時可以發揮想象力,開發一些文件模板,甚至是工程模板,大大加快開發效率。
參考資料:
Tutorial How To Create Custom Android Code Templates
How to create a group of File Templates in Android Studio – Part 3