Android模板开发-提高编写代码效率

在编写android项目中,我们难免会重复的去写一些东西或者写一些浪费时间但又和我们项目逻辑关系不大的代码,所以学会编写模板代码变得很重要,我这里指得模板有两种:

  1. 单个文件

  2. 某个功能

    对于第一种情况,如下:

sigle.png

singleton.png

在上面我们仿照系统文件新建了两个模板类,RecycleViewAdapterSingleton,在我们新建类列表中可以看到。我们得代码是仿照着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

推荐文章

mvp架构之路,从简单到复杂

Markdown编辑器一览,总有一款适合你

一键生成 MVP , Dagger2 相关类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Android Studio自定义模板 写页面竟然可以如此轻松 1、概述 上一篇文章,已经初步对Android S...
    Art_Collector阅读 1,419评论 0 5
  • 由于项目用上了 mvp 架构,基本上一个页面就至少需要新创建6个类,分别是 model view presente...
    大空ts翼阅读 1,933评论 0 4
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,042评论 25 707
  • 一 昨天看到职业规划的问题了,一万个小时。 一个人如果在某件事上全心全意投入一万个小时,那么他就会成为这个领域的专...
    慕谖阅读 215评论 0 0
  • 某女在八达岭动物园中与老公发生口角,愤然下车的结局让人唏嘘不已。可怜天下父母心,无论女儿如何无理,母亲都会以身犯...
    酷雪冰凌阅读 556评论 7 5