android studio的自定義工程模板詳解---讓你開(kāi)發(fā)神速的技巧

特別說(shuō)明

當(dāng)前博客平臺(tái)賬號(hào)已廢棄,如果有使用細(xì)節(jié)問(wèn)題請(qǐng)前往我新博客平臺(tái)進(jìn)行討論交流。

個(gè)人博客平臺(tái) HuRuWo的技術(shù)小站

文章首發(fā)于個(gè)人博客HuRuWo的技術(shù)小站,如果本文非vip用戶(hù)無(wú)法完全瀏覽或者圖片無(wú)法打開(kāi),可前往個(gè)人博客文章地址查看文章并留言討論。

個(gè)人博客文章地址android studio的自定義工程模板詳解---讓你開(kāi)發(fā)神速的技巧

更多技術(shù)文章訪(fǎng)問(wèn)本人博客HuRuWo的技術(shù)小站,包括 Electron從零開(kāi)發(fā) Android 逆向 app 微信數(shù)據(jù)抓取 抖音數(shù)據(jù)抓取 閑魚(yú)數(shù)據(jù)抓取 小紅書(shū)數(shù)據(jù)抓取 其他軟件爬蟲(chóng) 等技術(shù)文章

前言

最近看到有關(guān)技術(shù)博客,發(fā)現(xiàn)了關(guān)于androidstudio的模板的有關(guān)文章,表示感興趣。
參考資料:Android Studio自定義模板 寫(xiě)頁(yè)面竟然可以如此輕松
包括文中的引用部分。

關(guān)于自定義模板

在新建Activity的時(shí)候都有許多的模板供我們選擇,大??梢宰远x模板,減少開(kāi)發(fā)時(shí)候的重復(fù)工作。
比如這樣:

圖片來(lái)自鴻洋的csdn博客

其實(shí)模板不僅限于activity 包括圖片自由 布局文件 fragment service 以及一個(gè)類(lèi)都可以制作成模板。

這里只想看下Activity模板:

模板制作學(xué)習(xí)

分為三個(gè)步驟:

  1. 分析系統(tǒng)模板
  2. 改寫(xiě)系統(tǒng)模板
  3. 自己創(chuàng)造模板

分析和改寫(xiě)系統(tǒng)模板

Activity的模板地址在AS的plugins\android\lib\templates\activities目錄下:
比如我的電腦在
C:\Program Files\Android\Android Studio\plugins\android\lib\templates\activities

Login界面用的比較多,就從他開(kāi)始吧。

點(diǎn)開(kāi)文件夾,看到以下目錄:

模板LoginActivity.png

下面來(lái)一一分析這些文件:

template.xml

這個(gè)是模板配置文件,打開(kāi)可以看到:

<?xml version="1.0"?>
<template
    format="5"
    revision="6"
    name="Login Activity"
    description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
    requireAppTheme="true"
    minApi="8"
    minBuildApi="14">

    <dependency name="android-support-v4" revision="8" />

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

    <parameter
        id="activityClass"
        name="Activity Name"
        type="string"
        constraints="class|unique|nonempty"
        default="LoginActivity"
        help="The name of the activity class to create" />

    <parameter
        id="layoutName"
        name="Layout Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${activityToLayout(activityClass)}"
        default="activity_login"
        help="The name of the layout to create for the activity" />

    <parameter
        id="activityTitle"
        name="Title"
        type="string"
        constraints="nonempty"
        default="Sign in"
        help="The name of the activity." />

    <parameter
        id="parentActivityClass"
        name="Hierarchical Parent"
        type="string"
        constraints="activity|exists|empty"
        default=""
        help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />

    <parameter
        id="packageName"
        name="Package name"
        type="string"
        constraints="package"
        default="com.mycompany.myapp" />

    <thumbs>
        <thumb>template_login_activity.png</thumb>
    </thumbs>

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

</template>
  1. 最外面的template標(biāo)簽寫(xiě)的是基本的配置:包括模板名,描述,是否請(qǐng)求系統(tǒng)主題等等。我們可以將其修改為中文。
