studio模板,一键生成多个类,mvp党福利

  人类进步的根源是什么?是懒惰,是的,没有错,就是懒惰,正是当你想偷懒时,你才会去寻找更便捷的方法搞定一件事。写代码也是同样的,不想偷懒的程序猿不是好程序猿,下面咱们来看看如何“偷懒”。php

  首先,声明一下,本文的做用纯属抛砖引玉,并不会太详细的介绍具体使用方法,仅仅介绍大概使用思路及踩坑日记。虽然本文以mvp为例,可是本文所讲的内容不局限于此,基本上全部的模板代码,你均可以生成模板,方便后面使用。java

  使用mvp模式开发安卓项目的人都知道,建立一个activity一般须要建立包含接口在内的5个类,写一两个界面还好,若是真是写完整个项目,光是建立这些类都让人心烦,那么有没有快捷的方法呢?固然是有的,最简单的方法就是使用studio 自带的file template。android


下面来写一个简单的Preseter类模板:

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
public class ${NAME}Presenter extends StyleActivityPresenter<I${NAME}View,I${NAME}Model>{
    @Override
    protected void initPresenter (Bundle savedInstanceState) {

    }
}复制代码

使用效果:android-studio

public class TestPresenter extends StyleActivityPresenter<ITestView, ITestModel> {
    @Override
    protected void initPresenter (Bundle savedInstanceState) {

    }
}复制代码

  能够看出,使用上仍是很是简单的,${NAME}就是你建立文件时输入的名字,其余的相信不用解释你们都看的懂。app

  上面的模板功能虽然已经可以很方便的让我建立一个类而不用去写过多的重复代码了,可是依然不够好用,由于上面说了,mvp模式一般包含5个类,还有布局文件,还有activity注册代码,这些能够说是每次建立activity的必须代码,而若是仅仅使用上面的file template功能,依然须要屡次在不一样包下建立文件,还有没有更偷懒的方法呢?固然有,就是studio强大的activity模板功能了。
  其实这个功能,你们常常都在使用,只是不少人并无注意罢了,就好比咱们新建项目时:maven

  这其实就是studio自带的activity模板,咱们知道当咱们选中某个类型的模板后,生成了项目以后,项目中就会有相应的java代码和布局,而且他会帮你在manifest注册好这个activity。ide

下面咱们须要的也就是自定义这个功能,让他实现输入一个类名后在你指定的包下面自动生成5个mvp相关类和布局文件已经manifest注册。布局

首先,咱们须要知道,系统自带的模板位置:XXX\android-studio\plugins\android\lib\templates\activities
这个目录下就是上面咱们看到的全部activity模板的文件目录,先来简单介绍一下模板的目录下几个重要的文件及其做用,咱们以LoginActivity这个模板为例:测试

root:这个目录下面放的是咱们咱们的代码模板,和file template代码相似,可是有必定区别。我喜欢叫他们模板输出原型。ui

globals.xml:这个文件是用来配置某些特殊属性的,好比是不是启动页面之类的属性。

recipe.xml:这个文件主要是配置须要生成哪些文件,用哪一个模板生成,生成后要输出到哪一个目录。

template.xml这个文件主要是用来定义咱们的一些文件名和包名之类的变量属性,看看LoginActivity的配置界面效果,相信你们就懂这个文件的做用了:

template_login_activity.png:这个是上面图中那个界面示意图,一般不须要管它,固然你也能够放一张本身的图,替换掉。

下面说说怎么自定义本身的模板,(文章开始已经说了,本文并不会详细介绍如何进行自定义模板<我能说是本身也是才学这个东西吗?>,这里直接介绍我本身自定义时遇到的坑,和一些比较重要的注意事项):

  • 首先,建议你们从最简单的模板开始尝试,不要一开始就彻底以本身的mvp类去写,等熟悉了相关属性和规则后再去写mvp相关的模板,这是由于模板这个东西不是咱们的项目代码,若是你配置错了,使用时虽然会有报错提示,可是并不许确,因此若是你一次性写太多东西的话,排查错误时很慢。
  • 其次,建议直接先复制一份系统的模板代码好比(LoginActivity模板),而后在此基础上修改,不要本身去建立每一个文件,理由和第一条相似,容易出错。

