在编写android项目中,我们难免会重复的去写一些东西或者写一些浪费时间但又和我们项目逻辑关系不大的代码,所以学会编写模板代码变得很重要,我这里指得模板有两种:
-
单个文件
-
某个功能
对于第一种情况,如下:
singleton.png
在上面我们仿照系统文件新建了两个模板类,RecycleViewAdapter和Singleton,在我们新建类列表中可以看到。我们得代码是仿照着class文件编写得,class文件内容如下:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
public class ${NAME} {
}
PACKAGE_NAME | NAME | File Header.java |
---|---|---|
包名 | 类名 | 头部文件说明 |
头部文件如下:
header.png
这里单例实现用静态内部类实现得,因为静态内部类初始化是由java虚拟机管理的,classloder加载,线程安全,下面是这两个类的代码:
singleton.java
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
public class ${NAME}{
public static ${NAME} getInstance() {
return ${NAME}Holder.sInstance;
}
private ${NAME}() {
}
private static class ${NAME}Holder {
private static final ${NAME} sInstance = new ${NAME}();
}
}
RecycleViewAdapter.java
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
#parse("File Header.java")
public class ${NAME} extends RecyclerView.Adapter<${NAME}.ViewHolder> {
private Context ctx; private List<Object> objects;
public ${NAME}(Context ctx, List<Object> objects) {
this.ctx = ctx; this.objects = objects;
}
private @LayoutRes int provideItemLayout() {
// todo
return 0;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(ctx).inflate(provideItemLayout(), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(objects.get(position));
}
@Override
public int getItemCount() {
return objects == null ? 0 : objects.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
//todo
}
public void bind(Object o) {
// todo
} } }
最后使用的话就很简单了,在我们第一张图中新建模板上面两个类就是我们新建的模板,直接点击就可以生成我们想要的类了,我们只需要输入相应的类名即可。
第二种情况是针对某个功能,我们可能会新建很多上面的那种类,就是我们新建项目的时候系统提供给我们提供的各种模板,在Android studio中新建activity、fragment时用到。会同时新建相应的布局,注册manifest等等,如下:
yy.gif
上面这个模板本来是MVPArm作者写的,是一个mvp+dagger的架构实现,我这里对里面的部分内容进行了修改,用以适应我自己的demo,整个模板的核心是freemarker,实现思路如下:
free.png
freemarker作用是把我们写的.jva.ftl文件生成我们自己需要的.java文件,而我们需要生成的文件名,布局名、包名等内容在初始化界面时输入即可,整个功能是一个单独的包,现在我们来分析一下这个组件的包里面的内容:
ftl.png layout.png t.png template.png
如上图,我们需要的所有内容就在MVPTemplate里面,其中最重要的是template.xml,这里面进行我们需要关键字的注册,如包名、类名啥的:
template.xml
<?xml version="1.0"?>
<template
format="5"
revision="1"
name="MVP Template"
minApi="9"
minBuildApi="15"
description="mvp demo">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="pageName"
name="Page Name"
type="string"
constraints="unique|nonempty"
default="Main"
help="请填写页面名,如填写 Main,会自动生成 MainActivity, MainPresenter 等文件" />
<parameter
id="packageName"
name="Root Package Name"
type="string"
constraints="package"
default="com.mycompany.myapp"
help="请填写你的项目包名,请认真核实此包名是否是正确的项目包名,不能包含子包"
/>
<parameter
id="needActivity"
name="Generate Activity"
type="boolean"
default="true"
help="是否需要生成 Activity ? 不勾选则不生成" />
<parameter
id="activityLayoutName"
name="Activity Layout Name"
type="string"
constraints="layout|nonempty"
suggest="${activityToLayout(pageName)}"
default="activity_main"
visibility="needActivity"
help="Activity 创建之前需要填写 Activity 的布局名,若布局已创建就直接填写此布局名,若还没创建此布局,请勾选下面的单选框" />
<parameter
id="generateActivityLayout"
name="Generate Activity Layout"
type="boolean"
default="true"
visibility="needActivity"
help="是否需要给 Activity 生成布局? 若勾选,则使用上面的布局名给此 Activity 创建默认的布局" />
<parameter
id="ativityPackageName"
name="Ativity Package Name"
type="string"
constraints="package"
suggest="${packageName}.mvp.ui.activity"
visibility="needActivity"
help="Activity 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="needFragment"
name="Generate Fragment"
type="boolean"
default="false"
help="是否需要生成 Fragment ? 不勾选则不生成" />
<parameter
id="fragmentLayoutName"
name="Fragment Layout Name"
type="string"
constraints="layout|nonempty"
suggest="fragment_${classToResource(pageName)}"
default="fragment_main"
visibility="needFragment"
help="Fragment 创建之前需要填写 Fragment 的布局名,若布局已创建就直接填写此布局名,若还没创建此布局,请勾选下面的单选框" />
<parameter
id="generateFragmentLayout"
name="Generate Fragment Layout"
type="boolean"
default="true"
visibility="needFragment"
help="是否需要给 Fragment 生成布局? 若勾选,则使用上面的布局名给此 Fragment 创建默认的布局" />
<parameter
id="fragmentPackageName"
name="Fragment Package Name"
type="string"
constraints="package"
suggest="${packageName}.mvp.ui.fragment"
visibility="needFragment"
help="Fragment 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="needContract"
name="Generate Contract"
type="boolean"
default="true"
help="是否需要生成 Contract ? 不勾选则不生成" />
<parameter
id="contractPackageName"
name="Contract Package Name"
type="string"
constraints="package"
suggest="${packageName}.mvp.contract"
visibility="needContract"
help="Contract 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="needPresenter"
name="Generate Presenter"
type="boolean"
default="true"
help="是否需要生成 Presenter ? 不勾选则不生成" />
<parameter
id="presenterPackageName"
name="Presenter Package Name"
type="string"
constraints="package"
suggest="${packageName}.mvp.presenter"
visibility="needPresenter"
help="Presenter 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="needModel"
name="Generate Model"
type="boolean"
default="true"
help="是否需要生成 Model ? 不勾选则不生成" />
<parameter
id="modelPackageName"
name="Model Package Name"
type="string"
constraints="package"
suggest="${packageName}.mvp.model"
visibility="needModel"
help="Model 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="needDagger"
name="Generate Dagger (Moudle And Component)"
type="boolean"
default="true"
help="是否需要生成 Dagger 组件? 不勾选则不生成" />
<parameter
id="componentPackageName"
name="Component Package Name"
type="string"
constraints="package"
suggest="${packageName}.di.component"
visibility="needDagger"
help="Component 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<parameter
id="moudlePackageName"
name="Moudle Package Name"
type="string"
constraints="package"
suggest="${packageName}.di.module"
visibility="needDagger"
help="Moudle 将被输出到此包下,请认真核实此包名是否是你需要输出的目标包名"
/>
<!-- 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>
上面大部分属性猜大概都能猜出来什么意思,比较重要的是<parameter 参数中的值,id是唯一属性,在.java.ftl文件中调用,constraints是约束条件,suggest表示当前具体的包名、name和type是对应的输入项名称和类型,type为boolean情况下时,界面上生成的是勾选框而不是输入框,一个<parameter就是界面的一个操作项,我们可以进行动态的增加或者删除,那再看一下我们的这个属性在我们代码中是如何使用的吧:
MvpActivity.java.ftl
package ${ativityPackageName};
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import ${packageName}.R;
import ${componentPackageName}.Dagger${pageName}Component;
import ${moudlePackageName}.${pageName}Module;
import ${contractPackageName}.${pageName}Contract;
import ${presenterPackageName}.${pageName}Presenter;
import javax.inject.Inject;
public class ${pageName}Activity extends AppCompatActivity implements ${pageName}Contract.View {
@Inject
${pageName}Presenter ${pageName}Presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${activityLayoutName});
Dagger${pageName}Component.builder()
.${extractLetters(pageName[0]?lower_case)}${pageName?substring(1,pageName?length)}Module(new ${pageName}Module(this))
.build()
.inject(this);
${pageName}Presenter.getData();
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
@Override
public void showMessage(String message) {
}
@Override
public void launchActivity(Intent intent) {
}
@Override
public void killMyself() {
}
@Override
public void showData(String string) {
Log.e("yy", string);
}
}
代码中用${}符号表示EL表达式,目的是为了在代码中能够调用到template中传入的内容,仔细看我们的template.xml文件结尾其实还有两个类,globals.xml.ftl和recipe.xml.ftl,也包含一个生成案例的图片展示thumb字段,globals.xml.ftl中主要是声明一些属性格式:
<?xml version="1.0"?>
<globals>
<global id="hasNoActionBar" type="boolean" value="false" />
<global id="parentActivityClass" value="" />
<global id="excludeMenu" type="boolean" value="true" />
<global id="isLauncher" type="boolean" value="false" />
<global id="generateActivityTitle" type="boolean" value="false" />
<global id="relativePackage" value="${ativityPackageName}" />
<global id="activityClass" value="${pageName}Activity" />
<#include "../common/common_globals.xml.ftl" />
</globals>
recipe.xml.ftl文件主要是告诉freemarker文件从什么地方来(from xxx.java.ftl),到什么地方去(to xxx.java),如下:
<?xml version="1.0"?>
<recipe>
<#if needActivity>
<merge from="root/AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
</#if>
<#if needActivity && generateActivityLayout>
<instantiate from="root/res/layout/simple.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
</#if>
<#if needFragment && generateFragmentLayout>
<instantiate from="root/res/layout/simple.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
</#if>
<#if needActivity>
<instantiate from="root/src/app_package/MvpActivity.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(ativityPackageName)}/${pageName}Activity.java" />
<open file="${projectOut}/src/main/java/${slashedPackageName(ativityPackageName)}/${pageName}Activity.java" />
</#if>
<#if needFragment>
<instantiate from="root/src/app_package/MvpFragment.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(fragmentPackageName)}/${pageName}Fragment.java" />
<open file="${projectOut}/src/main/java/${slashedPackageName(fragmentPackageName)}/${pageName}Fragment.java" />
</#if>
<#if needContract>
<instantiate from="root/src/app_package/MvpContract.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(contractPackageName)}/${pageName}Contract.java" />
</#if>
<#if needPresenter>
<instantiate from="root/src/app_package/MvpPresenter.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(presenterPackageName)}/${pageName}Presenter.java" />
<open file="${projectOut}/src/main/java/${slashedPackageName(presenterPackageName)}/${pageName}Presenter.java" />
</#if>
<#if needModel>
<instantiate from="root/src/app_package/MvpModel.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(modelPackageName)}/${pageName}Model.java" />
</#if>
<#if needDagger>
<instantiate from="root/src/app_package/MvpComponent.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(componentPackageName)}/${pageName}Component.java" />
<instantiate from="root/src/app_package/MvpModule.java.ftl"
to="${projectOut}/src/main/java/${slashedPackageName(moudlePackageName)}/${pageName}Module.java" />
</#if>
</recipe>
可以看到,来源就是我们已经编写好的.java.ftl文件,去向是还未命名的.java文件,我改这个东西是复制一份包,直接进行改的,改好后放进去就行了,如果没改好,你是生不成代码的,好好去检查一下包下面的文件。
自定义类和模块模板的内容到这里就基本上结束了,希望对大家的开发有所帮助,最后说一个题外话,第一次转到简书来,熟悉markdown编辑器的语法花了一些时间,最后下载了一个有界面的markdown编辑器小书匠写的本篇文章。
模板包下载:百度网盘
密码:5lk7
网友评论