<template
    format="5"
    revision="6"
    name="登陸界面"
    description="創(chuàng)建一個(gè)新的登陸界面"
    requireAppTheme="true"
    minApi="8"
    minBuildApi="14">
  1. parameter標(biāo)簽
    參數(shù),也就是要在創(chuàng)建的時(shí)候自己設(shè)置的東西。每一個(gè) parameter標(biāo)簽對(duì)應(yīng)一個(gè)參數(shù)。這些參數(shù)會(huì)顯示在創(chuàng)建頁(yè)面上。也修改為中文。

. id :唯一標(biāo)識(shí),最終通過(guò)該屬性的值,獲取用戶(hù)輸入值(文本框內(nèi)容,是否選中)
. name:界面上的類(lèi)似label的提示語(yǔ)
. type : 輸入值類(lèi)型
. constraints:填寫(xiě)值的約束
. suggest:建議值,比如填寫(xiě)ActivityName的時(shí)候,會(huì)給出一個(gè)布局文件的建議值。
. default:默認(rèn)值
. help:底部顯示的提升語(yǔ)

<parameter
        id="activityClass"
        name="活動(dòng)類(lèi)名"
        type="string"
        constraints="class|unique|nonempty"
        default="LoginActivity"
        help="填寫(xiě)所創(chuàng)建的活動(dòng)類(lèi)的名稱(chēng)" />

    <parameter
        id="layoutName"
        name="布局文件名"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="${activityToLayout(activityClass)}"
        default="activity_login"
        help="填寫(xiě)所創(chuàng)建的布局文件的名稱(chēng)" />

    <parameter
        id="activityTitle"
        name="標(biāo)題欄標(biāo)題"
        type="string"
        constraints="nonempty"
        default="Sign in"
        help="The name of the activity." />

    <parameter
        id="parentActivityClass"
        name="父活動(dòng)類(lèi)"
        type="string"
        constraints="activity|exists|empty"
        default=""
        help="配置父活動(dòng)類(lèi),用于返回上一級(jí)按鈕" />

    <parameter
        id="packageName"
        name="包名"
        type="string"
        constraints="package"
        default="com.mycompany.myapp" />

3.thumbs
里面放置了樣例圖,可以嘗試更換。
4.最后指定了兩個(gè)引用文件

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

可以測(cè)試一下修改的效果:

my.gif

globals.xml.ftl和recipe.xml.ftl

在這之前先了解一下ftl結(jié)尾的文件是什么:

freemarker的文件一般以后綴ftl.
  freemarker確實(shí)是不錯(cuò)的模版語(yǔ)言引擎,尤其是處理對(duì)象圖很方便,處理xml也很方便,還支持xpath
  FreeMarker 是一個(gè)模版引擎,一個(gè)基于文本的模板輸出工具(生成任意的HTML表單代碼)。它是一個(gè)Java package,面向Java程序員的class library。它本身并不是針對(duì)最終用戶(hù)的應(yīng)用,而是允許程序員將其嵌入到他們的產(chǎn)品中。
  FreeMarker被設(shè)計(jì)用來(lái)生成HTML Web頁(yè)面,特別是基于MVC(Model View Controller)模式的應(yīng)用程序。使用 MVC 模式作為動(dòng)態(tài)的WEB頁(yè)面的想法,是為了分隔頁(yè)面設(shè)計(jì)者 (HTML 設(shè)計(jì)者) 和程序員。.每個(gè)人做自己擅長(zhǎng)的那一部分。設(shè)計(jì)者可以不通過(guò)程序員的改變或修改代碼來(lái)改變網(wǎng)頁(yè)的樣子,因?yàn)閼?yīng)用邏輯(Java程序)和頁(yè)面設(shè)計(jì)(FreeMarker 模版)是分開(kāi)的。模板不會(huì)被復(fù)雜繁瑣的程序框架所破壞。即使當(dāng)一個(gè)項(xiàng)目的程序員和HIMTL頁(yè)面的制作者是同一個(gè)人時(shí),這種分隔也是很有用,因?yàn)檫@樣有助于保持應(yīng)用的清晰并易于維護(hù)。

