玩轉Android Studio自定義模板插件-MVP模板爲例

版權說明 : 《玩轉Android Studio自定義模板插件-MVP模板爲例》於當前CSDN博客乘月網屬同一原創,轉載請說明出處,謝謝。

得益於Android Studio強大的拓展功能,我們可以開發出適於自己項目的插件以滿足快速高效的開發需求,本文以MVP模板插件爲例

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

演示圖

在日常開發中,新建一個如名爲ZModuleActivity的Activity:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

Empty Activity(插件)的視圖面板

新建後Android Studio(下文簡稱:AS)會依據我們編輯和勾選的一些要求生成具有一定代碼的.java和.xml的Activity和Layout文件,以及會幫我們在AndroidManifest.xml註冊好這個新建的Activity。

大體這樣:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

像這樣 –>

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

一定程度上爲我們省下了很多創建文件,寫一些基礎代碼的時間,但是這遠遠沒有達到我們想要的效果。一個成熟的項目開發會有着自己的一套代碼架構,層次和模板,這就意味着每次新建Activity都要修改些代碼以沿用項目框架。

如MVP設計模式代碼結構可能如下,以YModuleActivity爲例:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

代碼可能像這樣 –>

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

AndroidManifest就不貼了,不是本文重點。

每新建一個Activity都要做大量無意義的基礎代碼工作,不知道你有沒有這樣的感覺:好煩啊,總是重複這些工作有意思嗎?能不能直接幫我們生成好啊?有的人在感嘆之餘默默地把之前已經做好的(如本文 的YModule系列代碼:YModuleActivity,IYModule,YModulePresenter)相關文件給copy過來改文件名,改類名,改代碼,改註釋,刪冗餘代碼,最後變成類似YModule系列文件和代碼這樣。嗯,的確有省了不少事,但畢竟還是一連串的事兒。

既然有了困擾,就成了需求,既然是需求,就得滿足需求。好在AS允許我們開發這樣的代碼模板插件,那麼正式進入今天的主題。

打開AS安裝目錄(本文爲“android-studio”),依次打開:android-studio》plugins》android》lib》templates》activities 結果如下:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

看到目錄名是否覺得眼熟?如果你還反應過來,沒關係,看下圖:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

選擇插件模板

沒錯,這裏就是傳說中新建Activity的模板插件集中營,每個目錄即爲一個插件。

知道這些就好辦了,對EmptyActivity這個目錄進行令人窒息的Ctrl C and Ctrl V操作, 然後對這個副本重命名(本文爲“MVPActivity”)。你可能會問爲什麼一定是EmptyActivity?我可沒有強調,只是顧名思義,空蕩蕩的模板可以省去刪除一些冗餘的模板代碼操作,是吧?

打開MVPActivity目錄,你可看到如下文件結構:

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

下面先簡單介紹這幾個文件,然後開始寫模板

1. root目錄

用於存放源代碼模板和資源模板的.ftl文件,我們可以一路展開到最後一個目錄,可以看到文件SimpleActivity.java.ftl(如果AS版本較高的話還有SimpleActivity.kt.ftl,顧名思義,分別是java版和kotlin版的模板,本文僅專注java)。查看文件:

package ${packageName};

import ${superClassFqcn};
import android.os.Bundle;
<#if includeCppSupport!false>
import android.widget.TextView;
</#if>

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
<#if generateLayout>
        setContentView(R.layout.${layoutName});
</#if>
<#include "../../../../common/jni_code_usage.java.ftl">
    }
<#include "../../../../common/jni_code_snippet.java.ftl">
}

很明顯是Activity的模板嘛,一看便懂,確認過眼神。

  • ${xxx}爲引用值,由前臺面板(如上文新建Activity的插件視圖面板)填寫或勾選的結果值賦值,xxx可能爲變量,也可能爲函數返回值。

  • <#if xxx!bool值>yyy</#if>爲if判斷語句,與常見語言判斷語法規則並無區別,只是寫法的區別。

  • <#include "xxx">導入某某文件的內容。類似於Android佈局中的標籤用法。

這裏稍微提一下:EmptyActivityroot目錄下只有源代碼模板目錄src,因爲模板比較簡單,其直接引用了EmptyActivity同級目錄commonroot目錄下資源模板目錄res,下文會有提及,本文只講源代碼模板,不做深究。

2. template.xml

定義插件視圖面板的外觀,佈局,類似咱Android的layout.xml佈局文件,查看文件:

<?xml version="1.0"?>
<template
    format="5"
    revision="5"
    name="Empty Activity"
    minApi="9"
    minBuildApi="14"
    description="Creates a new empty activity">

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

    <parameter
        id="instantAppActivityHost"
        name="Instant App URL Host"
        type="string"
        suggest="${companyDomain}"
        default="instantapp.example.com"
        visibility="isInstantApp!false"
        help="The domain to use in the Instant App route for this activity"/>

    <parameter
        id="instantAppActivityRouteType"
        name="Instant App URL Route Type"
        type="enum"
        default="pathPattern"
        visibility="isInstantApp!false"
        help="The type of route to use in the Instant App route for this activity" >
        <option id="path">Path</option>
        <option id="pathPrefix">Path Prefix</option>
        <option id="pathPattern">Path Pattern</option>
    </parameter>

    <parameter
        id="instantAppActivityRoute"
        name="Instant App URL Route"
        type="string"
        default="/.*"
        visibility="isInstantApp!false"
        help="The route to use in the Instant App route for this 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" />

    <parameter
        id="generateLayout"
        name="Generate Layout File"
        type="boolean"
        default="true"
        help="If true, a layout file will be generated" />

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

    <parameter
        id="isLauncher"
        name="Launcher Activity"
        type="boolean"
        default="false"
        help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />

    <parameter
        id="backwardsCompatibility"
        name="Backwards Compatibility (AppCompat)"
        type="boolean"
        default="true"
        help="If false, this activity base class will be Activity instead of AppCompatActivity" />

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

    <!-- 128x128 thumbnails relative to template.xml -->
    <thumbs>
        <!-- default thumbnail is required -->
        <thumb>template_blank_activity.png</thumb>
    </thumbs>

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

</template>

對照着上文的【Empty Activity(插件)的視圖面板】示意圖或者自己新建個Empty Activity呼出面板,差不多能看懂或猜測到一大半吧?

下面是對parameter模塊補充介紹:

  • parameter標籤:表示一個參數模塊,爲變量的賦值而服務的視圖塊
  • id:變量名,上文提到引用值${xxx}中的變量
  • type:變量的數據類型,同java數據類型差不多,這裏常見的是string和boolean類型,boolean型模板面板以勾選框的形式顯示
  • constraints:約束,主要有class(輸入框填寫需要創建的java類名),layout(輸入框填寫需要創建的佈局名),package(輸入框+下拉框填寫或選擇包名),unique(填寫內容不能與已有的java完整類名重名,或佈局名重名,重複會自動在後面加個2,3…以此類推,nonempty(不能爲空,輸入框不能不填內容)
  • default:默認值,默認值會根據type類型自動填充到輸入框或決定勾選框勾選狀態
  • suggest:建議值,會覆蓋默認值,配合變量或函數動態修改輸入框內容或勾選框勾選狀態
  • thumb標籤:插件模板效果縮略圖
  • global標籤:引用全局變量文件
  • execute標籤:執行模板代碼配置文件,這裏是個文件引用

3. 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" />
    <#include "../common/common_globals.xml.ftl" />
</globals>

可以看到其同template.xml內的parameter類似,內部有標籤,分別定義id,type和默認值。

4. recipe.xml.ftl

用於執行模板代碼的配置文件。查看文件:

<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
    <#include "../common/recipe_manifest.xml.ftl" />
    <@kt.addAllKotlinDependencies />

<#if generateLayout>
    <#include "../common/recipe_simple.xml.ftl" />
    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</#if>

    <instantiate from="root/src/app_package/SimpleActivity.${ktOrJavaExt}.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}" />
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}" />

</recipe>

