第二十四章 Android 自定义模板

作者: 忆念成风 | 来源:发表于2017-10-17 17:49 被阅读130次

    1. 概述

    在读鸿洋大神的博客的时候,看到了自定义模板,然后网上查了一些资料,自己了解学习一下,希望和大家分享。我们在创建项目的时候,看到这样一个界面,这里的初始Activity就是模板中的Activity。


    自定义模板

      其实模板不只是只有activity 还包括图片自由 布局文件 fragment service 以及一个类都可以制作成模板。慢慢了解,这里只介绍Activity。Android Studio 自定义模板位于 \plugins\android\lib\templates\activities目录下。我们创建Activity的时候,也可以将自己定义的模板放在哪个目录下,然后创建Activity的时候,就可以出现创建的Activity。

    自定义模板

    2. 自定义模板结构

    学习自定义模板,我们需要参考IDE中的文件结构。Android Studio 中提供的最简单的Activity就是:Empty Activity 。


    自定义模板结构

    从图中可以看到每个文件夹的对应的插件:

    • root文件夹 存放对应源码的ftl文件,以及资源文件
    • globals.xml.ftl
    • recipe.xml.ftl
    • template.xml
    • template_blank_activity.png 效果缩略图

    2.1 template.xml 中parameter标签,主要用于提供参数

    打开文件

    <?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
    1. 最外层的template的属性有模板名,描述,api版本,格式大小
    <template
        format="5"
        revision="5"
        name="Empty Activity"
        minApi="9"
        minBuildApi="14"
        description="Creates a new empty activity">
    
    1. parameter标签, 看到这个界面,大部分属性都应该能才出来了,我们重点看parameter,界面上每一个红色框出来的部分都对应一个parameter,部分属性介绍:
     <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" />
    
    • id :唯一标识,最终通过该属性的值,获取用户输入值(文本框内容,是否选中)
    • name:界面上的类似label的提示语
    • type : 输入值类型
    • constraints:填写值的约束
    • suggest:建议值,比如填写ActivityName的时候,会给出一个布局文件的建议值。
    • default:默认值
    • help:底部显示的提升语
    1. thumbs
      这个是显示预览效果图,可以更换

    2. 最后指定两个引用文件

    <globals file="globals.xml.ftl" />
     <execute file="recipe.xml.ftl" />
    

    2.2 globals.xml.ftl 主要用于提供参数

    <?xml version="1.0"?>
    <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,默认值。
    同理,我们可以通过id的值访问到该值,例如:
    ${hasNoActionBar}的值为false。

    2.3 recipe.xml.ftl 主要用于生成我们实际需要的代码,资源文件等。例如,利用参数+MainActivity.java.ftl -> MainActivity.java;其实就是利用参数将ftl中的变量进行替换。

    <?xml version="1.0"?> 
    <recipe> 
    //include语法,跟C中的include是一个意思,就是引用这个文件
    
     <#include "../common/recipe_manifest.xml.ftl" /> 
    
    //if语法,这里代表了假如id为generateLayout的值为true,则往if里面走 
    
    <#if generateLayout> 
     
        <#include "../common/recipe_simple.xml.ftl" /> 
    
    //open语法,这里指打开${escapeXmlAttribute(resOut)}/layout/目录下的${layoutName}.xml文件,其中${escapeXmlAttribute(resOut)}/输出的目录就是项目中的res目录 
    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" /> 
    
    </#if> 
    //instantiae语法,这里是将root/src/app_package/目录下的SimpleActivity.java.ftl解析成项目中${escapeXmlAttribute(srcOut)}/${activityClass}.java,其中${escapeXmlAttribute(srcOut)}/输出的目录就是项目中的src目录 
    <instantiate from="root/src/app_package/SimpleActivity.java.ftl" 
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
    
     <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" /> 
    
     </recipe>
    
    

    为了介绍,我将该xml中比较重要的几个标签都列出来了:

    • copy :从root中copy文件到我们的目标目录,比如我们的模板Activity需要使用一些图标,那么可能就需要使用copy标签将这些图标拷贝到我们的项目对应文件夹。
    • merge : 合并的意思,比如将我们使用到的strings.xml合并到我们的项目的stirngs.xml中
    • instantiate : 和copy类似,但是可以看到上例试将ftl->java文件的,也就是说中间会通过一个步骤,将ftl中的变量都换成对应的值,那么完整的流程是ftl->freemarker process -> java。
    • open:在代码生成后,打开指定的文件,比如我们新建一个Activity后,默认就会将该Activity打开。

    图片来源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

    2.4 Root文件夹

    里面都是加了ftl的java文件和XML文件,所以我们用if来判断生成的方式。
    两个步骤:
    取值--->判断-->生成

    xml文件中:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android" >
        <application>
            <activity android:name="${relativePackage}.${activityClass}"
                <#if isNewProject>
                android:label="@string/app_name"
                <#else>
                android:label="@string/title_${activityToLayout(activityClass)}"
                </#if>
                >
                <#if isLauncher>
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
                </#if>
            </activity>
        </application>
    
    </manifest>
    
    
    1. 获取值,包括设定值和全局变量:

      设定值:activityClass获取,${activityClass}
      全局变量:isNewProject,hasNoActionBar

    2. if判断
      设定值:activityClass获取,${activityClass}
      全局变量:isNewProject,hasNoActionBar

    3. FreeMark语法

    上面我们已经基本了解模板生成的大致的流程以及涉及到的文件,大致了解了我们生成的源码或者xml文件,需要经过:

    • ftl->freemarker process->java/xml

    比如我们有个变量user=zhy;
    有个ftl文件内容:helloL${user}
    最后经过freemarker的输出结果即为 hello:zhy

    • if语法
    <#if generateLayout>
        //生成layout文件
    </#if>
    

    以SimpleActivity.java.ftl

    • \root\src\app_package
      这内部包含很多变量,这些变量的值一般来源于用户输入和global.xml.ftl中预定义的值,经过recipe.xml.ftl中instantiate标签的处理,将变量换成实际的值,即可在我们的项目的指定位置,得到我们期望的Activity。
    package ${packageName};
    
    import ${superClassFqcn};
    import android.os.Bundle;
    <#if includeCppSupport!false>
    import android.widget.TextView;
    </#if>
    <#if applicationPackage?? && generateLayout>
    import ${applicationPackage}.R;
    </#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">
    }
    
    

    创建之后的java文件

    package com.demo.mystyletemplate;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
        }
    }
    
    

    流程大致可用如下图所示:


    图片来源:http://www.slideshare.net/murphonic/custom-android-code-templates-15537501

    4. 创建自己的模板

    在创建自己模板的时候,我们最好从原来的Templates的activities中拷贝一个出来,然后在这个基础上更改。

    实现效果
    4.1 globals.xml,这个文件可以直接拷贝过来,这里一个id simpleLayoutName,是Activity的layoutName
    <?xml version="1.0"?>
    <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>
    
    4.2 template.xml的编写

    每个parameter对应界面上的一个控件,控件的这个id最终可以得到用户输入值,后面会用于渲染ftl文件

    <?xml version="1.0"?>
    <template
        format="5"
        revision="7"
        name="Tab With ViewPager Activity"
        minApi="7"
        minBuildApi="14"
        description="Creates a new activity with viewpager and tabs">
    
        <category value="Activity" />
        <formfactor value="Mobile" />
    
        <parameter
            id="activityClass"
            name="Activity Name"
            type="string"
            constraints="class|unique|nonempty"
            suggest="${layoutToActivity(activityLayoutName)}"
            default="MainActivity"
            help="The name of the activity class to create" />
    
        <parameter
            id="activityLayoutName"
            name="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="tabCount"
            name="Tab Count"
            type="string"
            constraints="nonempty"
            default="4"
            help="The count of tabs for ViewPager" />
    
        <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" />
    
        <!-- 128x128 thumbnails relative to template.xml -->
        <thumbs>
            <!-- default thumbnail is required -->
            <thumb>template_tab_with_vp_activity.png</thumb>
        </thumbs>
    
        <globals file="globals.xml.ftl" />
        <execute file="recipe.xml.ftl" />
    
    </template>
    
    
    4.3 recipe.xml 中定义的东西比较关键,例如将ftl->java,copy、合并资源文件等。

    引入依赖

    <?xml version="1.0"?>
    <recipe>
        <#include "../common/recipe_manifest.xml.ftl" />
    
        <#if !appCompat && !(hasDependency('com.android.support:support-v4'))>
                <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
         </#if>
    
        <#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>
    
    //省略其他
    </recipe>
    

    这里的这部分直接拷贝过来,我们导入support-v4,-v7和design包

    <?xml version="1.0"?>
    <recipe>
     ···
    
        <instantiate from="root/src/app_package/MainActivity.java.ftl"
            to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
                  
          
        <instantiate from="root/res/layout/activity_main.xml.ftl"
            to="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
            
    
        <instantiate from="root/src/app_package/SimpleFragment.java.ftl"
            to="${escapeXmlAttribute(srcOut)}/fragment/SimpleFragment.java" /> 
    
        
            
        <instantiate from="root/src/app_package/Fragment1.java.ftl"
          to="${escapeXmlAttribute(srcOut)}/fragment/Fragment1.java" /> 
          
        <instantiate from="root/src/app_package/Fragment2.java.ftl"
          to="${escapeXmlAttribute(srcOut)}/fragment/Fragment2.java" />  
          
          
          <instantiate from="root/src/app_package/Fragment3.java.ftl"
          to="${escapeXmlAttribute(srcOut)}/fragment/Fragment3.java" />  
          
          <instantiate from="root/src/app_package/Fragment4.java.ftl"
          to="${escapeXmlAttribute(srcOut)}/fragment/Fragment4.java" />  
         
            
        <open file="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml"/>        
    
        <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
        
    
    </recipe>
    

    包含多个instantiate标签,该标签很明显是将我们内置的ftl转化为当前项目有中的java类。

    4.4 接下来是编写类
    • root/src/app_package/MainActivity.java.ftl
      注意不是.java文件而是.ftl文件,可以看到上面的代码基础上和Java代码没什么区别,实际上就是Java代码,把可变的部分都换成了${变量名}的方式而已。
    package ${packageName};
    
    import android.os.Bundle;
    import android.support.design.widget.TabLayout;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentPagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.support.v7.app.AppCompatActivity;
    
    import ${packageName}.fragment.SimpleFragment;
    
    public class ${activityClass} extends AppCompatActivity {
    
    
        private TabLayout mTabLayout;
        private ViewPager mViewPager;
        private int mTabCount = ${tabCount};
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.${activityLayoutName});
    
            mTabLayout = (TabLayout) findViewById(R.id.id_tablayout);
            mViewPager = (ViewPager) findViewById(R.id.id_viewpager);
    
            mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
                @Override
                public Fragment getItem(int position) {
                    switch (position){
                        case 0:
                             return  new Fragment1();
                        case 1:
                            return  new Fragment2();
                        case 2:
                            return  new Fragment2();
                        case 3:
                            return  new Fragment3();
                    }
                  return  null;
                }
    
                @Override
                public int getCount() {
                    return mTabCount;
                }
    
                @Override
                public CharSequence getPageTitle(int position) {
                    switch (position){
                        case 0:
                            return "教育";
    
                        case 1:
                            return "科技";
    
                        case 2:
                            return  "文化";
    
                        case 3:
                            return  "军事";
                    }
                    return null;
                }
            });
    
            mTabLayout.setupWithViewPager(mViewPager);
        }
    }
    
    
    

    例如:类名是用户填写的,我们就使用${activityClass}替代,其他同理。

    相应的编写四个fragment.

    package ${packageName}.fragment;
    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    
    public class Fragment1 extends Fragment {
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater,
                                 @Nullable ViewGroup container,
                                 @Nullable Bundle savedInstanceState) {
                                 
            return inflater.inflate(R.layout.${layoutName}, container, false);
        }
    
    }
    
    4.5 编写的布局文件
    • root/res/layout/activity_main.xml.ftl
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="${packageName}.${activityClass}">
    
        <android.support.design.widget.TabLayout
            android:id="@+id/id_tablayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </android.support.design.widget.TabLayout>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/id_viewpager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
        />
    </LinearLayout>
    
    

    推荐一些模板:
    https://github.com/kanytu/Android-studio-material-template
    https://github.com/gabrielemariotti/AndroidStudioTemplate
    https://github.com/intrications/material-design-icons-adt-template
    https://github.com/WanAndroid/AndroidStudioTemplates

    参考链接:
    http://www.jianshu.com/p/c76facb61d69
    http://blog.csdn.net/lmj623565791/article/details/51635533

    相关文章

      网友评论

        本文标题:第二十四章 Android 自定义模板

        本文链接:https://www.haomeiwen.com/subject/hyknextx.html