轉載自:http://blog.csdn.net/lmj623565791/article/details/51635533
1、概述
下方鏈接文章已經初步對Android Studio的模板有了初步的介紹與使用,以及一些開源模板的推薦:
本文將對如何編寫Template,進行詳細的介紹(以activity模版爲例)。
2、模板的文件結構
學習編寫模板最好的方式呢,就是參考IDE中已經提供的最簡單的模板,那麼在Android Studio中最簡單的activity模板就是:Empty Activity
了,我們打開該模板文件,首先對文件結構有個直觀的瞭解,如圖:
可以看到每個插件對應一個文件夾,文件夾包含
- template.xml
- globals.xml.ftl
- recipe.xml.ftl
- root文件夾 存放對應源碼的ftl文件,以及資源文件
- 效果縮略圖
下面我們逐一對上述每個文件的作用就行介紹。
2.1 template.xml
打開該文件,發現其主要內容如下:
<?xml version="1.0"?>
<template
name="Empty Activity"
minApi="7"
minBuildApi="14"
description="Creates a new empty activity">
<category value="Activity" />
<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>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</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
其中
<template>
中的name
屬性,對應新建Activity
時顯示的名字<category>
對應New的類別爲Activity
剩下的,對應我們AndroidStudio新建Empty Activity
的界面就非常好理解了,如圖:
看到這個界面,大部分屬性都應該能纔出來了,我們重點看parameter,界面上每一個紫色框出來的部分都對應一個parameter
,部分屬性介紹:
- id :唯一標識,最終通過該屬性的值,獲取用戶輸入值(文本框內容,是否選中)
- name:界面上的類似label的提示語
- type : 輸入值類型
- constraints:填寫值的約束
- suggest:建議值,比如填寫ActivityName的時候,會給出一個佈局文件的建議值。
- default:默認值
- help:底部顯示的提升語
這個部分對應界面還是非常好理解的,大家可以簡單的修改一些字符串,或者添加一個<parameter>
,重啓AS,看看效果。
template.xml的最下面的部分引入了globals.xml.ftl
和recipe.xml.ftl
。
這兩個我們會詳細介紹。
2.2 globals.xml.ftl
<?xml version="1.0"?>
<globals>
<global id="hasNoActionBar" type="boolean" value="false" />
<#include "../common/common_globals.xml.ftl" />
</globals>
- 1
- 2
- 3
- 4
- 5
通過名稱可以猜到它是用於定義一些全局的變量,可以看到其內部有<global>
標籤,分別定義id,type,默認值。
同理,我們可以通過id的值訪問到該值,例如:
${hasNoActionBar}
的值爲false。
2.3 recipe.xml.ftl
<recipe>
<copy from="root/res/drawable-hdpi"
to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
<merge from="root/${resIn}/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</recipe>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
爲了介紹,我將該xml中比較重要的幾個標籤都列出來了:
- copy :從root中copy文件到我們的目標目錄,比如我們的模板Activity需要使用一些圖標,那麼可能就需要使用copy標籤將這些圖標拷貝到我們的項目對應文件夾。
- merge : 合併的意思,比如將我們使用到的strings.xml合併到我們的項目的stirngs.xml中
- instantiate : 和copy類似,但是可以看到上例試將ftl->java文件的,也就是說中間會通過一個步驟,將ftl中的變量都換成對應的值,那麼完整的流程是
ftl->freemarker process -> java
。 - open:在代碼生成後,打開指定的文件,比如我們新建一個Activity後,默認就會將該Activity打開。
在介紹instantiate時,涉及到了freemarker,不可避免的需要對它進行簡單的介紹。
目前我們已經基本瞭解了一個模板其內部的文件結構了,以及每個文件大致包含的東西,我們簡單做個總結:
- template 中parameter標籤,主要用於提供參數
- global.xml.ftl 主要用於提供參數
- recipe.xml.ftl 主要用於生成我們實際需要的代碼,資源文件等;例如,利用參數+MainActivity.java.ftl -> MainActivity.java;其實就是利用參數將ftl中的變量進行替換。
那麼整體的關係類似下圖:
圖片來源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
3、簡單的freemarker語法
上面我們已經基本瞭解模板生成的大致的流程以及涉及到的文件,大致瞭解了我們生成的源碼或者xml文件,需要經過:
ftl->freemarker process->java/xml
這樣的流程,那麼我們必須對freemarker有個簡單的瞭解。
- 非常簡單的例子
其實非常簡單:
比如我們有個變量user=zhy;
有個ftl文件內容:helloL${user}
最後經過freemarker的輸出結果即爲 hello:zhy
- 1
- 2
- 3
- if語法
<#if generateLayout>
//生成layout文件
</#if>
- 1
- 2
- 3
看一眼就知道大概的意思了~有一定的編程經驗,即使不知道這個叫freemarker,對於這些簡單的語法還是能看懂的。
我們最後以Empty Activity模板的中的SimpleActivity爲例:
root/src/app_package/SimpleActivity.java.ftl
package ${packageName};
import ${superClassFqcn};
import android.os.Bundle;
public class ${</span>activityClass} <span class="hljs-keyword">extends</span> ${superClass} {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
<#if generateLayout>
setContentView(R.layout.${layoutName});
</#if>
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
可以看到其內部包含很多變量,這些變量的值一般來源於用戶輸入和global.xml.ftl中預定義的值,經過recipe.xml.ftl中instantiate標籤的處理,將變量換成實際的值,即可在我們的項目的指定位置,得到我們期望的Activity。
流程大致可用下圖說明:
圖片來源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
看到這,最起碼理解了,當我們選擇創建不同的Activity類型,最終得到的不同的效果其中的原理原來在這。
4、具體的模板實例
瞭解了基本的理論之後,下面我們可以通過一個實例來將上面的知識點整合。
我們編寫一個Activity模板叫做:ViewPagerWithTabActivity
,用於創建一個攜帶TabLayout的ViewPager,效果如下:
當我們點擊New->Activity->ViewPagerWithTabActivity 就可能完成上面的Activity的創建,而避免了編寫佈局文件,引入design庫,以及一些簡單的編碼。
是不是感覺還是不錯的,大家可以針對自己的需求,按照規範的格式隨意定製模板。
建議大家copy一個現有的模板,再其基礎上修改即可,比如本例是在Empty Activity基礎之上修改的。
下面我們看上例的具體的實現。
4.1 template.xml的編寫
通過上面的學習我們知道template.xml中可以定義我們創建面板的控件佈局等,本例我們創建Activity的界面如下:
對應的template.xml如下:
<?xml version="1.0"?>
<template>
<category value="Activity" />
<formfactor value="Mobile" />
<parameter id="activityClass"/>
<parameter id="activityLayoutName"/>
<parameter id="tabCount"/>
<parameter id="isLauncher"/>
<parameter id="packageName"/>
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_vp_with_tab_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
經過前面的學習應該很好理解,每個parameter對應界面上的一個控件,控件的這個id最終可以得到用戶輸入值,後面會用於渲染ftl文件。
4.2 用到的類
本例中最終需要生成Fragment和Activity,也就是說對應會有兩個ftl文件分別用於最終生成這兩個類。
root/src/app_package/MainActivity.java.ftl
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)
{
return new SimpleFragment();
}
@Override
public int getCount()
{
return mTabCount;
}
@Override
public CharSequence getPageTitle(int position)
{
return "Tab:" + position;
}
});
mTabLayout.setupWithViewPager(mViewPager);
}
}
- 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
- 46
- 47
- 48
- 49
注意不是.java文件而是.ftl文件,可以看到上面的代碼基礎上和Java代碼沒什麼區別,實際上就是Java代碼,把可變的部分都換成了${變量名}
的方式而已。
例如:類名是用戶填寫的,我們就使用${activityClass}
替代,其他同理。
root/src/app_package/SimpleFragment.java.ftl
package ${packageName}.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by zhy on 16/6/6.
*/
public class SimpleFragment extends Fragment
{
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
TextView tv = new TextView(getActivity());
tv.setGravity(Gravity.CENTER);
tv.setTextSize(40);
tv.setText("just test");
return tv ;
}
}
- 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
這個類更簡單,除了package是動態的,其他都寫好了,主要用於作爲ViewPager的Fragment Item.
4.3 用到的佈局文件
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
發現和我們真正編寫的Activity並無多大區別。
看完用到的類和佈局文件的ftl,大家心裏應該有個底了,這模板幾乎就和我們平時寫的Java類一樣,只是根據用戶在新建Actiivty界面所輸入的參數進行替換一些變量或者做一些簡單的操作而已。
4.4 recipe.xml.ftl的編寫
除了template.xml還有globals.xml.ftl和recipe.xml.ftl,globals.xml.ftl中基本上沒有修改任何內容就不介紹了。
recipe.xml.ftl中定義的東西比較關鍵,例如將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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
本例依賴v4、v7、和design庫,我們需要在這裏定義引入;
你可能會問,這個引入的代碼看起來挺複雜,你怎麼知道這樣寫呢?
其實我也不知道怎麼寫,但是我可以打開IDE自帶模板參考參考,copy過來就好了。
- 剩下的內容
<?xml version="1.0"?>
<recipe>
//省略依賴
<instantiate from="root/src/app_package/MainActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<instantiate from="root/src/app_package/SimpleFragment.java.ftl"
to="${escapeXmlAttribute(srcOut)}/fragment/SimpleFragment.java" />
<instantiate from="root/res/layout/activity_main.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
<open file="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml"/>
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</recipe>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
可以看到包含多個instantiate標籤,該標籤很明顯是將我們內置的ftl轉化爲當前項目有中的java類。
上例,轉化了
${activityClass}.java
fragment/SimpleFragment.java"
/layout/${activityLayoutName}.xml
剩下是兩個open標籤,主要就是用於新建完成後,自動打開該文件。
本例新建完成後,Activity和其對應的佈局文件都會自動打開。
恩,這裏沒用到merge
標籤,不過也很簡單,假設你fragment上顯示的文本,你可以定義到一個strings.xml裏面,最後你需要將這個strings.xml合併到當前項目的strings.xml就可以使用merge標籤(內置模板很多用了merge標籤,參考下,抄一抄就搞定了)。
5、總結
本文我們首先詳細介紹了一個模板文件夾下各個文件以及其內部的標籤的作用,然後通過一個具體的實例,來演示如何編寫一個activity模板。當然,文中一些細節並沒有談到,對於這些不要擔心,你有什麼需求,你就想哪個內置的模板好像有類似的需求,看它的實現,copy它的相關代碼改一改就好了,沒有必要去被各種文件的編寫,這種東西copy修改就好了。
測試過程中,需要重啓Android Studio,如果有問題,記得查看Event Log面板的信息。
此外,模板這個東西我覺得最好的集大家的力量,所以我在github建立了一個組織倉庫,https://github.com/WanAndroid/AndroidStudioTemplates,如果你對模板有興趣或者想要將自己的模板文件與他人共享,可以加入這個組織,然後分享你的代碼,本文的例子也在其中。