簡單介紹下:

  • <#include/>標籤,引入某文件內容
  • 打開文件標籤,這個要看執行時機,如果文件還沒生成,則可能會報錯,所以該標籤會放在生成目標文件代碼後。
  • 標籤,實例化,即執行模板代碼。
  • from,執行指定某目錄下的模板代碼文件,上文提到模板文件是在root目錄下
  • to,執行後的結果內容輸出文件,像這樣的${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml由函數,變量拼接文件名(包擴路徑)的表達式,我們不需要了解它是怎麼實現的,但根據你的開發經驗一定能看得懂它會生成什麼樣的文件名(包括路徑),我好像說了一句廢話 T^T

5. template_blank_activity.png

沒什麼好介紹的,模板效果縮略圖文件,上文提到用thumb標籤使其顯示於面板上。

說好的簡單介紹,結果還是BB了那麼多

玩轉Android Studio自定義模板插件-MVP模板爲例_乘月網_專注於移動互聯網_android開發_html開發_java開發_linux運維_php開發_web開發

下面開始折騰“MVPActivity”插件

分析

上文已經給出代碼示意圖,我們需要自動生成YModuleActivity,IYModule和YModulePresenter三個java類,那麼:

1. 需要各自三個類的源碼模板

比如模板名分別爲:MVPActivity.java.ftl,MVPInterface.java.ftl和MVPPresenter.java.ftl,由上文可知,文件於root->src->app_package目錄下。

2. 需要各自三個類的類名變量和一個公共拼接變量

對於這樣的代碼:

public class YModuleActivity extends BaseActivity<IYModule.Presenter> implements IYModule.View {

    @Override
    public void setPresenter(IYModule.Presenter presenter) {
        // Bind presenter
        if (presenter == null) {
            presenter = new YModulePresenter(this);
        }
    }
}

需要轉換成動態化模板成這樣:

public class ${activityClass} extends BaseActivity<${mvpInterface}.Presenter> implements ${mvpInterface}.View {

    @Override
    public void setPresenter(${mvpInterface}.Presenter presenter) {
        // Bind presenter
        if (presenter == null) {
            presenter = new ${presenterClass}(this);
        }
    }
}

對,是這三個變量:activityClass,mvpInterface,presenterClass,上文查看template.xml文件中,已經看到“activityClass”已定義過,只要再添加另兩個就好了。

心細的同學可能注意到YModuleActivity,IYModule和YModulePresenter都有個YModule,可以提取變量爲commonClassName。

3. 需要一個時間變量(可選)

對於類的註釋,比如這樣:

/**
 * <pre>
 *     @author : www.icheny.cn
 *     @e-mail : [email protected]
 *     @time   : 2018.08.25
 *     @desc   : ${commonClassName} Activity.
 *     @version: 1.0.1
 * </pre>
 */

註釋很多都可以共用或稍加修改便可,但是創建的時間(time)是動態的,那麼需要變量:createTime來替換。

製作

1. 三個類的源碼模板

MVPActivity.java.ftl

package ${packageName};

<#if generateLayout>
import cn.icheny.plugin.mvp.demo.R;
</#if>
import cn.icheny.plugin.mvp.demo.module.base.BaseActivity;

/**
 * <pre>
 *     @author : www.icheny.cn
 *     @e-mail : [email protected]
 *     @time   : ${createTime}
 *     @desc   : ${commonClassName} Activity.
 *     @version: 1.0.1
 * </pre>
 */
public class ${activityClass} extends BaseActivity<${mvpInterface}.Presenter> implements ${mvpInterface}.View {

    @Override
    protected void initData() {

    }

    @Override
    protected void initViews() {

    }

    @Override
    protected int layoutId() {
        return R.layout.${layoutName};
    }

    @Override
    public void showData() {

    }

    @Override
    public void setPresenter(${mvpInterface}.Presenter presenter) {
        // Bind presenter
        if (presenter == null) {
            presenter = new ${presenterClass}(this);
        }
    }
}

MVPInterface.java.ftl

package ${packageName};

import cn.icheny.plugin.mvp.demo.module.base.IBasePresenter;
import cn.icheny.plugin.mvp.demo.module.base.IBaseView;

/**
 * <pre>
 *     @author : www.icheny.cn
 *     @e-mail : [email protected]
 *     @time   : ${createTime}
 *     @desc   :  MVP --> ${commonClassName} VP --> View,Presenter. Child View And Child Presenter Interface For This Module.
 *     @version: 1.0.1
 * </pre>
 */
public interface ${mvpInterface} {

    interface View extends IBaseView<Presenter> {
        /**
         * show data
         */
        void showData();

    }

    interface Presenter extends IBasePresenter {
        /**
         * load data
         */
        void loadData();
    }
}

MVPPresenter.java.ftl

package ${packageName};

/**
 * <pre>
 *     @author : www.icheny.cn
 *     @e-mail : [email protected]
 *     @time   : ${createTime}
 *     @desc   :  MVP --> ${commonClassName} P --> Presenter. Child Presenter Implements Class For This Module.
 *     @version: 1.0.1
 * </pre>
 */
public class ${presenterClass} implements ${mvpInterface}.Presenter {

    private final ${mvpInterface}.View view;

    /**
     * Constructor
     *
     * @param view
     */
    public ${presenterClass}(${mvpInterface}.View view) {
        this.view = view;
    }

    @Override
    public void loadData() {

    }

    @Override
    public void doRefresh() {

    }
}

2. 定義UI面板,template.xml

......
    <parameter
        id="createTime"
        name="Create Time"
        type="string"
        default="2018.08.25"
        help="The time that will show on class annotation." />

    <parameter
        id="commonClassName"
        name="Common Class Name"
        type="string"
        default="Main"
        help="The string ,Other class will use for their name." />  

    <parameter
        id="activityClass"
        name="Activity Name"
        type="string"
        constraints="class|unique|nonempty"
        suggest="${commonClassName}Activity"
        default="MainActivity"
        help="The name of the activity class to create" />

    <parameter
        id="mvpInterface"
        name="MVP Interface Name"
        type="string"
        constraints="class|unique|nonempty"
        suggest="I${commonClassName}"
        default="IMain"
        help="The name of the mvp interface to create" />   

    <parameter
        id="presenterClass"
        name="Presenter Name"
        type="string"
        constraints="class|unique|nonempty"
        suggest="${commonClassName}Presenter"
        default="MainPresenter"
        help="The name of the mvp presenter class to create" /> 
.......

限於文章篇幅,這裏只貼添加和修改的代碼。

3. 定義執行模板代碼配置文件,recipe.xml.ftl

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

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

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

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

同樣,這裏只貼添加和修改的代碼。

完成以上,重啓AS,就可以愉快地玩耍了。

至此,本文結束。Demo源碼後續文章更新發布,敬請關注。

2018年09月3日更新:Demo源碼:Github:AndroidStudioPluginForMVP

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