不知道大家寫(xiě)過(guò)網(wǎng)頁(yè)沒(méi)有,不管是jsp還是asp還是asp.net
都會(huì)有這種在標(biāo)簽語(yǔ)言里面插入編程語(yǔ)言的方式,大概類(lèi)似。

關(guān)于ftl的語(yǔ)法:

組成部分
一、整體結(jié)構(gòu)
1、注釋?zhuān)?lt;#--注釋內(nèi)容-->,不會(huì)輸出。
2、文本:直接輸出。
3、interpolation:由 ${var} 或 #{var} 限定,由計(jì)算值代替輸出。
4、FTL標(biāo)記
二、指令:
freemarker指令有兩種:
1、預(yù)定義指令:引用方式為<#指令名稱(chēng)>
2、用戶(hù)定義指令:引用方式為<@指令名稱(chēng)>,引用用戶(hù)定義指令時(shí)須將#換為@。
注意:如果使用不存在的指令,F(xiàn)reeMarker不會(huì)使用模板輸出,而是產(chǎn)生一個(gè)錯(cuò)誤消息。
freemarker指令由FTL標(biāo)記來(lái)引用,F(xiàn)TL標(biāo)記和HTML標(biāo)記類(lèi)似,名字前加#來(lái)加 以區(qū)分。如HTML標(biāo)記的形式為<h1></h1>則FTL標(biāo)記的形式是<#list>< /#list>(此處h1標(biāo)記和list指令沒(méi)有任何功能上的對(duì)應(yīng)關(guān)系,只是做為說(shuō)明使用一下)。
有三種FTL標(biāo)記:
1)、開(kāi)始標(biāo)記:<#指令名稱(chēng)>
2)、結(jié)束標(biāo)記:</#指令名稱(chēng)>
3)、空標(biāo)記:<#指令名稱(chēng)/>
注意:

  1. FTL會(huì)忽略標(biāo)記之中的空格,但是,<#和指令 與 </#和指令 之間不能有空格。
  2. FTL標(biāo)記不能夠交叉,必須合理嵌套。每個(gè)開(kāi)始標(biāo)記對(duì)應(yīng)一個(gè)結(jié)束標(biāo)記,層層嵌套。 如:
    <#list>
    <li>
    {數(shù)據(jù)} <#if 變量> <p>game over!</p> </#if> </li> </#list> 注意事項(xiàng): 1)、FTL對(duì)大小寫(xiě)敏感。 所以使用的標(biāo)記及interpolation要注意大小寫(xiě)。name與NAME就是不同的對(duì)象。<#list>是正確的標(biāo)記,而<#List>則不是。 2)、interpolation只能在文本部分使用,不能位于FTL標(biāo)記內(nèi)。如<#if{var}>是錯(cuò)誤的,正確的方法是:<#if var>,而且此處var必須為布爾值。
    3)、FTL標(biāo)記不能位于另一個(gè)FTL標(biāo)記內(nèi)部,注釋例外。注釋可以位于標(biāo)記及interpolation內(nèi)部。

其實(shí)只要知道if即可以了,這里只用if來(lái)判斷。

globals.xml.ftl

還要引入的依賴(lài)和包。
定義了全局變量,并引用了一個(gè)內(nèi)置的通用globals.xml.ftl

<?xml version="1.0"?>
<globals>
    <global id="hasNoActionBar" type="boolean" value="false" />
    <global id="isLauncher" type="boolean" value="${isNewProject?string}" />
    <global id="includePermissionCheck" type="boolean" value="${(targetApi gte 23)?string}" />
    <global id="GenericStringArgument" type="string" value="<#if buildApi lt 19>String</#if>" />
    <#include "../common/common_globals.xml.ftl" />
</globals>

recipe.xml.ftl
指定資源文件的路徑并相應(yīng)的生成到我們的項(xiàng)目目錄去:

<recipe>
   <#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>

    <merge from="root/AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />

    <merge from="root/res/values/dimens.xml"
             to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />

    <merge from="root/res/values/strings.xml.ftl"
             to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

    <instantiate from="root/res/layout/activity_login.xml.ftl"
                   to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

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

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

</recipe>

