最全详解!Android Studio activity template(模板)教程
概述
Android Studio的activity又叫Android ADT Template,就是当你创建一个新的activity时在activity gallery看到的那些已经建构好的模板:
你有没有想过自己写一个Activity模板,然后以后要创建相同类型的Activity时少写大量重复的代码呢?这篇文章就是为你打开新世界的大门。
当看到网上只介绍一个大概然后丢个链接让你自己去摸索的教程就想自己写一个完整的教程。这个系列教程将通过DIY一个模板的过程,穿插各种函数、数据类型等语法来讲解。
开始
emmmm,制作模板既没有IDE也没有代码提示,因此我们最好找到一个现成的样本来改。Android Studio的Activity模板的地址在 {Android Studio}安装地址\plugins\android\lib\templates\activities下,如图
我们可以看到AS内置的模板都在这里了,我们先copy其中的一份出来,我选择了BasicActivity毕竟这个里面的内容比较全。然后我们就可以开始啦。
先看看文件结构:
- globals.xml.ftl //全局变量(可选)
- recipe.xml.ftl //脚本(执行文件复制等操作)
- recipe_fragment.xml.ftl
- template.xml //描述模板的参数,变量,介绍等
- template_basic_activity.png //缩略图,即在activity gallery看到的那些
- template_basic_activity_fragment.png
- root //资源根目录, 以下文件结构同项目的结构
- res
- layout
- activity_fragment_container.xml.ftl
- fragment_simple.xml.ftl
- layout
- src
- app_package
- SimpleActivity.java.ftl
- SimpleActivity.kt.ftl
- SimpleActivityFragment.java.ftl
- SimpleActivityFragment.kt.ftl
- app_package
- res
制作一个RecyclerView Template
讲理论和读文档还不如直接动手做,所以这里我用RecyclerView的例子来实现整个流程顺带穿插各种详细的API。在制作之前我们需要复制一个现成的模板出来(如Drawer Activity)。然后把文件夹名改成RecyclerView Activity。然后创建一个Android Studio项目,把我们需要用模板生成的文件先写出来。要做一个RecyclerView需要:
+ 一个或两个Activity布局
+ 列表item的布局
+ Activity,
+ Adapter适配器
不会RecyclerView的话可以看这篇文章->
RecyclerView教程
我是用Kotlin写的示范(看不懂没关系,知道大概要做什么就可以了):
放一张Activity截图:
再放一张Adapter的截图:
Layout的话就*发挥吧,你经常写什么样的列表就写什么Layout。
等等。。。楼主你站住,错误那么多是怎么回事,给RecyclerView三个layoutManager又是什么鬼??!还有这变量命名你。。。怕不是个假的Android程序员??
咳咳。。。那、那个请不要在意那么多细节,毕竟RecyclerView的玩法太多了我们没法兼顾所有情况只能写一些通用的代码,再根据实际来进行修改。这只是个简单的示例,你也可以加上RecyclerView的点击回调代码等。
然后把这些文件复制到模板文件夹里相应的位置,比如ListActivity就放在root\src\app_package下。然后给这些文件都加上后缀名”.ftl”。(注意是加上后缀名而不是改后缀名)。
改完后的三个Layout文件:
由于没什么需要改的(除了layout里有context属性如下图需要修改), 因此模板预置的文件没什么参考意义可以直接删了。
回到res目录下,把用不到的文件夹删了,如果你有字符,颜色等资源的话把colors.xml或strings.xml之类的文件复制到相应文件夹下,加上后缀名.ftl就可以了。后面我们会用代码合并这些资源。
由于涉及到一些参数,所以先不急着修改类的内容,先看到主页三个文件:
globals,recipe,template。
Globals
globals好像用不到,直接把变量都删了吧~【喂楼主你这样太不负责了】好吧我还是说一下这个文件。除了template.xml,其他需要被Android Studio处理(其实底层是FreeMaker工作这个后面再说)的文件都需要加上后缀名.ftl( 全名FreeMarker Template Language),因此globals和recipe也不例外(因此图片和一些不需要变量替换和合并处理的资源文件就可以不用加啦)。
FTL语言由以下四种东西组成:
- Text : 直接输出的文本
- Interpolation(插值):以“${ var }”格式将变量插入字符串
- FTL tags (标签/指令):类似html的标签
- Comments (注释):<#– 这是一条注释 –>
在globals里的<#if><#else>就是指令:
<#if 表达式><#else>这个就不用说了吧,表达式成立执行<#if>标签下的指令。
而global就是用一个个键值对存放全局变量,这些变量一般是不作为在创建Activity时可以修改的参数的(所以应该叫全局常量?)。它们主要以${ 常量 }形式出现在其他ftl文件中。
全局变量type的类型有string,boolean,integer。使用String类型时type可省略。
在globals.xml.ftl中,你也可以使用<#include “../common/common_globals.xml.ftl” />指令来导入其他变量。<#assign themeName=theme.name!’AppTheme’>命令用于赋值。
Template.xml
首先看到根元素:
<template
format="5"
revision="1"
name="RecyclerView Activity"
minApi="9"
minBuildApi="14"
description="Creates a new Activity with a RecyclerView.">
其中:
format: 固定值,无视
revision: 版本,数值
name: Template的名字
minApi:对应minSdkVersion
minBuildApi:对应Api Level
description: 介绍
template下有这么几种节点:
- category 模板类型
- dependency 依赖的库
- formfactor 不修改
- parameter 参数
- thumbs 预览图
- globals 不修改
- execute 不修改
我还是先将这些节点梳理一遍吧:
category
有以下三种:
- Applications
- Activities
- UI Components
dependency
形式如下:
<dependency name="android-support-v4" revision="8" />
name为依赖库的名称
revision为库的最低版次
thumb
预览图
当需要根据变量使用不同的预览图时,使用以下方式:
<thumb features="tabs">template_blank_activity_tabs.png</thumb>
<thumb features="pager">template_blank_activity_pager.png</thumb>
<thumb features="spinner">template_blank_activity_dropdown.png</thumb>
<thumb useFragment="true">template_basic_activity_fragment.png</thumb>
parameter(变量)
这些变量就是在创建Activity时可修改的参数(当然也可以根据项目类型隐藏部分变量)
该节点有以下参数:
- id: 变量名, 变量的唯一标识
- name: 在创建Activity时展示的变量名
- type: 类型,有string, boolean, enum, or separator(这个我也不知道是什么,反正我试了一下AS报错了)
- constraints:变量约束,含有以下值
- nonempty
- apilevel
- package
- class
- activity
- layout
- drawable
- string
- id 是资源不是变量的ID
- unique 唯一约束,仅当声明了其它约束如layout时有效
- exists 变量必须存在的约束,仅当声明了其它约束如layout时有效
- suggest 推荐值,未修改变量时根据其他变量生成
- default 默认值
- help 当编辑一个变量时,显示在下方的提示
- visibility 是否可见
说完了这些,我们再来试一下编辑RecyclerView的变量。首先我们需要的变量有
- Bean类的名称
- itemLayout的文件名
- adapter名称
- 列表的布局(Linear, Grid, Staggered)
- 列表的列数
写出来就是下面这些
<parameter
id="beanName"
name="Bean Name"
type="string"
constraints="nonempty"
default="Bean"
help="The bean is the unit of message for creating a list" />
<parameter
id="itemLayoutName"
name="Item Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="item_${beanName}"
default="list_item"
help="The name of the item layout to create for the RecyclerView" />
<parameter
id="listType"
name="List Type"
type="enum"
default="linear"
help="The type of the list" >
<option id="linear">Linear Layout</option>
<option id="grid">Grid Layout</option>
<option id="stagger">Staggered Layout</option>
</parameter>
<parameter
id="listColumn"
name="Column"
type="enum"
default="c2"
help="the column of the grid or staggered list">
<option id="c1">1</option>
<option id="c2">2</option>
<option id="c3">3</option>
</parameter>
不支持integer这有点坑。。。因此column我用enum代替了
至于activity和主Layout的名称保留原有的就可以了,其他一些没用的变量可以删掉。
你可能发现有一些变量的suggest里用到了一些函数,比如在layoutName里有
suggest="${activityToLayout(activityClass)}"
这里的activityToLayout(activityClass)的作用是Activity的名字转为规范的layout名,比如参数CatActivity就会返回activity_cat。
除了activityToLayout,还有其他一些(看起来)实用的函数:
- string camelCaseToUnderscore(string)驼峰命名风格转下划线
- string escapeXmlAttribute(string)将“< ?”一类的特殊符号转码为xml可用的属性命名,比如Android’s 会变成Android's
- string escapeXmlText(string)将字符转换为xml可用的字符命名
- string escapeXmlString(string)同上但Android建议用这个,它会同时支持转换Android的一些特殊字符
- string extractLetters(string)去标点和空格
- classToResource(string)不知道怎么解释举个栗子吧输入CatActivity会返回Cat
- string layoutToActivity(string)嗯activityToLayout的反向操作
- string slashedPackageName(string)包名转路径比如:com.example.foo转换为com/example/foo.
- string underscoreToCamelCase(string)下划线转驼峰风格
然后我们就可以在class里使用这些变量啦:
首先是列表类型:
<#if listType == ‘linear’>
recyclerView.layoutManager = LinearLayoutManager(this)
<#elseif listType == ‘grid’>
recyclerView.layoutManager = GridLayoutManager(this,${listColumn})
<#else if listType == ‘staggered’>
recyclerView.layoutManager = StaggeredGridLayoutManager(${listColumn}, StaggeredGridLayoutManager.VERTICAL)
</#if>
这里有三个分支,用if elseif 写太丑陋了,不过强大的ftl语言还支持switch case:
<#switch listType>
<#case ‘linear’>
recyclerView.layoutManager = LinearLayoutManager(this)
<#break>
<#case ‘grid’>
recyclerView.layoutManager = GridLayoutManager(this,${listColumn})
<#break>
<#case ‘staggered’>
recyclerView.layoutManager = StaggeredGridLayoutManager(${listColumn}, StaggeredGridLayoutManager.VERTICAL)
<#break>
</#switch>
<#default>语句也是可以用的
类名,包名,布局文件名也别忘了改,我最后的Activity的代码如下:
package ${packageName};
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.StaggeredGridLayoutManager
import android.view.MenuItem
import kotlinx.android.synthetic.main.${layoutName}.*
import kotlinx.android.synthetic.main.${contentLayoutName}.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
class ${activityClass} : ${superClass}() {
private lateinit var adapter: ${adapterName}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.${layoutName})
initUI()
}
private fun initUI(){
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
<#switch listType>
<#case linear>
recyclerView.layoutManager = LinearLayoutManager(this)
<#break>
<#case grid>
recyclerView.layoutManager = GridLayoutManager(this,${listColumn})
<#break>
<#case staggered>
recyclerView.layoutManager = StaggeredGridLayoutManager(${listColumn}, StaggeredGridLayoutManager.VERTICAL)
<#break>
</#switch>
doAsync {
adapter = ${adapterName}(aaa@qq.com${activityClass},)
uiThread {
recyclerView.adapter = adapter
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
}
adapter也是类似的改法,这里就不贴代码了
接下来我们来写recipe.xml.ftl了
recipe中主要使用以下五种指令:
- copy: 将from属性指定文件或一整个文件夹复制到to指定的位置。如果to属性不写,会采用from属性相同的路径并把文件名的.ftl后缀去掉(如果是ftl后缀的话)。
- instantiate:实例化,类似copy,但在复制前会对文件进行预处理,也就是计算文件里的插值和执行里面的指令。
- merge: 合并文件,一般用于string.xml, color.xml等资源文件和 AndroidManifest.xml
- open:完成所有复制工作后在Android Studio打开这些文件
- dependency:依赖库,会自动下载mavenUrl所指定的library。
由于该项目依赖anko库,所以需要在项目没有该库时自动导入,dependency一般配合hasDependency函数使用:
<#if !(hasDependency('com.android.support:appcompat-v7'))>
<dependency mavenUrl="org.jetbrains.anko:anko-common:0.10.4.+"/>
</#if>
接下来就是对所有文件一个个instantiate或copy了,最后用open语句打开需要进一步修改的文件,完整代码如下:
<?xml version="1.0"?>
<#import "root://activities/common/kotlin_macros.ftl" as kt>
<recipe>
<@kt.addAllKotlinDependencies />
<#include "../common/recipe_manifest.xml.ftl" />
<#if !(hasDependency('com.android.support:appcompat-v7'))>
<dependency mavenUrl="org.jetbrains.anko:anko-common:0.10.4.+"/>
</#if>
<instantiate from="root/res/layout/activity_list.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="root/res/layout/content_list.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${contentLayoutName}.xml" />
<instantiate from="root/res/layout/list_item.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${itemLayoutName}.xml" />
<instantiate from="root/src/app_package/ListActivity.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<instantiate from="root/src/app_package/MyListAdapter.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${adapterName}.kt" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.kt" />
<open file="${escapeXmlAttribute(srcOut)}/${adapterName}.kt" />
<open file="${escapeXmlAttribute(resOut)}/layout/${contentLayoutName}.xml" />
<open file="${escapeXmlAttribute(resOut)}/layout/${itemLayoutName}.xml" />
</recipe>
到这一步我们已经基本完成了创建一个新模板的工作,为了完美我还特意画了一个缩略图用于创建时的预览(AI苦手一枚不要吐嘈了):
凭着我单身多年的画技。。。至少还能看的过去吧。。
记得更换缩略图后在template.xml在thumb标签同步图片名称
然后把整个修改好的文件夹放到\plugins\android\lib\templates\activities下,重启AS(必要,因为这些模板是在android studio启动时加载的【怪不得启动那么慢。。】)。然后测试,不行的话继续改。。。
测试截图:
预览图有点秀我有空把图片的尺寸修改一下
看起来还可以
如果as启动报错的话一定要查看log,因为template里错误的地方都在那里了
关于FreeMaker
activity 模板是用freeMaker工作的,ftl具有非常强大的特性,如果想深入研究的话可以去看官方文档:FreeMaker文档