**丨**版权说明 : 《玩转Android Studio自定义模板插件-MVP模板为例》于当前CSDN博客和乘月网属同一原创,转载请说明出处,谢谢。
得益于Android Studio强大的拓展功能,我们可以开发出适于自己项目的插件以满足快速高效的开发需求,本文以MVP模板插件为例
在日常开发中,新建一个如名为ZModuleActivity的Activity:
新建后Android Studio(下文简称:AS)会依据我们编辑和勾选的一些要求生成具有一定代码的.java和.xml的Activity和Layout文件,以及会帮我们在AndroidManifest.xml注册好这个新建的Activity。
大体这样:
像这样 -->
一定程度上为我们省下了很多创建文件,写一些基础代码的时间,但是这远远没有达到我们想要的效果。一个成熟的项目开发会有着自己的一套代码架构,层次和模板,这就意味着每次新建Activity都要修改些代码以沿用项目框架。
如MVP设计模式代码结构可能如下,以YModuleActivity
为例:
代码可能像这样 -->
AndroidManifest就不贴了,不是本文重点。
每新建一个Activity都要做大量无意义的基础代码工作,不知道你有没有这样的感觉:好烦啊,总是重复这些工作有意思吗?能不能直接帮我们生成好啊?有的人在感叹之余默默地把之前已经做好的(如本文 的YModule
系列代码:YModuleActivity,IYModule,YModulePresenter)相关文件给copy过来改文件名,改类名,改代码,改注释,删冗余代码,最后变成类似YModule系列文件和代码这样。嗯,的确有省了不少事,但毕竟还是一连串的事儿。
既然有了困扰,就成了需求,既然是需求,就得满足需求。好在AS允许我们开发这样的代码模板插件,那么正式进入今天的主题。
打开AS安装目录(本文为“android-studio”),依次打开:android-studio》plugins》android》lib》templates》activities 结果如下:
看到目录名是否觉得眼熟?如果你还反应过来,没关系,看下图:
没错,这里就是传说中新建Activity的模板插件集中营,每个目录即为一个插件。
知道这些就好办了,对EmptyActivity这个目录进行令人窒息的Ctrl C
and Ctrl V
操作, 然后对这个副本重命名(本文为“MVPActivity”)。你可能会问为什么一定是EmptyActivity?我可没有强调,只是顾名思义,空荡荡的模板可以省去删除一些冗余的模板代码操作,是吧?
打开MVPActivity目录,你可看到如下文件结构:
下面先简单介绍这几个文件,然后开始写模板
用于存放源代码模板和资源模板的.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布局中的标签用法。这里稍微提一下:EmptyActivity的root目录下只有源代码模板目录src,因为模板比较简单,其直接引用了EmptyActivity同级目录common的root目录下资源模板目录res,下文会有提及,本文只讲源代码模板,不做深究。
定义插件视图面板的外观,布局,类似咱Android的layout.xml布局文件,查看文件:
<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">Pathoption>
<option id="pathPrefix">Path Prefixoption>
<option id="pathPattern">Path Patternoption>
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" />
<thumbs>
<thumb>template_blank_activity.pngthumb>
thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
template>
对照着上文的【Empty Activity(插件)的视图面板】示意图或者自己新建个Empty Activity呼出面板,差不多能看懂或猜测到一大半吧?
下面是对parameter模块补充介绍:
${xxx}
中的变量声明一些全局变量的文件。查看文件:
<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和默认值。
用于执行模板代码的配置文件。查看文件:
<#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>
简单介绍下:
${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml
由函数,变量拼接文件名(包扩路径)的表达式,我们不需要了解它是怎么实现的,但根据你的开发经验一定能看得懂它会生成什么样的文件名(包括路径),我好像说了一句废话 T^T没什么好介绍的,模板效果缩略图文件,上文提到用thumb标签使其显示于面板上。
说好的简单介绍,结果还是BB了那么多
下面开始折腾“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. 需要一个时间变量(可选)
对于类的注释,比如这样:
/**
*
* @author : www.icheny.cn
* @e-mail : [email protected]
* @time : 2018.08.25
* @desc : ${commonClassName} Activity.
* @version: 1.0.1
*
*/
注释很多都可以共用或稍加修改便可,但是创建的时间(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;
/**
*
* @author : www.icheny.cn
* @e-mail : [email protected]
* @time : ${createTime}
* @desc : ${commonClassName} Activity.
* @version: 1.0.1
*
*/
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;
/**
*
* @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
*
*/
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};
/**
*
* @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
*
*/
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" />
template>
.......
限于文章篇幅,这里只贴添加和修改的代码。
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