而后开始咱们的模板建立之旅:

1.建立Demo项目用于测试

很是简单,只包含了一个默认的自动生成的MainActivity类

2.复制LoginActivity模板

在咱们的对应目录XXX\android-studio\plugins\android\lib\templates\activities中复制LoginActivity文件夹并重命名为MVPTestActivity,接着进入咱们复制的文件夹,打开template.xml这个文件,改掉name的值,最好是和你的目录名保持一致,咱们这里就叫MVPTestActivity,其余几个属性能够按照本身的须要进行修改。

<template format="5" revision="6" name="Login Activity"-->此属性更名为MVPTestActivity
    description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
    minApi="8"
    minBuildApi="14">复制代码

parameter,这个标签是咱们配置界面上的字段,我这里只截图两个重要字段:

<parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" default="LoginActivity" help="The name of the activity class to create" />

    <parameter id="layoutName" name="Layout Name" type="string" constraints="layout|unique|nonempty" suggest="${activityToLayout(activityClass)}" default="activity_login" help="The name of the layout to create for the activity" />复制代码

两张图一块儿看的话就很好理解了,id为activityClass的这个标签就是咱们的类名,id为layoutName就是咱们的布局名,id这个字段是咱们在其余配置文件中引用name的查找依据,type天然就是类型,constraints是一些输入限制信息,default固然就是默认实如今配置界面上的值了,每一个属性的具体用法,你们本身搜索一下相关博文,这里不是本文的重点。

咱们这里先开始写最简单的,那么咱们确定是不须要Title之类的属性的,因此咱们先把不须要显示或配置的字段注释掉。

<!-- <parameter id="activityTitle" name="Title" type="string" constraints="nonempty" default="Sign in" help="The name of the activity." /> -->
    ...复制代码

其余的一些字段咱们也能够根据咱们的须要稍做调整,这样这个文件基本就修改完成了。

recipe.xml,打开此文件,咱们能够看到

<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
   <#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
       <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+" />
    </#if>

    <#if (buildApi gte 22) && appCompat && !(hasDependency('com.android.support:design'))>
        <dependency mavenUrl="com.android.support:design:${buildApi}.+" />
    </#if>

    <#include "../common/recipe_theme.xml.ftl" />

    <merge from="root/AndroidManifest.xml.ftl" to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />

    <merge from="root/res/values/dimens.xml" to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />

    <merge from="root/res/values/strings.xml.ftl" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

    <instantiate from="root/res/layout/activity_login.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

<#if generateKotlin>
    <@kt.addAllKotlinDependencies />
    <instantiate from="root/src/app_package/LoginActivity.kt.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<#else>
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>

</recipe>复制代码

是否是看的一头雾水,其实不难,ifelse相信不用过多解释,咱们重点讲讲其余几个比较重要的标签的做用。

  • merge 顾名思义就是合并的意思,这个主要是用在资源文件或者manifest文件,由于咱们一般是须要把咱们新建的xml文件和项目中的进行合并,这里稍微讲解一下manifest的合并,由于其余的资源文件合并都很简单,就不说了。

打开MVPTestActivity\root目录下的manifest文件,主要代码以下:

<activity android:name=".${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            <#else>
            android:label="@string/title_${simpleName}"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            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>
            <@manifestMacros.commonActivityBody />
        </activity>复制代码

基本上不是很难理解,相似于这种${}代码,都是对其余文件中属性的引用,下面咱们去掉咱们不须要的属性,修改后:

