studio模板,一鍵生成多個類,mvp黨福利

人類進步的根源是什么?是懶惰,是的,沒有錯,就是懶惰,正是當(dāng)你想偷懶時,你才會去尋找更便捷的方法搞定一件事。寫代碼也是一樣的,不想偷懶的程序猿不是好程序猿,下面我們來看看如何“偷懶”。

首先,聲明一下,本文的作用純屬拋磚引玉,并不會太詳細的介紹具體使用方法,僅僅介紹大概使用思路及踩坑日記。雖然本文以mvp為例,但是本文所講的內(nèi)容不局限于此,基本上所有的模板代碼,你都可以生成模板,方便后面使用。

使用mvp模式開發(fā)安卓項目的人都知道,創(chuàng)建一個activity通常需要創(chuàng)建包含接口在內(nèi)的5個類,寫一兩個界面還好,如果真是寫完整個項目,光是創(chuàng)建這些類都讓人心煩,那么有沒有快捷的方法呢?當(dāng)然是有的,最簡單的方法就是使用studio 自帶的file template。


下面來寫一個簡單的Preseter類模板:

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
public class ${NAME}Presenter extends StyleActivityPresenter<I${NAME}View,I${NAME}Model>{
    @Override
    protected void initPresenter (Bundle savedInstanceState) {

    }
}

使用效果:

public class TestPresenter extends StyleActivityPresenter<ITestView, ITestModel> {
    @Override
    protected void initPresenter (Bundle savedInstanceState) {

    }
}

可以看出,使用上還是非常簡單的,${NAME}就是你創(chuàng)建文件時輸入的名字,其他的相信不用解釋大家都看的懂。

上面的模板功能雖然已經(jīng)能夠很方便的讓我創(chuàng)建一個類而不用去寫過多的重復(fù)代碼了,但是依然不夠好用,因為上面說了,mvp模式通常包含5個類,還有布局文件,還有activity注冊代碼,這些可以說是每次創(chuàng)建activity的必須代碼,而如果僅僅使用上面的file template功能,依然需要多次在不同包下創(chuàng)建文件,還有沒有更偷懶的方法呢?當(dāng)然有,就是studio強大的activity模板功能了。
??其實這個功能,大家經(jīng)常都在使用,只是很多人并沒有注意罷了,就比如我們新建項目時:

這其實就是studio自帶的activity模板,我們知道當(dāng)我們選中某個類型的模板后,生成了項目之后,項目中就會有相應(yīng)的java代碼和布局,并且他會幫你在manifest注冊好這個activity。

下面我們需要的也就是自定義這個功能,讓他實現(xiàn)輸入一個類名后在你指定的包下面自動生成5個mvp相關(guān)類和布局文件已經(jīng)manifest注冊。

首先,我們需要知道,系統(tǒng)自帶的模板位置:XXX\android-studio\plugins\android\lib\templates\activities
這個目錄下就是上面我們看到的所有activity模板的文件目錄,先來簡單介紹一下模板的目錄下幾個重要的文件及其作用,我們以LoginActivity這個模板為例:

root:這個目錄下面放的是我們我們的代碼模板,和file template代碼類似,但是有一定區(qū)別。我喜歡叫他們模板輸出原型。

globals.xml:這個文件是用來配置某些特殊屬性的,比如是否是啟動頁面之類的屬性。

recipe.xml:這個文件主要是配置需要生成哪些文件,用哪個模板生成,生成后要輸出到哪個目錄。

template.xml這個文件主要是用來定義我們的一些文件名和包名之類的變量屬性,看看LoginActivity的配置界面效果,相信大家就懂這個文件的作用了:

template_login_activity.png:這個是上面圖中那個界面示意圖,通常不需要管它,當(dāng)然你也可以放一張自己的圖,替換掉。

下面說說怎么自定義自己的模板,(文章開始已經(jīng)說了,本文并不會詳細介紹如何進行自定義模板<我能說是自己也是才學(xué)這個東西嗎?>,這里直接介紹我自己自定義時遇到的坑,和一些比較重要的注意事項):

  • 首先,建議大家從最簡單的模板開始嘗試,不要一開始就完全以自己的mvp類去寫,等熟悉了相關(guān)屬性和規(guī)則后再去寫mvp相關(guān)的模板,這是因為模板這個東西不是我們的項目代碼,如果你配置錯了,使用時雖然會有報錯提示,但是并不準(zhǔn)確,所以如果你一次性寫太多東西的話,排查錯誤時很慢。
  • 其次,建議直接先復(fù)制一份系統(tǒng)的模板代碼比如(LoginActivity模板),然后在此基礎(chǔ)上修改,不要自己去創(chuàng)建每個文件,理由和第一條類似,容易出錯。

然后開始我們的模板創(chuàng)建之旅:

1.創(chuàng)建Demo項目用于測試

非常簡單,只包含了一個默認的自動生成的MainActivity類


2.復(fù)制LoginActivity模板

