Android模板製作

http://www.cnblogs.com/fqyi/p/6506960.html

本文詳細介紹模板相關的知識和如何製作Android模版及使用,便於較少不必要的重複性工作。比如我在工作中如果要創建一個新的模塊,就不要需要創建MVP相關的幾個類:Model、View、Presenter、Entity等。

本文專門介紹和模板相關的知識,那麼問題來了:

  1. 模板是什麼
  2. 模板使用位置
  3. 模板如何創建(包含模板存放位置)
  4. 模板如何使用

接下來,我就按照以上順序爲大家解讀看起來高大上的模板

警告

本文所有模板路徑均爲Mac下的路徑,Windows用戶也可以查看路徑中的相關信息,進而快速定位。

模板是什麼

個人理解:模板即爲了幫助人們簡化某些固定而繁瑣的操作而製作的工具,用於快速實現這些固定而繁瑣的操作。

模板使用位置

當我們在使用AndroidStudio進行開發的時候,將鼠標選中工程項目,然後右擊可以在New選項下面看到很多AndroidStudio提供給我們的模板類別,例如:Activity、AIDL等。具體可看下圖:

工程右擊-New

細心的你會發現在這些模板的上面有一個選項:Edit File Templates...,如下圖所示:

Edit-File-Templates

點擊這個選項,會進入自定義模板頁面,其中內置的變量在頁面下方都有解釋,是不是很方便,但是它有一個致命的缺點:一次只能創建一個java文件。具體可看下圖:

Edit-File-Templates-in

因爲覺得這個很簡單,所以我就不做過多闡述了,接下來我就仔細闡述一下,如何一次創建多個java文件,而且還可以選擇是否包含xml文件。

模板如何創建(包含模板存放位置)

警告

如果直接複製相關代碼的話,請注意其中的註釋,可能會帶來一些問題,如果出現問題,可以把#開頭的註釋去除,再嘗試!!!

如果不懂上面這段話的意思的話,可以先行跳過。

FreeMarker

AndroidStudio的模板是使用FreeMarker模板引擎製作的,有興趣的可以瞭解一下。

案例&解答

案例:

由於現在的項目使用的是類MVP架構,所以基本上每個模塊都需要entity、request、activity、presenter、viewmodel這五個類,無論是登錄註冊模塊,還是商品詳情頁、首頁、收益頁面等模塊,都無法擺脫這幾個類,因此準備爲這個類MVP架構製作一個通用模板。

解答:

製作好模板之後,我想說,其實很簡單,只是把會變化的部分用${...}替換罷了,不過在這裏我們還是老老實實的從頭開始吧!

步驟

模板存放位置

首頁我們進入AndroidStudio安裝目錄下的/plugins/android/lib/templates文件夾,這就是AndroidStudio模板文件的目錄了,到這裏你可能還有所迷惑,因爲你沒有發現像我剛開始所說的Activity、AIDL等模板文件,沒關係,你再進入activities文件夾下面就可以看到Activity的相關模板了,進入other文件夾就可以看到AIDL的相關模板了。

模板副本

這裏我們選擇activities文件夾,然後你是不是覺得手足無措,不知道如何下手?其實一開始我也不知道怎麼做,但是沒關係,AndroidStudio不是已經提供給我們這麼多模板了麼,爲了簡單起見,我們在這裏拷貝一份EmptyActivity,並將其重命名爲MVPActivity放在當前目錄下

目錄結構

打開文件夾後,我們看到以下目錄結構:

EmptyActivity
  |----globals.xml.ftl # 全局變量文件
  |----recipe.xml.ftl  # 配置要引用的模板路徑以及生成文件的路徑
  |----root
    |----src
      |----app_package
        |----SimpleActivity.java.ftl # 模板文件
  |----template_blank_activity.png   # 創建模板時界面左邊的預覽圖
  |----template.xml # 模板的配置信息以及要輸入的參數

接下我們可以根據目錄結構順序(建議按以下順序看),打開看一下,這裏大致介紹一下:

globals.xml.ftl

globals.xml.ftl中都是類似

<global id="hasNoActionBar" type="boolean" value="false" />

