前言
Android開發(fā)中經(jīng)常需要?jiǎng)?chuàng)建Activity递惋。一般情況下,咱們都是"New"->Java Class/Activity溺忧。但是Android Studio自帶的Activity模板都比較簡(jiǎn)單突梦,未必符合我們所需的模板樣式。例如在MVP框架下笛辟,需創(chuàng)建Activity功氨、Present、Contract手幢、Model等文件捷凄,并關(guān)聯(lián)關(guān)系,初始化围来、author跺涤、統(tǒng)一的網(wǎng)絡(luò)請(qǐng)求寫法匈睁。所以我們需要自定義模板,幫我們省去這一系列重復(fù)操作桶错。
舉個(gè)栗子航唆,創(chuàng)建自定義MVP模板(本文較長,趕時(shí)間的小伙伴可以到底部下載源碼)
圖解1 配置創(chuàng)建信息
MVP Name為紅框標(biāo)記部分統(tǒng)一命名(Activity牛曹、Layout佛点、Contract、Model黎比、Present)超营。由Generate MVP File選擇框決定是否在生成activity的同時(shí)生成Contract、Model和Presenter等Java類阅虫。
圖解2 文件結(jié)構(gòu)
點(diǎn)擊Finish完成創(chuàng)建后演闭,可以看到自動(dòng)生成的文件(如下圖)
這里我們主要看自動(dòng)生成的HomePageActivity。其他仨請(qǐng)看開頭Gif颓帝,這里就不一一截圖了
以上內(nèi)容均為自動(dòng)生成米碰,使用過MVP的小伙伴是不是很熟悉?是不是很方便购城?媽媽再也不用擔(dān)心我的CV大法導(dǎo)致代碼區(qū)域亂七八糟了吕座。
PS:每個(gè)項(xiàng)目MVP有或多或少差異,大家可以根據(jù)項(xiàng)目實(shí)際編寫合適的模板瘪板。
附上Activity分區(qū)注釋
/*-----------------------靜態(tài)Activity啟動(dòng)方法區(qū)-------------------*/
/*-----------------------常量聲明區(qū)-------------------------------*/
/*-----------------------UI控件成員變量聲明區(qū)---------------------*/
/*-----------------------普通成員變量聲明區(qū)-----------------------*/
/*-----------------------初始化相關(guān)方法區(qū)-------------------------*/
/*-----------------------生命周期回調(diào)方法區(qū)(除onCreate()方法外)- */
/*-----------------------事件響應(yīng)方法區(qū)---------------------------*/
/*-----------------------重載的邏輯方法區(qū)-------------------------*/
/*-----------------------普通邏輯方法區(qū)---------------------------*/
/*-----------------------內(nèi)部接口聲明區(qū)---------------------------*/
/*-----------------------內(nèi)部類聲明區(qū)-----------------------------*/
如何自定義模板
1.熟悉安卓模板
在Android Studio的安裝目錄下 \plugins\android\lib\templates\activities
(同Mac)保存著系統(tǒng)自帶的activity模板和我們自定義的模板
在編寫自定義模板前吴趴,需要熟悉下模板的結(jié)構(gòu)和組成,這里我們從最簡(jiǎn)單的模板EmptyActivity入手
src:代碼文件侮攀,生成對(duì)應(yīng)的文件模板
globals.xml.ftl:Java類庫锣枝,主要用于提供參數(shù)±加ⅲ可存儲(chǔ)全局變量以供其他模板文件統(tǒng)一引用
recipe.xml.ftl:用于組合生成我們實(shí)際需要的代碼文件和布局文件等撇叁。
template.xml:相當(dāng)于Android中的布局文件,用于圖形化提供參數(shù)畦贸,布局等陨闹。
template_blank_activity.png:模板照片
自定義模板通常都是復(fù)制已有,然后再修改修改
globals.xml.ftl
存儲(chǔ)全局變量薄坏。每一個(gè)變量定義正林,由id 唯一標(biāo)識(shí),type 類型颤殴,value 實(shí)際值組成
recipe.xml.ftl
即便不懂freemarker引擎也不影響觅廓,也能依樣畫葫蘆,這里就簡(jiǎn)單講下用到的標(biāo)簽及含義:
-
instantiate
將 ftl->java文件涵但,中間會(huì)通過一個(gè)步驟杈绸,將ftl中的變量都換成對(duì)應(yīng)的值帖蔓,完整的流程是 ftl->freemarker process -> java 。 -
open
用于轉(zhuǎn)換完成時(shí)在項(xiàng)目中對(duì)應(yīng)的包下生成對(duì)應(yīng)文件瞳脓,例如Activity -
if
if指令塑娇,這里if判斷是否生成layout文件- <#if >
<#elseif >
<#else >
</#if >
- <#if >
template.xml
對(duì)應(yīng)用戶輸入的模板界面,
<parameter>
獲取用戶輸入?yún)?shù)
-
id
唯一標(biāo)識(shí)劫侧,通過該屬性埋酬,獲取輸入值,也可用于其他文件查找引用 -
name
標(biāo)簽名稱烧栋,類似label展示給用戶看 -
type
輸入值類型 -
constraints
填寫值的約束 -
suggest
建議值写妥,例如填寫Activity Name,給出一個(gè)建議值 -
help
底部提示語言审姓,對(duì)應(yīng)圖形化界面操作New EmptyActivity
幾個(gè)文件的整體的關(guān)系類似下圖:
圖片來源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
2.定義MVPActivity模板
了解完以上簡(jiǎn)單介紹珍特,基本足夠我們寫自(zhào)定(yàng)義(huà)模(hú)板(lū)
2.1創(chuàng)建MVPActivity文件夾
一般都是復(fù)制現(xiàn)有的(例如EmptyActivity,或者文章末尾的源碼)魔吐,再改改扎筒。
添加MVP所對(duì)應(yīng)文件
2.2編寫MVP相關(guān)文件
activity_mvp.xml.ftl
<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="${packageName}.${activityClass}">
</RelativeLayout>
IContract.java.ftl
package ${packageName};
import com.xx.mvp.base.BaseModel;
import com.xx.mvp.base.BasePresenter;
import com.xx.mvp.base.BaseView;
import rx.Observable;
<#assign aDateTime = .now>
/**
* Model: {@link ${modelName}} View:{@link ${activityClass}} Presenter:{@link ${presenterName}}
* @Author: ${author}
* @Description:
* @Date: Create in ${aDateTime}
* @Modified By:
*/
public interface ${contractName} {
interface Model extends BaseModel {
/**
* 更新數(shù)據(jù)
*
* @return
*/
Observable<Object> update();
}
interface View extends BaseView {
}
interface Presenter extends BasePresenter<Model, View> {
/**
* 更新數(shù)據(jù)
*/
void update();
}
}
Model.java.ftl
package ${packageName};
import com.xx.bean.base.BaseResult;
import com.xx.http.rx.TransformUtils;
import com.xx.mvp.base.BaseModelImpl;
import retrofit2.http.POST;
import rx.Observable;
<#assign aDateTime = .now>
/**
* Present: {@link ${presenterName}}
* @Author: ${author}
* @Description:
* @Date: Create in ${aDateTime}
* @Modified By:
*/
public class ${modelName} extends BaseModelImpl<${modelName}.Service> implements ${contractName}.Model {
public ${modelName}() {super(${modelName}.Service.class);}
@Override
public Observable<Object> update() {
return getRequestService()
.update()
.compose(TransformUtils.defaultSchedulers());
}
public interface Service {
/**
* 更新數(shù)據(jù)
*
* @return
*/
@POST("pos-web/updateAllData")
Observable<BaseResult<Object>> update();
}
}
MVPActivity.java.ftl
package ${packageName};
import ${superClassFqcn};
import android.os.Bundle;
<#if includeCppSupport!false>
import android.widget.TextView;
</#if>
<#assign aDateTime = .now>
/**
<#if generateMVP>
* Model: {@link ${modelName}} Presenter:{@link ${presenterName}}
</#if>
* @Author: ${author}
* @Description:
* @Date: Create in ${aDateTime}
* @Modified By:
*/
<#if generateMVP>
public class ${activityClass} extends BaseActivity<${contractName}.Model, ${contractName}.View, ${contractName}.Presenter> implements ${contractName}.View {
<#else>
public class ${activityClass} extends BaseActivity{
</#if>
/*-----------------------靜態(tài)Activity啟動(dòng)方法區(qū)-------------------*/
public static void startActivity(Activity activity) {
Intent intent = new Intent(activity, ${activityClass}.class);
activity.startActivity(intent);
}
/*-----------------------常量聲明區(qū)-------------------------------*/
/*-----------------------UI控件成員變量聲明區(qū)---------------------*/
/*-----------------------普通成員變量聲明區(qū)-----------------------*/
/*-----------------------初始化相關(guān)方法區(qū)-------------------------*/
@Override
public int getContentView() {
return R.layout.${layoutName};
}
@Override
public void initView(Bundle savedInstanceState, View titleLayout) {
}
@Override
public void initData(Intent intent) {
getPresenter().update();
}
<#if generateMVP>
@Override
public ${contractName}.Model createModel() {
return new ${modelName}();
}
@Override
public ${contractName}.View createView() {
return this;
}
@Override
public ${contractName}.Presenter createPresenter() {
return new ${presenterName}();
}
<#else>
@Override
public BasePresenter createPresenter() {
return null;
}
</#if>
/*-----------------------生命周期回調(diào)方法區(qū)(除onCreate()方法外)-*/
/*-----------------------事件響應(yīng)方法區(qū)---------------------------*/
/*-----------------------重載的邏輯方法區(qū)-------------------------*/
/*-----------------------普通邏輯方法區(qū)---------------------------*/
/*-----------------------內(nèi)部接口聲明區(qū)---------------------------*/
/*-----------------------內(nèi)部類聲明區(qū)-----------------------------*/
}
Presenter.java.ftl
package ${packageName};
import android.os.Bundle;
import com.xx.http.rx.BaseSubscribe;
import com.xx.mvp.base.BasePresenterImpl;
import rx.Subscription;
<#assign aDateTime = .now>
/**
* Model: {@link ${modelName}} View:{@link ${activityClass}}
* @Author: ${author}
* @Description:
* @Date: Create in ${aDateTime}
* @Modified By:
*/
class ${presenterName} extends BasePresenterImpl<${contractName}.Model, ${contractName}.View>
implements ${contractName}.Presenter {
@Override
public void onCreate(Bundle savedInstanceState) {
}
/**
* 更新數(shù)據(jù)
*/
@Override
public void update() {
Subscription subscribe = getModel().update()
.subscribe(new BaseSubscribe<Object>(this) {
@Override
protected void onSuccess(Object bean) {
}
});
addSubscribeRequest(subscribe);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
2.3編寫配置文件
template.xml 定義界面
<?xml version="1.0"?>
<template
format="5"
revision="5"
name="MVPActivity"
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="activityName"
name="MVP Name"
type="string"
constraints="nonempty"
default="MVP"
help="The name of the MVP class to create" />
<parameter
id="activityClass"
name="MVPActivity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${activityName}Activity"
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(activityName)}"
default="activity_main"
visibility="generateLayout"
help="The name of the layout to create for the activity" />
<parameter
id="generateMVP"
name="Generate MVP File"
type="boolean"
default="true"
help="If true, a mvp file will be generated" />
<parameter
id="contractName"
name="MVP Contract"
type="string"
suggest="${activityName}Contract"
help="The name of the contract to create for the activity" />
<parameter
id="modelName"
name="MVP Model"
type="string"
suggest="${activityName}Model"
help="The name of the model to create for the activity" />
<parameter
id="presenterName"
name="MVP Presenter"
type="string"
suggest="${activityName}Presenter"
help="The name of the presenter 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>
recipe.xml.ftl
注意其中$變量,引用temlplate.xml中各控件的變量id酬姆,方便創(chuàng)建的文件名字已命名好嗜桌。
<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
<#include "../common/recipe_manifest.xml.ftl" />
<@kt.addAllKotlinDependencies />
<#if generateLayout>
<instantiate from="root/res/layout/activity_mvp.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"/>
</#if>
<#if generateMVP>
<instantiate from="root/src/app_package/IContract.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${contractName}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${contractName}.java" />
<instantiate from="root/src/app_package/Model.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${modelName}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${modelName}.java" />
<instantiate from="root/src/app_package/Presenter.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${presenterName}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${presenterName}.java" />
</#if>
<#if generateKotlin>
<instantiate from="root/src/app_package/MVPActivity.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<#else>
<instantiate from="root/src/app_package/MVPActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>
</recipe>
template.xml
想要讓id activityName統(tǒng)一命名Activity、Present辞色、Contract骨宠、Model、layout淫僻,需要通過suggest屬性統(tǒng)一引用它的id
<?xml version="1.0"?>
<template
format="5"
revision="5"
name="MVPActivity"
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="activityName"
name="MVP Name"
type="string"
constraints="nonempty"
default="MVP"
help="The name of the MVP class to create" />
<parameter
id="activityClass"
name="MVPActivity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${activityName}Activity"
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(activityName)}"
default="activity_main"
visibility="generateLayout"
help="The name of the layout to create for the activity" />
<parameter
id="generateMVP"
name="Generate MVP File"
type="boolean"
default="true"
help="If true, a mvp file will be generated" />
<parameter
id="contractName"
name="MVP Contract"
type="string"
suggest="${activityName}Contract"
help="The name of the contract to create for the activity" />
<parameter
id="modelName"
name="MVP Model"
type="string"
suggest="${activityName}Model"
help="The name of the model to create for the activity" />
<parameter
id="presenterName"
name="MVP Presenter"
type="string"
suggest="${activityName}Presenter"
help="The name of the presenter 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>
2.4小結(jié)诱篷,集(tì)成(huàn)為自己項(xiàng)目MVP
以上自定義MVP模板已經(jīng)全部結(jié)束壶唤。最后需要把MVPActivity挪移到Android Studio的安裝目錄下 \plugins\android\lib\templates\activities
(同Mac)雳灵,再重啟Android Studio,大功告成!
1.java.ftl模板中使用變量闸盔。
例如自動(dòng)生成的IContract.java文件中頂部含Header 信息悯辙,Present、Activity等信息(記得修改globals.xml.ftl
中author
值迎吵;java import com.xx.等導(dǎo)包)躲撰。如圖
2.內(nèi)聯(lián)的順序
內(nèi)聯(lián)的順序在代碼中是從上往下執(zhí)行的,因此要想生成文件后焦點(diǎn)窗口定位在新的Activity內(nèi)击费,則要把Activity的內(nèi)聯(lián)代碼放在最下面拢蛋,這樣所有文件生成完畢后才會(huì)優(yōu)先定位到Activity窗口
PS:若編碼的時(shí)候出現(xiàn)語法錯(cuò)誤,那么在Android studio中點(diǎn)擊finish生成Activity的時(shí)候會(huì)直接報(bào)錯(cuò)蔫巩,查看點(diǎn)擊log可以看到詳細(xì)的報(bào)錯(cuò)位置谆棱,自己再進(jìn)行修改就可以了快压。
附上模板源碼:MVP模板下載地址