在我們的對應(yīng)目錄XXX\android-studio\plugins\android\lib\templates\activities中復(fù)制LoginActivity文件夾并重命名為MVPTestActivity,接著進入我們復(fù)制的文件夾,打開template.xml這個文件,改掉name的值,最好是和你的目錄名保持一致,我們這里就叫MVPTestActivity,其他幾個屬性可以按照自己的需要進行修改。

<template
    format="5"
    revision="6"
    name="Login Activity"-->此屬性改名為MVPTestActivity
    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."
    minApi="8"
    minBuildApi="14">

parameter,這個標(biāo)簽是我們配置界面上的字段,我這里只截圖兩個重要字段:

 <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" />

兩張圖一起看的話就很好理解了,id為activityClass的這個標(biāo)簽就是我們的類名,id為layoutName就是我們的布局名,id這個字段是我們在其他配置文件中引用name的查找依據(jù),type自然就是類型,constraints是一些輸入限制信息,default當(dāng)然就是默認實現(xiàn)在配置界面上的值了,每個屬性的具體用法,大家自己搜索一下相關(guān)博文,這里不是本文的重點。

我們這里先開始寫最簡單的,那么我們肯定是不需要Title之類的屬性的,所以我們先把不需要顯示或配置的字段注釋掉。

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

其他的一些字段我們也可以根據(jù)我們的需要稍作調(diào)整,這樣這個文件基本就修改完成了。

recipe.xml,打開此文件,我們可以看到

<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<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>

    <#include "../common/recipe_theme.xml.ftl" />

    <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" />

<#if generateKotlin>
    <@kt.addAllKotlinDependencies />
    <instantiate from="root/src/app_package/LoginActivity.kt.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<#else>
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>

</recipe>

是不是看的一頭霧水,其實不難,ifelse相信不用過多解釋,我們重點講講其他幾個比較重要的標(biāo)簽的作用。

  • merge 顧名思義就是合并的意思,這個主要是用在資源文件或者manifest文件,因為我們通常是需要把我們新建的xml文件和項目中的進行合并,這里稍微講解一下manifest的合并,因為其他的資源文件合并都很簡單,就不說了。

打開MVPTestActivity\root目錄下的manifest文件,主要代碼如下:

  <activity android:name=".${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            <#else>
            android:label="@string/title_${simpleName}"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            android:theme="@style/${themeName}"
            </#if>
            <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
            <#if parentActivityClass != "">
            <meta-data android:name="android.support.PARENT_ACTIVITY"
                android:value="${parentActivityClass}" />
            </#if>
            <@manifestMacros.commonActivityBody />
        </activity>

基本上不是很難理解,類似于這種${}代碼,都是對其他文件中屬性的引用,下面我們?nèi)サ粑覀儾恍枰膶傩?修改后:

  <activity android:name=".${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            android:theme="@style/${themeName}"
            </#if>>
                
            <@manifestMacros.commonActivityBody />
        </activity>
  • instantiate 就是創(chuàng)建文件
  • open file當(dāng)然就是在我們生成好類后,打開相關(guān)類

這里重點說說from和to這兩個屬性,看名字大家應(yīng)該都能猜出一點了,from就是指的我們的模板文件路徑,to當(dāng)然就是我們生成文件的路徑,以簡單的布局文件為例,可以看到一個instantiate標(biāo)簽內(nèi)容如下:

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

其中root/res/layout/activity_login.xml.ftl這個路徑,明顯就是我們當(dāng)前模板目錄下面的文件,而to的屬性,我們也可以理解下,${escapeXmlAttribute(resOut)}多半就是指的當(dāng)前項目的res資源主目錄,然后${layoutName}明顯就是引用的template.xml中配置的,用戶輸入的layoutName這個String。
明白了這些屬性后,我們做出如下修改:

  • 刪掉MVPTestActivity\root\res目錄下的values文件夾
  • 注釋掉recipe.xml文件中兩個關(guān)于資源合并的標(biāo)簽(不去掉會報空指針,因為我們已經(jīng)刪掉了資源模板)
  • 最后修改MVPTestActivity\root\src\app_package目錄下的LoginActivity.java文件(其實還需要同步修改LoginActivity.kt,不過因為我暫時沒有使用kotlin,所以沒做它的適配),這個文件內(nèi)容太多,為了方便查看,我們簡化成如下代碼:
package ${packageName};

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>
/**
 * A login screen that offers login via email/password.
 */
public class ${activityClass} extends AppCompatActivity {

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

}

好了,到此為止,其實我們已經(jīng)完成了一些最簡單的修改,下面我們來看看運行效果,保存每個文件后,重啟studio(一開始寫的時候千萬不要想一次改完,不然多半會因為失敗而放棄這個):

可以看到我們修改了模板介紹,修改了默認的activity名字,去掉了title和parent兩個輸入框,讓我們點擊完成試一下效果:


TestActivity和activity_test都是我們通過模板自動生成的文件,并且mainfest中已經(jīng)注冊了這個activity了,具體截圖就不放了,大家可以自己嘗試。

上面的步驟已經(jīng)算是完成了模板的最基本使用了,但是離我們的要求還差點,因為我們想要的是生成多個文件,并且多個文件有可能不再同一個包下,那么我們怎么實現(xiàn)呢?

