android 自定义模板下载,android studio 自定义模板
由于項(xiàng)目用上了 mvp 架構(gòu),基本上一個(gè)頁面就至少需要新創(chuàng)建6個(gè)類,分別是 model view presenter 的接口以及其對(duì)應(yīng)的實(shí)現(xiàn)類,再加上使用 dagger 的話就要更多了,所以這時(shí)候 android studio 的自定義模板就派上用場了,可以節(jié)省很多編寫模板代碼的重復(fù)性工作
那么該如何入手呢?相信大部分用過 as 的人以及使用過一些自帶的模板樣式了,這些自帶的模板就是最好的參照目標(biāo)了,廢話不多說,先看看它的結(jié)構(gòu)
1.模板結(jié)構(gòu)
這里參照的是 empty activity
Empty Activity
它的位置就在 as的安裝目錄(mac的話右鍵as應(yīng)用-> 顯示包內(nèi)容 -> content 里就是了)/plugins/android/lib/templates/activities,
模板的結(jié)構(gòu)
這里簡單做個(gè)總結(jié):
template:主要是給生成頁面提供一些需要用戶傳入的參數(shù)
global.xml.ftl:主要提供一些全局參數(shù)
recipe.xml.ftl:主要用于生成實(shí)際需要的代碼,資源文件等
root文件夾:包含 project 中一系列屬性文件的模板
root 底下還有一些相關(guān)文件介紹
build.gradle.ftl:project 的 build.gradle 模板,如果需要添加 maven 庫的地址,就在這里添加
gradle.properties.ftl:project 的 gradle.properties 的模板,如果需要添加工程的一些公用屬性(版本號(hào)\版本名\簽名信息\私有 maven 庫的 group 和 id 信息等)就在這里面修改
local.properties.ftl:project 的 local.properties.ftl 模板,里面指定 SDK的路徑,如果設(shè)置好環(huán)境變量,創(chuàng)建工程的時(shí)候就動(dòng)態(tài)生成指定的路徑,不需要手動(dòng)修改
project_ignore:project 的.gitingore 模板,里面可以增刪版本管理需要過濾的文件夾\文件
settings.gradle.ftl:project 的 settings.gradle 模板,里面可以指定真?zhèn)€工程需要編譯的 module,這個(gè)建議不要修改,可以在工程中手動(dòng)修改
1.1首先是 template.xml 文件,打開后的主要內(nèi)容如下
format="5"
revision="5"
name="Empty Activity"
minApi="9"
minBuildApi="14"
description="Creates a new empty activity">
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" />
template_blank_activity.png
其中
1.的 name 屬性,對(duì)應(yīng)新建 Activity 時(shí)顯示的名字
2.對(duì)應(yīng) New 的類別為 Activity
頁面和屬于對(duì)照
現(xiàn)在來詳解 parameter標(biāo)簽 的屬性
id:唯一表示,最終通過這個(gè)屬性來獲取輸入值(分為input 和 checkbox)
name:相當(dāng)于 hint 了
type:屬性的類型,分為 String 和 Boolean
constraints:填寫值的約束
suggest:建議值
default:默認(rèn)值
visibility:是否顯示(一般就是根據(jù)其他類型為 checkbox 的 parameter 來確定了),例如上圖的 layoutname,只有 generateLayout 為 true 時(shí)才顯示
generateLayout 為 false 時(shí)不顯示 Layout Name
generateLayout 為 true 時(shí)顯示 Layout Name
help:鼠標(biāo)懸浮在該 parameter 時(shí)顯示的幫助提示
help 屬性的效果
然后是 thumbs 標(biāo)簽,也沒啥,就是個(gè)縮略圖罷了
thumbs
最后還有兩個(gè)標(biāo)簽,引用了外部文件,也是下面要講的內(nèi)容
1.2 globals.xml.ftl
里面定義的是一些全局變量,方便其他文件可以引用這里的值,引用的方式是&{id的值}
最后可以看到還引用了另外一個(gè) ftl,這也說明了這個(gè)文件里定義的屬性同時(shí)也可以被其他模板引用
1.3 recipe.xml.ftl
#if>
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
跳過兩個(gè) include 引入的 ftl,先介紹能看到的標(biāo)簽
open:在代碼生成后,打開指定的文件,這里寫了兩個(gè) open,所以創(chuàng)建了一個(gè) activity 后,就會(huì)把 activity 的 java 文件和layout.xml 同時(shí)打開
instantiate:就是把模板轉(zhuǎn)換成實(shí)際目標(biāo)文件的一個(gè)操作了,from 指定的是模板文件,to 指定的是生成文件,后面再詳細(xì)介紹
然后可以看到前面還 include 了兩個(gè) ftl,實(shí)際上代表的就是 menifest 和 layout 的相關(guān)操作,下面是 recipe_manifest.xml.ftl 的內(nèi)容
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
這里又看到一個(gè)新的標(biāo)簽merge,字面意義就是合并,也就是把模板文件合并到項(xiàng)目中已經(jīng)存在的對(duì)應(yīng)文件中,這里是合并了 AndroidManifest.xml 和 string.xml
recipe中還有一個(gè)比較常見的標(biāo)簽,這個(gè)模板里沒看到
copy :從root中copy文件到我們的目標(biāo)目錄,比如我們的模板Activity需要使用一些圖標(biāo),那么可能就需要使用copy標(biāo)簽將這些圖標(biāo)拷貝到我們的項(xiàng)目對(duì)應(yīng)文件夾。
2.代碼生成的過程
模板里的文件基本都是 ftl 結(jié)尾的, 這里首先需要要解釋一下 ftl 的概念
ftl是FreeMarker Template Language的縮寫,它是簡單的,專用的語言, 不是 像PHP那樣成熟的編程語言。 那就意味著要準(zhǔn)備數(shù)據(jù)在真實(shí)編程語言中來顯示,比如數(shù)據(jù)庫查詢和業(yè)務(wù)運(yùn)算, 之后模板顯示已經(jīng)準(zhǔn)備好的數(shù)據(jù)。在模板中,你可以專注于如何展現(xiàn)數(shù)據(jù), 而在模板之外可以專注于要展示什么數(shù)據(jù)。
而AS中的這些模板是就是通過這個(gè)FreeMarker模板引擎創(chuàng)建的
FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來生成輸出文本(HTML網(wǎng)頁,電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個(gè)Java類庫,是一款程序員可以嵌入他們所開發(fā)產(chǎn)品的組件。
代碼生成的簡單示意圖
3.簡單的 freemarker 語法
1.插入屬性
定義好一個(gè)屬性,在模板文件中使用${定義好的屬性名稱},即可完成替換
2.if 語法
例如前面在 recipe.xml.ftl 里看到的, 這個(gè)generateLayout 是再 template 中定義的 boolean 的 parameter
#if>
下面以 Empty Activity模板中的 SimpleActivity.java.ftl 為例子
package ${packageName}; import ${superClassFqcn}; import android.os.Bundle; import android.widget.TextView; #if> public class ${activityClass} extends ${superClass} { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.${layoutName}); #if> } }
可以看到模板中有很多變量需要替換后才能生成為最終需要的代碼,這些變量一般來自 globals.xml.ftl 中預(yù)先定義好的變量以及 template 中需要用戶輸入的變量,經(jīng)過 recipe.xml.ftl 中的instantiate標(biāo)簽指定生成的路徑即可完成這個(gè)過程
4.具體示例
最近的項(xiàng)目用到 mvp 和 dagger(這里就不細(xì)談 dagger 的用法了),所以每個(gè)頁面要多寫很多接口以及實(shí)現(xiàn)類,下面是項(xiàng)目的分包:
包結(jié)構(gòu)
contract:定義了一個(gè)頁面的 presenter 和 view 的接口,放在contract 里是為了方便查看
di.component.presenter:用于往目標(biāo)類注入 presenter
di.module:提供presenter所依賴的組件
model.event:model的接口
model.impl:model 的實(shí)現(xiàn)類
presenter:contract 的 presenter 實(shí)現(xiàn)類
4.1 模板代碼分析
下面分析一下,編寫一個(gè)具體業(yè)務(wù)要生成哪些模板代碼,例如要做一個(gè)登錄的業(yè)務(wù)
4.1.1 mvp 的部分
首先要定義的是Contract, 包含整個(gè)業(yè)務(wù)邏輯與頁面顯示
public interface LoginContract {
// Contract 中肯定是要包括 View 和 Presenter 的
interface View {
// 具體的方法,這部分不是模板能解決的
void showLoginSuccess(UserInfo info);
void showLoginFailed(Throwable e);
}
interface Presenter {
// 具體的方法,這部分不是模板能解決的
void loginIn(String account, String password);
}
}
** View 的實(shí)現(xiàn),一般就是讓 activity 或者 fragment 實(shí)現(xiàn) LoginContract.View 了,這部分不是模板能解決的,就不寫了**
然后是 Presenter 的實(shí)現(xiàn),當(dāng)然實(shí)現(xiàn) LoginContract.Presenter 即可
//項(xiàng)目有Presenter 的基類的話模板里還需要添加繼承
public class LoginPresenter extends BasePresenter implement LoginContract.Presenter {
// 接口定義的方法,也不是模板能解決的
public void loginIn(String account, String password) {
// 具體的實(shí)現(xiàn)代碼
}
}
4.1.2 Presenter的部分
這里presenter 的具體實(shí)現(xiàn)實(shí)際上也是包含很多重復(fù)代碼的
首先,Presenter 里肯定需要持有 上面定義的Contract.View 的引用,這樣才能再邏輯處理結(jié)束后回調(diào) View 層代碼
然后,Presenter 也需要持有 Model層的引用去處理數(shù)據(jù),一般 Model 層也是需要定義接口的,所以又多了兩個(gè)類:LoginModel 和 LoginModelImpl
public interface LoginModel {
void login(String account, String password);
}
public class LoginModel extends BaseModel implement LoginModel {
public void login(String account, String password) {
// 具體代碼實(shí)現(xiàn)
}
}
修改后的 Presenter 代碼為
public class LoginPresenter extends BasePresenter implement LoginContract.Presenter {
private LoginContract.View mView;
private LoginModel mModel;
public LoginPresenter(LoginContract.View view, LoginModel model) {
mView = view;
mModel = model;
}
// 接口定義的方法,也不是模板能解決的
public void loginIn(String account, String password) { // 具體的實(shí)現(xiàn)代碼 }
}
由于一般的業(yè)務(wù)都是要通過請(qǐng)求或者本地?cái)?shù)據(jù)庫來處理的,所以這里抽取父類 BaseModel,項(xiàng)目里使用了 GreenDao 和 Retrofit,所以 BaseModel依賴于DaoMaster.DevOpenHelper和 Retrofit 兩個(gè)對(duì)象
public class BaseModel {
protected final Retrofit retrofit;
protected final DaoMaster.DevOpenHelper dbOpenHelper;
public BaseModel(DaoMaster.DevOpenHelper helper, Retrofit retrofit) {
this.dbOpenHelper = helper;
this.retrofit = retrofit;
}
}
修改后的 LoginModelImpl的代碼為
public class LoginModelImpl extends BaseModel implement LoginModel {
public LoginModelImpl(DaoMaster.DevOpenHelper helper, Retrofit retrofit) {
super(helper, retrofit);
}
public void login(String account, String password) {
// 具體代碼實(shí)現(xiàn)
}
}
到這里登錄業(yè)務(wù)的 P層和 M 層代碼基本就寫完了,一共需要 LoginContract/ LoginPresenter/LoginModel/LoginModelImpl四個(gè)文件
4.1.3 dagger 的部分
首先這里說一下 dagger 的好處,簡單來說,dagger 就是將目標(biāo)類與其依賴的對(duì)象的實(shí)例化過程隔離開來,例如這里的 LoginPresenter,一般在 activity 或者 fragment 中實(shí)例化
public class LoginActivity extends Activity implement LoginContract.View {
private LoginPresenter mPresenter;
public void onCreate(Bundle saveInstanceState) {
super.onCrate(saveInstanceState);
// 省略DaoMaster.DevOpenHelper 和 Retrofit 的實(shí)例化
....
mPresenter = new LoginPresenter(this, new LoginModelImpl(helper, retrofit));
}
void showLoginSuccess(UserInfo info){...}
void showLoginFailed(Throwable e){...}
}
實(shí)際上寫這種 new 的代碼是很 low 的,萬一 LoginPresenter 的構(gòu)造函數(shù)被修改了,就需要修改 LoginActivity 的代碼,如果這個(gè) LoginPresenter 到處都是的話,那就悲催了...
所以dagger就是為了解決這個(gè)問題而存在的,dagger 是一種依賴注入, 此處 LoginActivity 依賴于 LoginPresenter, dagger 可以把 LoginPresenter 的實(shí)例化放在一個(gè)獨(dú)立的模塊中去執(zhí)行,而 LoginActivity 不必關(guān)心也不知曉 Presnter 的實(shí)例化過程,這樣上面的問題就迎刃而解了.至于 dagger 的用法這里就忽略了
接下來講使用 dagger 所需要?jiǎng)?chuàng)建的類
mPresenter = new LoginPresenter(this, new LoginModelImpl(helper, retrofit));
從這句代碼就可以看出 LoginPresenter依賴于兩個(gè)對(duì)象,一個(gè)是 View 接口,另一個(gè)是 LoginModel 接口,修改 LogingPresenter:
public class LoginPresenter extends BasePresenter implement LoginContract.Presenter {
private LoginContract.View mView;
private LoginModel mModel;
@Inject
public LoginPresenter(LoginContract.View view, LoginModel model) {
mView = view;
mModel = model;
}
// 接口定義的方法,也不是模板能解決的
public void loginIn(String account, String password) { // 具體的實(shí)現(xiàn)代碼 }
}
這里給 LoginPresenter 的構(gòu)造函數(shù)添加 @Inject 注解,這樣 dagger 就能判斷這是一個(gè)可用依賴注入實(shí)例化的目標(biāo)
接下來,LoginPresenter 又有進(jìn)一步的依賴,由于傳入的參數(shù)都是接口,是不可能用 @Inject 標(biāo)注在構(gòu)造函數(shù)的了,所以這里又需要 dagger 中的Module提供實(shí)現(xiàn)類的對(duì)象,本著 m 層和 v 層分離的原則,這里就需要兩個(gè) Module
@Module
public class LoginViewModule {
LoginContract.View view;
public LoginViewModule(LoginContract.View view) {
this.view = view;
}
@Provide
public LoginContract.View provideLoginView() {
return view;
}
}
@Module
public class LoginModelModule {
Context context;
public LoginModelModule(Context context) {
this.context = context;
}
@Provide
public LoginModel provideLoginModel() {
// 省略DaoMaster.DevOpenHelper 和 Retrofit 的實(shí)例化
....
return new LoginModelImpl(helper, retrofit);
}
}
最后就是 Component 注入器了
@Component(dependencies = {LoginModelModule.class, LoginViewModule.class})
public interface LoginPresenterComponent {
void inject(LoginActivity activity);
}
到這里 dagger 部分的代碼也就完了,下面開始編寫自定義模板,這里列舉一下所有需要的模板代碼
Contract 類
package ${packageName}.contract;
public interface ${businessName}Contract {
interface View {}
interface Presenter{}
}
Presneter 實(shí)現(xiàn)類
package ${modulePackageName}.presenter;
import ${modulePackageName}.contract.${businessName}Contract;
import ${modulePackageName}.model.event.${businessName}Model;
import ${parentPresenterPackage}.${basePresenterClassName};
import javax.inject.Inject;
public class ${businessName}Presenter extends ${basePresenterClassName} implements ${businessName}Contract.Presenter {
private ${businessName}Contract.View view;
private ${businessName}Model model;
@Inject
public ${businessName}Presenter(${businessName}Contract.View view, ${businessName}Model model) {
this.view = view;
this.model = model;
}
}
Model 接口
package ${packageName}.model.event;
public interface ${businessName}Model {}
Model 實(shí)現(xiàn)類
package ${packageName}.model.impl;
import ${packageName}.model.event.${businessName}Model;
import ${daoPackage}.DaoMaster;
import ${projectPackage}.model.impl.${baseModelClassName};
import retrofit2.Retrofit;
public class ${businessName}ModelImpl extends ${baseModelClassName} implements ${businessName}Model {
public ${businessName}ModelImpl(DaoMaster.DevOpenHelper dataBaseHelper, Retrofit retrofit) {
super(dataBaseHelper, retrofit);
}
}
Model 接口 的 Module
package ${packageName}.di.module.model;
import android.content.Context;
import ${daoPackage}.DaoMaster;
import ${packageName}.model.event.${businessName}Model;
import ${packageName}.model.impl.${businessName}ModelImpl;
import retrofit2.Retrofit;
import dagger.Module;
import dagger.Provides;
@Module
public class ${businessName}ModelModule {
Context context;
public ${businessName}ModelModule(Context context) {
this.context = context;
}
@Provides
public ${businessName}Model provide${businessName}Model() {
// 省略DaoMaster.DevOpenHelper 和 Retrofit 的實(shí)例化
....
return new ${businessName}ModelImpl(helper, retrofit);
}
}
View層接口的 Module
package ${packageName}.di.module.view;
import ${packageName}.contract.${businessName}Contract;
import dagger.Module;
import dagger.Provides;
@Module
public class ${businessName}ViewModule {
${businessName}Contract.View view;
public ${businessName}ViewModule(${businessName}Contract.View view) {
this.view = view;
}
@Provide
public ${businessName}Contract.View provide${businessName}View() {
return view;
}
}
Presenter實(shí)例的注入器
package ${packageName}.di.component.presenter;
import ${packageName}.di.module.view.${businessName}ViewModule;
import @{packageName}.di.module.model.${businessName}ModelModule;
import dagger.Component;
@Component(dependencies = {${businessName}ViewModule.class, ${businessName}ModelModule.class})
public interface ${businessName}PresenterComponent {
void inject(t target);
}
模板代碼準(zhǔn)備好之后就可以開始制作模板了
首先復(fù)制整個(gè) Empty Activity模板(推薦復(fù)制過來再修改的方式)
由于這個(gè)模板不涉及 activity 和manifest.xml 以及 layout,所以先刪掉相關(guān)的標(biāo)簽
先從template.xml開始,刪掉沒用的 parameter,留下一個(gè) packageName,然后添加一個(gè)業(yè)務(wù)名稱,有這兩個(gè)就夠了
接著是設(shè)置 globals,設(shè)置一個(gè) srcOut
最后是配置 recipe.xml.ftl, 根據(jù)自己想要的包修改一下路徑即可, 只是簡單的復(fù)制工作而已了
to="${escapeXmlAttribute(srcOut)}/contract/${businessName}Contract.java" />
to="${escapeXmlAttribute(srcOut)}/model/event/${businessName}Model.java" />
to="${escapeXmlAttribute(srcOut)}/model/impl/${businessName}ModelImpl.java" />
to="${escapeXmlAttribute(srcOut)}/di/module/model/${businessName}ModelModule.java" />
to="${escapeXmlAttribute(srcOut)}/di/module/view/${businessName}ViewModule.java"/>
to="${escapeXmlAttribute(srcOut)}/presenter/${businessName}Presenter.java"/>
to="${escapeXmlAttribute(srcOut)}/di/component/presenter/${businessName}PresenterComponent.java"/>
5.遇到的一些坑
1.模板一旦有錯(cuò),as 跑起來就跪了,窗口關(guān)不掉只能強(qiáng)行關(guān)閉 as 再開過
2.前面提到要留下這個(gè) packageName 本來想做成固定路徑的,但不是報(bào)錯(cuò)就是路徑不對(duì).另外不指定這個(gè) id 就沒辦法弄到當(dāng)前的路徑和包,不知道是為啥
3.這個(gè)是網(wǎng)上搜索的時(shí)候看到的,貌似自定義的模板會(huì)造成as 升級(jí)失敗,如果遇到,把這份模板剪切出來,升級(jí)結(jié)束后再復(fù)制回去即可
總結(jié)
以上是生活随笔為你收集整理的android 自定义模板下载,android studio 自定义模板的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言stoi函数源码,一系列相关函数的
- 下一篇: android wear 2.0 mot