概述
我們在使用Android Studio創建Activity、Fragment等等的時候,都會使用Android Studio提供的模板來簡化我們創建的,使用模板時,我們只要做簡單的配置,Android就能爲我們生成相應的代碼,所以使用模板可以提高開發的效率,接下來我們將學習如何去自定義一個符合自己項目框架的模板。
介紹
Android Studio模板的安裝路徑:
<AndroidStudio安裝目錄>/plugins/android/lib/templates
模板文件結構
Android Studio中已有的 EmptyActivity
模板:
模板組成結構:
- template.xml:定義模板參數
- globals.xml.ftl:定義全局變量
- recipe.xml.ftl:配置要引用的模板路徑和生成的文件的路徑
- root文件:存放模板文件和資源文件
- 效果縮略圖
模板變量處理流程:
template.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="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>
說明:
<template>
中的name
對應新建Activity
時顯示的名字<category>
對應New的類別爲Activity
<parameter>
對應界面上藍色框的一個項,- id:唯一表示,最終通過該屬性值,獲取用戶界面上的輸入值
- name:界面上Label提示語
- type:輸入值類型
- constraints:值約束
- suggest:建議值,比如填寫ActivityName的時候,會給出LayoutName的建議值
- help:底部顯示的提示語
globals.xml.ftl
<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>
這個文件用於定義一些全局變量。
說明:
<global>
:表示一個全局變量
- id:變量名
- type:變量類型
- value:默認值
訪問變量: ${變量id}
recipe.xml.ftl
<#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>
:導入另一個ftl文件<open>
:在代碼生成後打開指定文件,例如,當我們創建一個Activity後,AS會自動打開Activity及佈局文件。<instantiate>
:將.ftl
文件轉成.java
或.kt
文件。<copy>
:用於從root
文件夾中複製文件到目標目錄。<merge>
:用於合併文件,如將模板的strings.xml合併到我們項目中的strings.xml
Freemarker語法
AS 中模板的定義使用的是Freemarker的語法。
語法
${變量名}
:訪問變量值<#if 變量名>
:條件判斷<#include "xx.ftl">
:引入其他模板文件
實例:EmptyActivity\root\src\app_package\SimpleActivity.java.ftl
package ${packageName}; import ${superClassFqcn}; import android.os.Bundle; <#if (includeCppSupport!false) && generateLayout> 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}); <#include "../../../../common/jni_code_usage.java.ftl"> <#elseif includeCppSupport!false> // Example of a call to a native method android.util.Log.d("${activityClass}", stringFromJNI()); </#if> } <#include "../../../../common/jni_code_snippet.java.ftl"> }
從模板到代碼的流程:
自定義MVP模板
在Google給出的MVP Sample中,每創建一個頁面,需要創建:
XxActivity
、XxFragment
、XxContract
、XxPresenter
四個文件,步驟繁瑣,且AS目前沒有提供相應的模板,所以接下來將自定義一個MVP的模板,來簡化這些繁瑣的操作。
template.xml
<?xml version="1.0"?> <template format="5" revision="5" name="Page" minApi="9" minBuildApi="14" description="Creates a new MVP page"> <category value="MVP" /> <formfactor value="Mobile" /> <parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" suggest="${layoutToActivity(activityLayout)}" default="MainActivity" help="The name of the activity class to create" /> <parameter id="activityLayout" name="Activity Layout Name" type="string" constraints="layout|unique|nonempty" suggest="${activityToLayout(activityClass)}" default="activity_main" help="The name of the layout to create for the activity" /> <parameter id="fragmentClass" name="Fragment Name" type="string" constraints="class|unique|nonempty" suggest="${underscoreToCamelCase(classToResource(activityClass))}Fragment" default="MainFragment" help="The name of the fragment class to create" /> <parameter id="fragmentLayout" name="Fragment Layout Name" type="string" constraints="layout|unique|nonempty" suggest="fragment_${classToResource(fragmentClass)}" default="fragment_main" help="The name of the layout to create for the fragment" /> <parameter id="contractClass" name="Contract Name" type="string" constraints="class|unique|nonempty" suggest="${underscoreToCamelCase(classToResource(fragmentClass))}Contract" default="MainViewModel" help="The name of the contract class to create" /> <parameter id="presenterClass" name="Presenter Name" type="string" constraints="class|unique|nonempty" suggest="${underscoreToCamelCase(classToResource(fragmentClass))}Presenter" default="MainViewModel" help="The name of the presenter class to create" /> <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="packageName" name="Package name" type="string" constraints="package" default="com.mycompany.myapp" /> <parameter id="pagePackage" name="Page package path" type="string" constraints="package" suggest="ui.${classToResource(fragmentClass)?replace('_', '')}" default="ui.main" help="The package path for the page." /> <!-- 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>
globals.xml.ftl
<?xml version="1.0"?> <globals> <global id="hasNoActionBar" type="boolean" value="false" /> <global id="parentActivityClass" value="" /> <global id="simpleLayoutName" value="${activityLayout}" /> <global id="excludeMenu" type="boolean" value="true" /> <global id="generateActivityTitle" type="boolean" value="false" /> <#include "../../activities/common/common_globals.xml.ftl" /> </globals>
recipe.xml.ftl
<?xml version="1.0"?> <recipe> <#-- 生成mainfest配置 --> <merge from="root/AndroidManifest.xml.ftl" to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" /> <#-- 生成佈局文件 --> <instantiate from="root/res/layout/activity.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(activityLayout)}.xml" /> <instantiate from="root/res/layout/fragment.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentLayout)}.xml" /> <#-- 生成.java文件 --> <instantiate from="root/src/app_package/Activity.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${activityClass}.${ktOrJavaExt}" /> <instantiate from="root/src/app_package/Fragment.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${fragmentClass}.${ktOrJavaExt}" /> <instantiate from="root/src/app_package/Contract.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${contractClass}.${ktOrJavaExt}" /> <instantiate from="root/src/app_package/Presenter.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${presenterClass}.${ktOrJavaExt}" /> <#-- 打開文件.java文件 --> <open file="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${activityClass}.${ktOrJavaExt}" /> <open file="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${fragmentClass}.${ktOrJavaExt}" /> <open file="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${contractClass}.${ktOrJavaExt}" /> <open file="${escapeXmlAttribute(srcOut)}/${pagePackage?replace('.', '/')}/${presenterClass}.${ktOrJavaExt}" /> <#-- 打開佈局文件 --> <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(activityLayout)}.xml" /> <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentLayout)}.xml" /> </recipe>
Activity.java.ftl
package ${packageName}.${pagePackage}; import ${superClassFqcn}; import android.os.Bundle; import com.github.xch168.mvp.util.ActivityUtil; import ${packageName}.R; public class ${activityClass} extends ${superClass} { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.${activityLayout}); ${fragmentClass} fragment = (${fragmentClass}) getSupportFragmentManager().findFragmentById(R.id.contentFrame); if (fragment == null) { fragment = ${fragmentClass}.newInstance(); ActivityUtil.addFragmentToActivity(getSupportFragmentManager(), fragment, R.id.contentFrame); } new ${presenterClass}(fragment); } }
Fragment.java.ftl
package ${packageName}.${pagePackage}; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import ${packageName}.R; public class ${fragmentClass} extends Fragment implements ${contractClass}.View { private ${contractClass}.Presenter mPresenter; public static ${fragmentClass} newInstance() { Bundle arguments = new Bundle(); arguments.putString("", ""); ${fragmentClass} fragment = new ${fragmentClass}(); fragment.setArguments(arguments); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.${fragmentLayout}, container, false); return root; } @Override public void onResume() { super.onResume(); mPresenter.start(); } @Override public void setPresenter(@NonNull ${contractClass}.Presenter presenter) { mPresenter = presenter; } }
Contract.java.ftl
package ${packageName}.${pagePackage}; import com.github.xch168.mvp.ui.BasePresenter; import com.github.xch168.mvp.ui.BaseView; public interface ${contractClass} { interface View extends BaseView<Presenter> { } interface Presenter extends BasePresenter { } }
Presenter.java.ftl
package ${packageName}.${pagePackage}; public class ${presenterClass} implements ${contractClass}.Presenter { private final ${contractClass}.View mView; public ${presenterClass}(${contractClass}.View view) { mView = view; mView.setPresenter(this); } @Override public void start() { } }
AndroidManifest.xml.ftl
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${packageName}"> <application> <activity android:name="${packageName}.${pagePackage}.${activityClass}" <#if generateActivityTitle!true> <#if isNewProject> android:label="@string/app_name" <#else> android:label="@string/title_${activityToLayout(activityClass)}" </#if> </#if> <#if hasNoActionBar> android:theme="@style/${themeNameNoActionBar}" <#elseif (requireTheme!false) && !hasApplicationTheme && appCompat> 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> </activity> </application> </manifest>
使用MVP模板
將模板文件複製到
<AndroidStudio安裝目錄>/plugins/android/lib/templates/{userName}/MVP
目錄下,然後重啓Android Studio。
Step1:新建一個MVP頁面
Step2:配置參數
Step3:點擊Finish,將自動生成相關代碼及資源文件
參考鏈接
- https://blog.csdn.net/lmj623565791/article/details/51635533
- http://www.slideshare.net/murphonic/custom-android-code-templates-15537501
- https://puke3615.github.io/2017/03/06/TemplateBuilder[Chinese]/