<activity android:name=".${activityClass}"
            <#if isNewProject>
            android:label="@string/app_name"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            android:theme="@style/${themeName}"
            </#if>>

            <@manifestMacros.commonActivityBody />
        </activity>复制代码
  • instantiate 就是建立文件
  • open file固然就是在咱们生成好类后,打开相关类

这里重点说说from和to这两个属性,看名字你们应该都能猜出一点了,from就是指的咱们的模板文件路径,to固然就是咱们生成文件的路径,以简单的布局文件为例,能够看到一个instantiate标签内容以下:

<instantiate from="root/res/layout/activity_login.xml.ftl" to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />复制代码

其中root/res/layout/activity_login.xml.ftl这个路径,明显就是咱们当前模板目录下面的文件,而to的属性,咱们也能够理解下,${escapeXmlAttribute(resOut)}多半就是指的当前项目的res资源主目录,而后${layoutName}明显就是引用的template.xml中配置的,用户输入的layoutName这个String。
明白了这些属性后,咱们作出以下修改:

  • 删掉MVPTestActivity\root\res目录下的values文件夹
  • 注释掉recipe.xml文件中两个关于资源合并的标签(不去掉会报空指针,由于咱们已经删掉了资源模板)
  • 最后修改MVPTestActivity\root\src\app_package目录下的LoginActivity.java文件(其实还须要同步修改LoginActivity.kt,不过由于我暂时没有使用kotlin,因此没作它的适配),这个文件内容太多,为了方便查看,咱们简化成以下代码:
package ${packageName};

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>
/**
 * A login screen that offers login via email/password.
 */
public class ${activityClass} extends AppCompatActivity {

    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.${layoutName});
    }

}复制代码

好了,到此为止,其实咱们已经完成了一些最简单的修改,下面咱们来看看运行效果,保存每一个文件后,重启studio(一开始写的时候千万不要想一次改完,否则多半会由于失败而放弃这个):

能够看到咱们修改了模板介绍,修改了默认的activity名字,去掉了title和parent两个输入框,让咱们点击完成试一下效果:

TestActivity和activity_test都是咱们经过模板自动生成的文件,而且mainfest中已经注册了这个activity了,具体截图就不放了,你们能够本身尝试。

上面的步骤已经算是完成了模板的最基本使用了,可是离咱们的要求还差点,由于咱们想要的是生成多个文件,而且多个文件有可能再也不同一个包下,那么咱们怎么实现呢?

首先,假如咱们的项目结构以下:

那么我如今须要的就是:

  • 在presenter包下建立一个TestActivityPresenter类
  • 在model包下建立一个TestActivityModel
  • 在view包下建立一个TestActivityView
  • 在port包下建立一个ITestActivityModel接口和ITestActivityView接口
  • 而且TestActivityView应该实现ITestActivityView接口,TestActivityModel应该实现ITestActivityModel接口
  • 生成对应布局文件,而且注册activity

最后一条,基本上不用再说了,咱们开始的改动已经知足了,重点说说下面几条怎么实现,主要是说说怎么在不一样目录下生成对应文件。

回到咱们的recipe.xml文件中,咱们注意看这里

<instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />复制代码

这里其实就是用来生成咱们刚刚那个TestActivity的配置代码,明显的咱们想多生成几个文件的话,只须要多写几个这种标签就好,好比像下面这样:

<!--IView -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />                                    
    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />复制代码

注意看to的值的最后一点,我采用拼接的方式规定了每一个文件的名字格式,这样全部mvp下的类名都是符合必定命名规范的,固然不必定和个人同样,可是你必定要有本身的格式,不要随意取名字,这是基础,不过多解释缘由。
这样咱们生成的时候就会获得5个文件,可是还不够,由于他们如今都在同一个目录下,怎么让他们在本身的目录下生成呢,再来改改:

<!--IView -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/view/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/model/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/LoginActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />                                    
    <open file="${escapeXmlAttribute(srcOut)}/presenter/${activityClass}.java" />