這樣的語句,顯然它的意思就是我定義了一個全局變量hasNoActionBar,它的類型是boolean,默認值爲false。

recipe.xml.ftl

recipe.xml.ftl稍微有些複雜,這裏講解以下instantiate、open等幾個重要參數:

copy:複製--將from中的文件複製到to路徑下,但並不會將ftl中得變量進行轉換,即如果源文件中的類名爲${activityClass},複製過後類名還是${activityClass}轉換爲我們需要的類名。

merge:合併--將from中的文件合併到to路徑下的文件中。

instantiate:和copy類似,也是將from中的文件複製到to路徑下,但是它會將${activityClass}轉換爲我們需要的類名。其實有這樣一個過程:ftl->freemarker process -> java

open:代碼生成後,打開file中指定的文件。

SimpleActivity.java.ftl

打開SimpleActivity.java.ftl文件,會發現和我們創建Activity類後及其類似,只是把包名、類名、佈局名等用${...}替換了,其實${...}中得內容都是id名,這裏不做過多闡述,我們繼續往下看。

template.xml

template.xml:打開以後你會發現這個文件好長,看來是重頭戲了!!!是的,我們來詳細解讀一下:

一眼看去是不是和AndroidManifest.xml中得Application節點中的內容結構很相似(包括Application節點)

<?xml version="1.0"?>
<template
    format="5"   # The template format version that this template adheres to. Should be 3
    revision="5" # 可選,當你想更新模板的時候可以以整數的形式增加此模板的版本號
    name="Empty Activity" # 模板顯示的名字
    minApi="7"   # 可選,模板所需的最小API值,IDE將確保在實例化模板之前,目標工程的minSdkVersion不低於這個值
    minBuildApi="14"      # 可選,模板所需的最小編譯API,值爲API級別,IDE將確保在實例化模板之前,項目工程的API等級大於或等於這個值
    description="Creates a new empty activity"> # 模板的描述信息

    <category value="Activity" /> # 模板類型,用於在菜單欄File-New下顯示,如Activity、AIDL等
    <formfactor value="Mobile" /> # 如同我們在創建module時所顯示的類型,如:Wear、TV等。

    <parameter
        id="activityClass"   # 唯一標示,在ftl文件中可以用${activityClass}獲取參數值
        name="Activity Name" # 創建模板時在文本框左邊顯示的該文本框名稱
        type="string"        # 這個參數的類型,如:string, boolean, enum等
        constraints="class|unique|nonempty"       # 可選,這個參數的約束類型,可用|符號聯合使用,constraints值類型大全請看4.5
        suggest="${layoutToActivity(layoutName)}" # 可選,自動提示,比如輸入layout的值可以自動生成activityClass
        default="MainActivity" # 可選,參數默認值,創建模板時在文本框中顯示,相當於hint
        help="The name of the activity class to create" /> # 創建模板時,選中文本框後,在底部顯示的關於該文本框的幫助信息

    <!-- 128x128 thumbnails relative to template.xml -->
    <thumbs>
        <!-- default thumbnail is required -->
        <thumb>template_blank_activity.png</thumb> # 可選,用於創建模板時,在左邊顯示名爲template_blank_activity的預覽圖片
    </thumbs>

    <globals file="globals.xml.ftl" /> # 可選,將工程定義的全局變量包含進來
    <execute file="recipe.xml.ftl" />  # 開始執行模板渲染

</template>

constraints值類型大全

Valid constraint types are:
    nonempty — the value must not be empty
    apilevel — the value should represent a numeric API level
    package — the value should represent a valid Java package name
    class — the value should represent a valid Java class name
    activity — the value should represent a fully-qualified activity class name
    layout — the value should represent a valid layout resource name
    drawable — the value should represent a valid drawable resource name
    string — the value should represent a valid string resource name
    id — the value should represent a valid id resource name
    unique — the value must be unique; this constraint only makes sense when other constraints are specified, such as layout, which would mean that the value should not represent an existing layout resource name
    exists — the value must already exist; this constraint only makes sense when other constraints are specified, such as layout, which would mean that the value should represent an existing layout resource name

template.xml製作

到這裏相信大家對template.xml文件有了一定的瞭解了,好了,讓我們來大幹一場吧!