. copy :從root中copy文件到我們的目標(biāo)目錄,比如我們的模板Activity需要使用一些圖標(biāo),那么可能就需要使用copy標(biāo)簽將這些圖標(biāo)拷貝到我們的項(xiàng)目對(duì)應(yīng)文件夾。
. merge : 合并的意思,比如將我們使用到的strings.xml合并到我們的項(xiàng)目的stirngs.xml中
. instantiate : 和copy類(lèi)似,但是可以看到上例試將ftl->java文件的,也就是說(shuō)中間會(huì)通過(guò)一個(gè)步驟,將ftl中的變量都換成對(duì)應(yīng)的值,那么完整的流程是ftl->freemarker process -> java。
. open:在代碼生成后,打開(kāi)指定的文件,比如我們新建一個(gè)Activity后,默認(rèn)就會(huì)將該Activity打開(kāi)。

那么整體的關(guān)系類(lèi)似下圖:


圖片來(lái)源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

這里只是指定了生成的路徑的文件名,但是如何生成呢,這就要根據(jù)前面創(chuàng)建的選項(xiàng)來(lái)設(shè)置生成的目標(biāo)文件。

root文件夾

里面都是加了ftl的java文件和XML文件,所以我們用if來(lái)判斷生成的方式。
兩個(gè)步驟:
取值--->判斷-->生成

xml文件中:

配置文件:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

    <!-- To auto-complete the email text field in the login form with the user's emails -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application>
        <activity android:name=".${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            <#else>
            android:label="@string/title_${simpleName}"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            </#if>
            <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
            <#if parentActivityClass != "">
            <meta-data android:name="android.support.PARENT_ACTIVITY"
                android:value="${parentActivityClass}" />
            </#if>
            <#if isLauncher && !(isLibraryProject!false)>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            </#if>
        </activity>
    </application>
</manifest>

獲取值,包括設(shè)定值和全局變量:

  1. 設(shè)定值:activityClass獲取,${activityClass}
    全局變量:isNewProject,hasNoActionBar
  2. if判斷
<#if isNewProject>
 android:label="@string/app_name"
 <#else>
 android:label="@string/title_${simpleName}"
</#if>

java文件中

代碼太長(zhǎng)不貼
也是類(lèi)似的方式:
比如包名引入package ${packageName};
判斷:

<#if parentActivityClass != "">
 setupActionBar();
</#if>

最后系統(tǒng)根據(jù)這些東西來(lái)生成最終的文件:

流程大致可用下圖說(shuō)明:


圖片來(lái)源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

自己創(chuàng)造模板

分析改寫(xiě)已經(jīng)完成了,下面開(kāi)始創(chuàng)造

寫(xiě)個(gè)自己的登錄界面

不用完全從頭開(kāi)始。我們就拿LoginActivity模板來(lái)寫(xiě)。

先寫(xiě)一個(gè)簡(jiǎn)單的登錄系統(tǒng):
像這樣:

QQ截圖20161026144605.png

把相應(yīng)的文件復(fù)制到root目錄下:
包括所用的資源文件和java文件.

template修改,添加3個(gè)東西,一個(gè)是否含有記住密碼選項(xiàng),一個(gè)是否含有注冊(cè)選項(xiàng),還有一個(gè)是標(biāo)題。
都是bool類(lèi)型:

<parameter
        id="titleString"
        name="標(biāo)題名"
        type="string"
        constraints="nonempty"
        default="默認(rèn)標(biāo)題" />
    <parameter
        id="isRemember"
        name="是否含有記住密碼"
        type="boolean"
        default="false"
        help="為真則添加一個(gè)checkbox在登錄按鈕上面" />
    <parameter
        id="isRegiste"
        name="是否含有注冊(cè)選項(xiàng)"
        type="boolean"
        default="false"
        help="為真則添加一個(gè)注冊(cè)按鈕在登錄按鈕上面" />

java文件改寫(xiě),我這里沒(méi)有寫(xiě)很多邏輯,簡(jiǎn)單的引入包名就可以了。加上布局文件。

package ${packageName};

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