首先,假如我們的項目結(jié)構(gòu)如下:

那么我現(xiàn)在需要的就是:

  • 在presenter包下創(chuàng)建一個TestActivityPresenter類
  • 在model包下創(chuàng)建一個TestActivityModel
  • 在view包下創(chuàng)建一個TestActivityView
  • 在port包下創(chuàng)建一個ITestActivityModel接口和ITestActivityView接口
  • 并且TestActivityView應(yīng)該實現(xiàn)ITestActivityView接口,TestActivityModel應(yīng)該實現(xiàn)ITestActivityModel接口
  • 生成對應(yīng)布局文件,并且注冊activity

最后一條,基本上不用再說了,我們開始的改動已經(jīng)滿足了,重點說說下面幾條怎么實現(xiàn),主要是說說怎么在不同目錄下生成對應(yīng)文件。

回到我們的recipe.xml文件中,我們注意看這里

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

這里其實就是用來生成我們剛剛那個TestActivity的配置代碼,明顯的我們想多生成幾個文件的話,只需要多寫幾個這種標(biāo)簽就好,比如像下面這樣:

 <!--IView -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />                                    
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

注意看to的值的最后一點,我采用拼接的方式規(guī)定了每個文件的名字格式,這樣所有mvp下的類名都是符合一定命名規(guī)范的,當(dāng)然不一定和我的一樣,但是你一定要有自己的格式,不要隨意取名字,這是基礎(chǔ),不過多解釋原因。
這樣我們生成的時候就會得到5個文件,但是還不夠,因為他們現(xiàn)在都在同一個目錄下,怎么讓他們在自己的目錄下生成呢,再來改改:

  <!--IView -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/view/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/model/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />                                    
    <open file="${escapeXmlAttribute(srcOut)}/presenter/${activityClass}.java" />
</#if>

是不是很簡單?只需要在${escapeXmlAttribute(srcOut)}后面跟上具體的包路徑就好了,那么我們還缺點什么?我們還缺模板源文件,因為上面from的文件都是LoginActivity這個文件,也就是說生成的每個類的代碼都是相同的。

下面讓我們來完成最后一步,編寫模板代碼了,這個就相對簡單了,畢竟基本上都是java代碼,難不倒大家的,我們在MVPTestActivity\root\src\app_package路徑下把LoginActivity.java.ftl這個文件復(fù)制四份,kt那個文件不管,那個是適配kotlin的,如果你是用的Kotlin的話,也可以復(fù)制那個。
然后依次改名,效果如下:

每個類的代碼如下:
MvpView.java

package ${packageName}.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>
import com.xujl.demo.port.I${activityClass}View;
import com.xujl.demo.presenter.${activityClass}Presenter;

class ${activityClass}View extends AppCompatActivity implements I${activityClass}View{
    private ${activityClass}Presenter mPresenter;

    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.${layoutName});
        mPresenter = new ${activityClass}Presenter(this);
    }

}

MvpPresenter.java

package ${packageName}.presenter;

import com.xujl.demo.model.${activityClass}Model;
import com.xujl.demo.port.I${activityClass}Model;
import com.xujl.demo.port.I${activityClass}View;

public class ${activityClass}Presenter  {
    private I${activityClass}View mView;
    private I${activityClass}Model mModel;

    public ${activityClass}Presenter(I${activityClass}View view){
        mView = view;
        mModel = new ${activityClass}Model();
    }
   

}

MvpModel.java

package ${packageName}.model;

import com.xujl.demo.port.I${activityClass}Model;

public class ${activityClass}Model implements I${activityClass}Model {
    
   

}


IMvpModel.java

package ${packageName}.port;

public interface I${activityClass}Model{

}


IMvpView.java

package ${packageName}.port;


public interface I${activityClass}View{

}


最后不要忘記修改recipe.xml中from的模板文件名和模板mainfest中的activity名字

 <!--IView -->
    <instantiate from="root/src/app_package/IMvpView.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/MvpView.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/view/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/IMvpModel.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/MvpModel.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/model/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/MvpPresenter.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />   
----------------------------------------------------
 <activity android:name=".view.${activityClass}View"
            <#if isNewProject>
            android:label="@string/app_name"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            android:theme="@style/${themeName}"
            </#if>>
                
            <@manifestMacros.commonActivityBody />
        </activity>

最后來看看運行效果(右鍵點擊的時候一定要在主包上面點擊,不然生成路徑可能會出錯):


如果你不是在主包上點擊的,也沒關(guān)系,記得修改這里為主包路徑就好:


至此我們就完成了一個完整的模板了,當(dāng)然這里不論是包結(jié)構(gòu)還是命名方式,還是mvp結(jié)構(gòu),都是我自己定義的,大家完全可以根據(jù)自己的項目實際情況的來寫,我這樣定義的結(jié)構(gòu),好處是只需要鍵入類名,其他都可以生成了,當(dāng)然你也可以多設(shè)置幾個包名字段,用來動態(tài)配置model,view,presenter,和接口的包路徑。

最后附上整個MVPTestActivity的模板文件鏈接: https://pan.baidu.com/s/1skW4nTV 密碼: kbve

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容