MVP版template.xml

既然這裏詳細的講解了template.xml文件,我們先從template.xml文件入手吧,這裏我就不一個個細說了,直接上完整代碼:

<?xml version="1.0"?>
<template
    format="2"          # 可修改,此處已修改
    revision="2"        # 可修改,此處已修改
    name="MVP Activity" # 需要修改
    minApi="7"          # 可修改
    minBuildApi="14"    # 可修改
    description="Creates a new MVP activity"> # 需要修改

    <category value="AAShowJoyMVP" /> # 可修改,此處已修改
    <formfactor value="Mobile" />     # 一般不修改

    <parameter          # Activity類
        id="activityClass"   # 可修改
        name="Activity Name" # 可修改
        type="string"        # 一般不修改
        constraints="class|unique|nonempty" # 一般不修改
        default="TestActivity" # 可修改,此處已修改
        help="The name of the activity class to create" /> # 可修改,此處未修改

    <parameter          # Activity類的佈局文件
        id="layoutName"
        name="Layout Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${classToResource(activityClass)}_activity" # 可修改,此處已修改,若不明白可以跳過,之後會有詳解!!!
        default="test_activity"
        help="The name of the layout to create for the activity" />

    <parameter          # 是否作爲啓動Activity
        id="isLauncher"
        name="Launcher Activity"
        type="boolean"
        default="false" # 默認非啓動Activity
        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.showjoy.shop" />

    <parameter          # viewModel類
        id="viewModelClass"
        name="View Model Name"
        type="string"
        constraints="class|nonempty|unique"
        default="TestViewModel"
        suggest="${underscoreToCamelCase(classToResource(activityClass))}ViewModel" # 此類同佈局文件,之後會有詳解!!!
        help="The name of the ViewModel to create" />

    <parameter          # presenter類
        id="presenterClass"
        name="Presenter Name"
        type="string"
        constraints="class|nonempty|unique"
        default="TestPresenter"
        suggest="${underscoreToCamelCase(classToResource(activityClass))}Presenter"
        help="The name of the Presenter to create" />

    <parameter          # request類
        id="requestClass"
        name="Request Name"
        type="string"
        constraints="class|nonempty|unique"
        default="TestRequest"
        suggest="${underscoreToCamelCase(classToResource(activityClass))}Request"
        help="The name of the Request to create" />

    <parameter          # entity類
        id="entityClass"
        name="Entity Name"
        type="string"
        constraints="class|nonempty|unique"
        default="TestEntity"
        suggest="${underscoreToCamelCase(classToResource(activityClass))}Entity"
        help="The name of the Entity to create" />


    <globals file="globals.xml.ftl" /> # 一般不修改
    <execute file="recipe.xml.ftl" />  # 一般不修改

</template>

template.xml文件的使用到這裏就結束了,還是比較簡單的,以下闡述之前所留下的兩個問題:

(1)

suggest="${classToResource(activityClass)}_activity"

classToResource(activityClass):這句話的意思是,當我們在創建該模板後,在activityClass對應的文本框中輸入某個值,比如:test,它會直接在layoutName對應的文本框中顯示,即:test,所以以完整的語句(1)而言,此時layoutName對應的文本框中顯示的應該是test_activity。

(2)

suggest="${underscoreToCamelCase(classToResource(activityClass))}ViewModel"

classToResource(activityClass)在(1)中描述的已經很清楚了,即爲test,那麼underscoreToCamelCase又是什麼意思呢?其實就是將test轉換爲駝峯命名的方法,即Test。所以以完整的語句(2)而言,此時viewModelClass對應的文本框中顯示的應該是TestViewModel。

如果你覺得文字描述過於繁瑣,仍然看不懂的話,可以查看以下gif:

AndroidStudio自帶模板列表-suggest

MVP版目錄結構

接下來我們就可以把要製作成模板的類,拷貝到相應的文件夾中,此時的目錄結構爲:

MVPActivity
  |----globals.xml.filter
  |----recipe.xml.ftl
  |----activity_layout_recipe.xml.filter # 此文件與recipe類似,只是因爲解耦思想,所以將class和layout分別引入
  |----root
    |----src
      |----app_package
        |----classes
          |----Activity.java.ftl
          |----Entity.java.ftl
          |----Presenter.java.ftl
          |----Request.java.ftl
          |----ViewModel.java.ftl
        |----layout
          |----activity_layout.xml.ftl
  |----template.xml

Request.java.ftl

爲了方便而又全面的進行講解,此處我們以Request.java.ftl文件爲例,這裏我就直接上全部代碼了:

package ${packageName}.request;                # ${packageName}對應的是template.xml文件中id爲packageName的參數設置的字符串,如果該類不在包名根目錄下,可以在後面添加相應的module名。

import android.support.annotation.NonNull;     # 如果包名中未涉及到在創建模板時設置的包名和類名,則無需修改
import ${packageName}.entities.${entityClass}; # 如果包名中涉及到在創建模板時設置的包名和類名,則只需相對應的進行修改即可

/**
 * 將以下涉及到在創建模板時設置的包名和類名,進行如下相對應的替換即可,佈局文件也是這樣替換的!!!
 */
public class ${requestClass} extends SHGetRequest<${entityClass}> {
    @Override
    protected Class<${entityClass}> getDataClass() {
        return ${entityClass}.class;
    }

    @Override
    protected TypeReference<${entityClass}> getDataTypeReference() {
        return null;
    }

    @NonNull
    @Override
    protected String getRequestUrl() {
        return null;
    }
}

佈局文件

接下來我們來看一下佈局文件的替換:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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"
    tools:context="${relativePackage}.${activityClass}">

</RelativeLayout>

雖然說tools命名空間一般都是可有可無的,這裏爲了全面,也講述以下,你應該發現了一個從未見過的id:relativePackage,不用迷惑,估計你也想到了,其實我就是在globals.xml.ftl文件中定義了一個全局變量而已,它的值默認爲包名,具體代碼如下:

<global id="relativePackage" type="string" value="${packageName}"/>

globals.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" />
    <global id="relativePackage" type="string" value="${packageName}"/>
    <#include "../common/common_globals.xml.ftl" />
</globals>

其實並沒有什麼,global代表的都是全局變量,#include代表的是引用的文件,相當於繼承。

recipe.xml.ftl

然後就只有recipe.xml.ftl文件了,也快結束了:

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

    # 引入同級目錄中的activity_layout_recipe.xml.ftl文件,其內容會在下一節中講述
    <#include "activity_layout_recipe.xml.ftl" />

    <instantiate from="src/app_package/classes/Activity.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

    <instantiate from="src/app_package/classes/ViewModel.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/${viewModelClass}.java" />

    <instantiate from="src/app_package/classes/Entity.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/entities/${entityClass}.java" />

    <instantiate from="src/app_package/classes/Presenter.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/${presenterClass}.java" />

    <instantiate from="src/app_package/classes/Request.java.ftl"
      to="${escapeXmlAttribute(srcOut)}/request/${requestClass}.java" />

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

    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

</recipe>

instantiate的作用在上面已經講的很清楚了,簡單來說就是將ftl文件轉換爲java文件,而open指的是在創建模板成功後,打開指定的文件,很簡單吧,這裏只有一個注意點:路徑不要寫錯了!!!

${escapeXmlAttribute(srcOut)}代表的即爲包名所代表的路徑
${escapeXmlAttribute(resOut)}代表的是res根目錄

activity_layout_recipe.xml.ftl

之前因爲解耦思想,所以把佈局文件的recipe文件單獨處理了,即爲activity_layout_recipe.xml.ftl,打開文件,其實很簡單:

<recipe>

    <instantiate from="src/app_package/layout/activity_layout.xml.ftl"
                 to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

</recipe>

這裏就不做闡述了,大家看上一節就明白了。

模板如何使用

模板創建好之後,我們首先需要的是驗證是否能夠正確創建出我們需要的部分,且沒有錯誤發生,這個過程其實就是模板使用的過程,具體可以參考模板使用位置

總結

至此,Android模板製作已經全部完成了,本文篇幅還是比較長的,如果有什麼疑問可以評論,我會盡力解決每一個問題的,謝謝!!!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章