</#if>复制代码

是否是很简单?只须要在${escapeXmlAttribute(srcOut)}后面跟上具体的包路径就行了,那么咱们还缺点什么?咱们还缺模板源文件,由于上面from的文件都是LoginActivity这个文件,也就是说生成的每一个类的代码都是相同的。

下面让咱们来完成最后一步,编写模板代码了,这个就相对简单了,毕竟基本上都是java代码,难不倒你们的,咱们在MVPTestActivity\root\src\app_package路径下把LoginActivity.java.ftl这个文件复制四份,kt那个文件无论,那个是适配kotlin的,若是你是用的Kotlin的话,也能够复制那个。
而后依次更名,效果以下:

每一个类的代码以下:
MvpView.java

package ${packageName}.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
<#if applicationPackage??>
import ${applicationPackage}.R;
</#if>
import com.xujl.demo.port.I${activityClass}View;
import com.xujl.demo.presenter.${activityClass}Presenter;

class ${activityClass}View extends AppCompatActivity implements I${activityClass}View{
    private ${activityClass}Presenter mPresenter;

    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.${layoutName});
        mPresenter = new ${activityClass}Presenter(this);
    }

}复制代码

MvpPresenter.java

package ${packageName}.presenter;

import com.xujl.demo.model.${activityClass}Model;
import com.xujl.demo.port.I${activityClass}Model;
import com.xujl.demo.port.I${activityClass}View;

public class ${activityClass}Presenter  {
    private I${activityClass}View mView;
    private I${activityClass}Model mModel;

    public ${activityClass}Presenter(I${activityClass}View view){
        mView = view;
        mModel = new ${activityClass}Model();
    }


}复制代码

MvpModel.java

package ${packageName}.model;

import com.xujl.demo.port.I${activityClass}Model;

public class ${activityClass}Model implements I${activityClass}Model {



}复制代码

IMvpModel.java

package ${packageName}.port;

public interface I${activityClass}Model{

}复制代码

IMvpView.java

package ${packageName}.port;


public interface I${activityClass}View{

}复制代码

最后不要忘记修改recipe.xml中from的模板文件名和模板mainfest中的activity名字

<!--IView -->
    <instantiate from="root/src/app_package/IMvpView.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}View.java" />
  <!--View -->
    <instantiate from="root/src/app_package/MvpView.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/view/${activityClass}.java" />
  <!--IModel -->
    <instantiate from="root/src/app_package/IMvpModel.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/port/I${activityClass}Model.java" />   
  <!--Model -->  
    <instantiate from="root/src/app_package/MvpModel.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/model/${activityClass}Model.java" />    
  <!--Presenter -->
    <instantiate from="root/src/app_package/MvpPresenter.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}Presenter.java" />   
----------------------------------------------------
 <activity android:name=".view.${activityClass}View"
            <#if isNewProject>
            android:label="@string/app_name"
            </#if>
            <#if hasNoActionBar>
            android:theme="@style/${themeNameNoActionBar}"
            <#elseif !(hasApplicationTheme!false)>
            android:theme="@style/${themeName}"
            </#if>>

            <@manifestMacros.commonActivityBody />
        </activity>复制代码

最后来看看运行效果(右键点击的时候必定要在主包上面点击,否则生成路径可能会出错):


若是你不是在主包上点击的,也不要紧,记得修改这里为主包路径就好:


至此咱们就完成了一个完整的模板了,固然这里不管是包结构仍是命名方式,仍是mvp结构,都是我本身定义的,你们彻底能够根据本身的项目实际状况的来写,我这样定义的结构,好处是只须要键入类名,其余均可以生成了,固然你也能够多设置几个包名字段,用来动态配置model,view,presenter,和接口的包路径。

最后附上整个MVPTestActivity的模板文件连接: pan.baidu.com/s/1skW4nTV 密码: kbve