public class ${activityClass} extends ${superClass} {

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

這里主要是layout文件要根據(jù)選項(xiàng)創(chuàng)建:

<?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"
    tools:context="com.example.administrator.demo.MainActivity"
    android:orientation="vertical"
    android:background="#cccccc"
    >


        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:text="${titleString}"
            android:textColor="#FF4500"
            android:textSize="40sp"
            android:typeface="monospace" />
        <EditText
            android:id="@+id/edt_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/rounded_editview"
            android:drawableLeft="@drawable/ic_name"
            android:hint="輸入用戶(hù)名"
            android:textSize="30sp" />

        <View
            android:layout_width="match_parent"
            android:layout_height="20dp" />

        <EditText
            android:id="@+id/edt__"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/rounded_editview"
            android:drawableLeft="@drawable/ic_pass"
            android:hint="輸入密碼"
            android:inputType="textPassword"
            android:textSize="30sp" />

            <#if isRemember>
        <CheckBox
            android:padding="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="記住密碼"
            android:textColor="#000079"
            android:textSize="20sp" />
            </#if>
            <#if isRegiste>
        <TextView
            android:textColor="#000079"
            android:padding="20dp"
            android:text="沒(méi)有賬號(hào)?點(diǎn)此注冊(cè)"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
            </#if>
        <Button
            android:id="@+id/angry_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_edittext"
            android:shadowColor="#A89A7B"
            android:shadowDx="0"
            android:shadowDy="0"
            android:shadowRadius="5"
            android:text="登錄"
            android:textColor="#ffffff"
            android:textSize="30sp" />

</LinearLayout>

最后檢查recipe文件對(duì)應(yīng)的名字和目錄有沒(méi)有問(wèn)題以及生成方式。

<merge from="root/AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />


     <copy from="root/res/drawable/ic_password.png"
            to="${escapeXmlAttribute(resOut)}/drawable/ic_password.png" />
    <copy from="root/res/drawable/ic_user.png"
            to="${escapeXmlAttribute(resOut)}/drawable/ic_user.png" />
    <merge from="root/res/values/dimens.xml"
             to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />

    <merge from="root/res/values/strings.xml.ftl"
             to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

    <instantiate from="root/res/layout/activity_login.xml.ftl"
                   to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

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

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

主要是兩個(gè)copy 就是復(fù)制文件到目錄下,然后布局文件中就可以調(diào)用了。

測(cè)試:
這時(shí)候老板叫你開(kāi)發(fā)一個(gè)登錄頁(yè)面,你只需要慢慢的泡一杯茶,然后點(diǎn)擊幾下鼠標(biāo)。

1460513940727641.jpg
gif.gif

當(dāng)然這是個(gè)簡(jiǎn)單的模板,不過(guò)各位學(xué)會(huì)了之后可以寫(xiě)一些符合自己需求的模板。

推薦一些開(kāi)源的模板地址

如果大家有時(shí)間逛github,直接搜索Android Studio Template即可。
https://github.com/kanytu/Android-studio-material-template
A template for Android Studio to create applications with material design and Navigation Drawer.包含:MaterialNavigationDrawerActivity。

https://github.com/gabrielemariotti/AndroidStudioTemplate
包含SwipeRefreshLayout,還有一些常用的模板。

https://github.com/intrications/material-design-icons-adt-template
用于創(chuàng)建Material Design Icon,可以非常方便的創(chuàng)建Icon,再也不需要自己去找下載各種尺寸了。

hoangyang的例子
一個(gè)ViewPage的模板

最后的總結(jié)

  1. 模板雖好,切勿濫用
  2. 寫(xiě)模板盡量寫(xiě)的通用一些。一些具體的東西還是要手寫(xiě)。
  3. 注意備份模板,重裝系統(tǒng)的軟件都會(huì)丟失模板。

我的開(kāi)發(fā)工具用的是sublime2,理論上任何一個(gè)文本編輯器都可以,但是我有Notepad++會(huì)有格式上的報(bào)錯(cuò)。

附上本文中我寫(xiě)的模板,當(dāng)然我也會(huì)繼續(xù)開(kāi)發(fā)好用的模板上傳。
https://github.com/HuRuWo/Android-Studio-Template
放置到Android Studio\plugins\android\lib\templates\activities 目錄下,重啟as即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容