Android 从service调用dialog

需要权限

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

示例代码

1
2
3
Dialog dialog = new AlertDialog.Builder(ctx).create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();

注意事项

当activity不在前台的时候,部分手机(比如小米2s,系统4.1.1)不支持直接弹出dialog,之后在显示activity之后,才会看到dialog。

【Chrome】几个扩展程序

一,下载网易公开课

网易公开课虽然提供了下载链接,但是下载之后的文件名并不正确,需要下载后手动修改文件名,非常不方便。所以本扩展的目标就是在下载的时候将视频名称正确对应上去。

原理

拦截原网页上的下载链接,在获取正确的文件名称后直接调用chrome的download接口来下载视频文件。

使用步骤

  1. 下载并载入扩展,下载地址:https://github.com/xesam/chrome_plugins

二,查看Android离线文档

解决Android官网被墙后离线文档查看不是很方便的问题。

简介

因为虽然是离线文档,但是实际上在连接网络的情况下,离线页面还是会去google的网站上下载一部分资源,典型的就是会下载实现搜索功能的js。现在的问题是,google被和谐了,当打开离线文档的时候,页面会被加载中的资源阻塞,以至于浏览器在很长时间内都是一片空白,除非每次都手动点击浏览器的停止按钮,强制浏览器结束资源载入,才能看到文档的其他内容。

所以为了更方便的查看离线文档,可以采用几种方法:

1.查看的时候断开机器网络,这个不是很可取。
2.用脚本替换掉所有文档中不可加载的js和css等资源,网上貌似有现成的脚本可以达到这个目的。问题就是Android离线文档有600多M,每次更新都得进行处理,而且在可以访问google的环境下,搜索功能依旧不可用。
3.鉴于以上的问题,我写了个简单的chrome插件,可以在查看离线文档的时候选择是否屏蔽google的资源请求,在屏蔽google资源请求的情况下不会阻塞页面,从而避免修改实际页面的缺陷。

使用方法

  1. 载入chrome插件文件夹
  2. 点击插件图标可以切换是否屏蔽google资源请求。默认是关闭,开启的时候图标会有“ON”字样。
  3. 切换到“ON”模式,打开离线文档即可。

插件地址:https://github.com/xesam/android_offline_doc_plugin

Android OnCheckedChangeListener的bug

RadioGroup.OnCheckedChangeListener 被回调两次

今天又碰到了,貌似还没有被修复,顺便贴出来。

原android Issue地址:RadioGroup.OnCheckedChangeListener is called twice when the selection is cleared

具体表现

RadioGroup中包含有若干个RadioButton,当在代码中调用RadioGroup.check(id)方法动态设置被选中的RadioButton的时候,RadioGroup.OnCheckedChangeListener(RadioGroup group, int checkedId)会被调用多次。

示例

假设当前选择的是RadioButtonA,调用RadioGroup.check(RadioButtonBid)之后,RadioGroup.OnCheckedChangeListener(RadioGroup group, int checkedId)的调用情况如下:

第一次:checkedId 为 RadioButtonAId

第二次:checkedId 为 RadioButtonBId

第三次:checkedId 为 RadioButtonBId

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class MainActivity extends Activity implements CompoundButton.OnCheckedChangeListener,
RadioGroup.OnCheckedChangeListener{

RadioGroup mRadioGroup;
RadioButton radio_0;
RadioButton radio_1;
RadioButton radio_2;

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

mRadioGroup = (RadioGroup) findViewById(R.id.group);
radio_0 = (RadioButton) findViewById(R.id.radio_0);
radio_1 = (RadioButton) findViewById(R.id.radio_1);
radio_2 = (RadioButton) findViewById(R.id.radio_2);

mRadioGroup.setOnCheckedChangeListener(this);
radio_0.setOnCheckedChangeListener(this);
radio_1.setOnCheckedChangeListener(this);
radio_2.setOnCheckedChangeListener(this);
}

public void doTestClick(View view) {
switch (view.getId()) {
case R.id.check_radio_0:
mRadioGroup.check(R.id.radio_0);
break;
case R.id.check_radio_1:
mRadioGroup.check(R.id.radio_1);
break;
case R.id.check_radio_2:
mRadioGroup.check(R.id.radio_2);
break;

default:
break;
}
}

@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Log.i("mRadioGroup onCheckedChanged", "checkedId:" + checkedId);
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
switch (buttonView.getId()) {
case R.id.radio_0:
Log.i("radio_0 onCheckedChanged", "isChecked:" + isChecked);
break;
case R.id.radio_1:
Log.i("radio_1 onCheckedChanged", "isChecked:" + isChecked);
break;
case R.id.radio_2:
Log.i("radio_2 onCheckedChanged", "isChecked:" + isChecked);
break;

default:
break;
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<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=".MainActivity" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >

<Button
android:id="@+id/check_radio_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doTestClick"
android:text="check_radio_0" />

<Button
android:id="@+id/check_radio_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doTestClick"
android:text="check_radio_1" />

<Button
android:id="@+id/check_radio_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doTestClick"
android:text="check_radio_2" />
</LinearLayout>

<RadioGroup
android:id="@+id/group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<RadioButton
android:id="@+id/radio_0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0" />

<RadioButton
android:id="@+id/radio_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="1" />

<RadioButton
android:id="@+id/radio_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2" />
</RadioGroup>

</LinearLayout>

产生原因

RadioGroup check简化如下:

1
2
3
4
5
public void check(int newid) {
setCheckedStateForView(mCheckedId, false); //这里取消原来选择的RadioButton
setCheckedStateForView(newid, true);//设置当前选中的RadioButton为激活状态
setCheckedId(newid);//触发OnCheckedChangeListener
}

RadioGroup setCheckedStateForView简化如下:

1
2
3
4
private void setCheckedStateForView(int viewId, boolean checked) {
RadioButton childRadioButton = (RadioButton)findViewById(viewId);
childRadioButton.setChecked(checked);
}

RadioGroup setCheckedId简化如下:

1
2
3
4
private void setCheckedId(int id) {
mCheckedId = id;//保存当前选择的RadioButton
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}

同时RadioGroup内部有一个check状态跟踪器,简化一下如下:

1
2
3
4
5
6
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
public void onCheckedChanged(CompoundButton childRadioButton, boolean isChecked) {
int childRadioButtonId = childRadioButton.getId();
setCheckedId(id);
}
}

当每一个RadioButton被添加到RadioGroup的时候,就给每一个RadioButton设置一个checked状态监听

1
childRadioButton.mOnCheckedChangeWidgetListener = mCheckedStateTracker;

所以每当RadioButton的check状态发生变化,都会触发check状态跟踪器。

RadioButton的setChecked(boolean)简化如下:

1
2
3
4
public void setChecked(boolean checked) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked); //触发RadioButton各自的监听
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);//触发check状态跟踪器
}

说明

所以结合开头的例子,调用RadioGroup.check(RadioButtonBid)之后发生的情况如下:

1.RadioGroup.setCheckedStateForView(RadioButtonAid, false);
    --> RaidoButton.mOnCheckedChangeWidgetListener.onCheckedChanged(RadioButtonA, false) 
        --> RadioGroup.setCheckedId(RadioButtonAId) 
            --> RadioGroup.mOnCheckedChangeListener.onCheckedChanged(RadioGroup, RadioButtonAId);

2.RadioGroup.setCheckedStateForView(RadioButtonBid, true);
    --> RaidoButton.mOnCheckedChangeWidgetListener.onCheckedChanged(RadioButtonB, true) 
        --> RadioGroup.setCheckedId(RadioButtonBId) 
            --> RadioGroup.mOnCheckedChangeListener.onCheckedChanged(RadioGroup, RadioButtonBId);

3.RadioGroup.setCheckedId(RadioButtonBId) 
    --> RadioGroup.mOnCheckedChangeListener.onCheckedChanged(RadioGroup, RadioButtonBId);

按照Android官方文档的API说明:

public abstract void onCheckedChanged (RadioGroup group, int checkedId)
Parameters
    group	the group in which the checked radio button has changed
checkedId	the unique identifier of the newly checked radio button

那个checkedId的参数在实现上是名不副实的。

ListView的getViewTypeCount与getItemViewType

对于Listiew来说,getViewTypeCount 和getItemViewType主要用于为不同的列表项目提供不同的视图view,
主要用法在有人已经在《ListView 和 Adapter 的基础》中描述得比较清楚了,
但是文章有一点没说,就是下面这三行:

1
2
3
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;

这三行是非常重要的,因为TYPE_ITEM和TYPE_SEPARATOR的值最后是被当作一个数组的索引来使用的。

我们已经知道ListView的隐藏列表行开始被显示的时候,行View是被缓存在RecycleBin中的,那么RecycleBin缓存的顺序是什么样的呢?以上面的代码为例,大致过程如下(简化过程,并不代表实际有这些类):

1
2
3
4
5
6
7
8
9
10
11
12
mRecycler[RecycleBin]
1.mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
--> int[] mRecycler.viewCache = new View[TYPE_MAX_COUNT]
--> int[] mRecycler.viewCache = new View[2];
2.mRecycler.addScrapView(child1);
--> mRecycler.viewCache[child.viewType] = child1.view
--> mRecycler.viewCache[TYPE_ITEM] = child1.view
--> mRecycler.viewCache[0] = child1.view
3.mRecycler.addScrapView(child2);
--> mRecycler.viewCache[child.viewType] = child2.view
--> mRecycler.viewCache[TYPE_SEPARATOR] = child2.view
--> mRecycler.viewCache[1] = child2.view

可以发现,一旦ITEM_N的定义不符合数组的规范,那么溢出是非常常见的。我一开始犯的错误就是TYPE_1和TYPE_2…TYPE_N都是随意定义的,
结果报的错误居然是数组溢出,查看了ListView源码之后才发现原来具体的执行过程,以前实在是忽略了。。。。

所以综合看来,google这么做实在是有些不好理解,为什么不直接使用map来存储呢?那样就容易理解得多。

LIstView的HeaderView

一,添加HeaderView之后尺寸布局被忽略。

通常添加头部的方法是

1
2
3
LayoutInflater lif = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View headerView = lif.inflate(R.layout.header, null);
mListView.addHeaderView(headerView);

原因:

lif.inflate(R.layout.header, null)丢失了XML布局中根View的LayoutParam,应该使用的是

1
lif.inflate(R.layout.header, mListView, false);

二,添加HeaderView之后导致OnItemClickListener的position移位

OnItemClickListener接口的方法:

1
void onItemClick(AdapterView<?> parent, View view, int position, long id)

position通常是从0开始的,但是添加了HeaderView之后,position也会将HeaderView的数目计算进去。

几个解决办法:

1,手动计算真实的position位置:

1
2
3
4
5
6
7
8
final headerCount = 1;
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Item item = myAdapter.getItem(position - headerCount);
}
});

2,其实上面的步骤ListView已经为我们提供了,所以可以改写为:

1
2
3
4
5
6
7
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Item item = parent.getAdapter().getItem(position);
}
});

原因在源码中有比较清晰的解释:

当有headerView被添加时,实际传递给ListView的adapter被包装,parent.getAdapter()返回真实被 ListView 使用的 Adapter(HeaderViewListAdapter),HeaderViewListAdapter的getItem(int)方法处理了position的问题。

三,LayoutInflater的infalte()

用来呼应第一个问题。LayoutInflater的作用很简单,就是将XML的布局文件“翻译”成相应的View对象,而且出于性能的考虑,LayoutInflater只能处理编译后的XML文件,而不能处理通常明文编码的XML文件。
最常用的一个方法:

1
View inflate(int resource, ViewGroup root, boolean attachToRoot)

其中:

resource是布局文件ID 
root是父ViewGroup对象, 
attachToRoot是是否将“翻译”出来的View添加到上面的root中 

root和attachToRoot是共同作用的:

  1. 有root,同时attachToRoot为false,那么inflate()返回的就是“翻译”得到的view
  2. 有root,同时attachToRoot为true,那么inflate()就是将“翻译”得到的view添加到root后,然后返回root
  3. 无root,同时attachToRoot为false,那么inflate()返回的就是“翻译”得到的view。
  4. 无root,同时attachToRoot为true,报错。

另外,root还有一个重要的作用就是为“翻译”得到的view添加合适的LayoutParam,并且如果并不想将得到的View添加到root的话,传递何种root是并没有要求的,比如:

1
2
3
View view = mLayoutInflater.inflate(R.layout.header, new ListView(mContext), false);
View view = mLayoutInflater.inflate(R.layout.header, new LinearLayout(mContext), false);
View view = mLayoutInflater.inflate(R.layout.header, new RelativeLayout(mContext), false);

上面得到的View,除了view的LayoutParam分别为AbsListView.LayoutParams,LinearLayout.LayoutParams,RelativeLayout.LayoutParams之外,内容都一致。

EditText--输入法

1. 默认显示输入法的数字键盘,但是同时允许输入文本.

初始化的时候调用setRawInputType来设置输入法类型
EditText editText = (EditText) findViewById(R.id.x_edit_id);
edit.setRawInputType(EditorInfo.TYPE_CLASS_NUMBER);

2. 上接(1), 动态修改默认的输入法类型

修改方式参考(1),但是动态修改的之后需要重启一下InputMethodManager
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final EditText editText = (EditText) findViewById(R.id.x_edit_1);

findViewById(R.id.x_show_number).setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
editText.setRawInputType(EditorInfo.TYPE_CLASS_NUMBER);
InputMethodManager inputMethodManager=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null){
inputMethodManager.restartInput(editText);
}
}
});

findViewById(R.id.x_show_text).setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
editText.setRawInputType(EditorInfo.TYPE_CLASS_TEXT);
InputMethodManager inputMethodManager=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null){
inputMethodManager.restartInput(editText);
}
}
});

3.setRawInputType与setInputType的区别

setRawInputType只是修改输入法类型,不做其他的改动. setInputType除了修改输入法类型外,还会修改KeyListener.
换句话说,就是setRawInputType修改输入法的类型,setInputType修改输入框的类型.

输入法面板的显示由输入法类型类型控制,内容的过滤由KeyListener控制.所以设置下面的调用是不同的:

1
edit.setRawInputType(EditorInfo.TYPE_CLASS_NUMBER)

只是设置输入法类型为数字类型,于是输入法面板会弹出数字面板.

1
setInputType(EditorInfo.TYPE_CLASS_NUMBER)

除了设置输入法类型为数字类型,同时会将非数字的输入全部过滤掉,因此只能输入纯数字.

4.设置输入法面板类型

1
2
EditText editText = (EditText) findViewById(R.id.x_edit_id);
editText.setRawInputType(Configuration.KEYBOARD_12KEY);

但是各输入法实现不是很标准,所以使用也不是非常可靠.

KEYBOARD_12KEY:设备有一个12键的物理键盘,就是以前功能机的那种数字键盘. 
KEYBOARD_NOKEYS):设备有一个没有物理键盘. 
KEYBOARD_QWERTY):设备有一个QWERTY键的物理键盘,比如Moto里程碑系列. 
KEYBOARD_UNDEFINED):未定义 

Android 自定义对话框--模拟Dim

Android 在弹出 Dialog 的时候,默认在 dialog 背后会产生办透明的模糊效果(backgroundDim).这个dim层可以调整模糊值:

1
2
<item name="android:backgroundDimEnabled">true</item><!-- 开启背景模糊 -->
<item name="android:backgroundDimAmount">0.5</item><!-- 背景模糊值 -->

现在的问题是要改变此dim层的颜色,我没有找到修改这个dim color 的方法,因此只能弄点偏门了.

解决办法:

将对话框覆盖整个activity,然后自定义背景.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MockDimDialog extends Dialog{

public MockDimDialog(Context context) {
super(context, R.style.MockDimDialog);
setContentView(R.layout.sample_segment_dialog);
WindowManager.LayoutParams wlp = getWindow().getAttributes();
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
//下面两行用来保留status bar,不然就全屏了
wlp.gravity = Gravity.TOP;
wlp.y = 1;
getWindow().setAttributes(wlp);
}
}

样式:

1
2
3
4
5
6
<style name="MockDimDialog" parent="android:Theme.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowBackground">@color/dim</item>
</style>
<color name="dim">#55ff6600</color>

自定义的dialog view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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:gravity="center"
android:orientation="vertical" >

<ProgressBar
style="@android:style/Widget.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dip"
android:textSize="20dip"
android:textColor="#707070"
android:text="Loading..." />

</LinearLayout>

显示效果:

1

Android 自定义ADT模板

ADT模板就是在Eclipse中使用向导新建Android工程或者Android组件的时候使用的模板。ADT模板的特点:

  1. 可以通过简单的可视化配置[后面称之为UI parameters]来生成Android代码和资源样本
  2. 集成到Eclipse ADT中
  3. FreeMarker驱动

Android的默认模板

SDK下载完成之后,一般自带了一部分模板,模板的位置为:

$your_android_sdk_dir/tools/templates

模板的类型有:

  1. Android Application Templates
    这个模板是使用Eclipse的新建Android工程向导[包括Android project,lib project和test project]时使用的模板类型
  2. Android Activity Templates
    这个显然就是使用Eclipse的新建Android Activity向导时使用的模板类型
  3. Android Object Templates
    这个就是创建其他一些android组件向导时使用的一些模板[File -> New -> Other -> Android/Android Object 可以打开此类向导]

Activity示例

打开新建Android Activity向导,ADT插件首先就会列出一些可选模板让我们选择,如下:

1

我们再打开 $your_android_sdk_dir/tools/templates/activities 文件夹,会发现正好和向导的选择一一对应,不过要指出的是,模板文件夹的名字并不是模板的名字,这里只是恰好一样而已。
至于其他的Application Templates和Object Templates的基本情况都是一样的。

模板的工作流程

2

模板的具体构成

下面结合Activity模板来稍微说明下。开始之前,除了必要的Eclipse + ADT plugin + Android SDK,我们需要一个辅助工具——FreeMarker IDE
FreeMarker IDE是个eclipse的插件,安装过程在FreeMarker的官网有介绍。

官方提供的模板就是最好的资料,为了避免破坏原有的模板,我们新建一个模板工程:
File -> New -> Project -> General/Project:
把新工程Xe_CustomActivity建立在了SDK的templates里面,然后将BlankActivity文件夹中的内容拷贝到新工程里面,这样就可以在eclipse里面直接使用了。

3

我们重复一下上面的使用向导创建Activity的步骤,会发现有两个BlankActivity,其中一个是SDK自带的,一个是我们刚才创建的,这里再次表明文件夹的名字和模板名字是两码事。

4

我们查看一下新工程的大致目录结构:

project_name:
...root
......AndroidManifest.xml.ftl
......res
.........layout
............*.ftl/*.*
......src
.........app_package
............*.ftl/*.*
...template.xml
...recipe.xml.ftl
...globals.xml.ftl
...*.png

附带说明:.ftl表示FreeMarker模板语言

文件说明

template.xml

可以说是模板的模板,定义了模板的流 程框架 基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0"?>
<template
format="3"
revision="2"
name="Blank Activity" <!-- 在向导中显示的模板名称 -->
description="Creates a new blank activity, with an action bar and optional navigational elements such as tabs or horizontal swipe.">
<dependency name="android-support-v4" revision="8" />
<category value="Activities" /> <!-- 模板类型 -->
<parameter
id="activityClass" <!-- 参数名在ftl文件中可以用${activityClass}获取参数值 -->
name="Activity Name" <!-- UI 界面输入框前的提示标签值 -->
type="string" <!-- 参数值类型 -->
constraints="class|unique|nonempty" <!-- 参数值约束条件,这里的约束是必须是类名,唯一,非空 -->
suggest="${layoutToActivity(layoutName)}" <!-- 自动提示,比如输入layout的值可以自动生成activityClass -->
default="MainActivity" <!--默认值 -->
help="The name of the activity class to create" /> <!-- 向导对话框底部的帮助性文字 -->
<thumbs>
<thumb>template_blank_activity.png</thumb>
<thumb navType="none">template_blank_activity.png</thumb>
<thumb navType="tabs">template_blank_activity_tabs.png</thumb>
<thumb navType="tabs_pager">template_blank_activity_tabs_pager.png</thumb>
<thumb navType="pager_strip">template_blank_activity_pager.png</thumb>
<thumb navType="dropdown">template_blank_activity_dropdown.png</thumb>
</thumbs>

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

</template>

几个重要的节点:

category节点:表示模板的类型,可选的值包括三种:

  1. Applications表示Android Application Templates
  2. Activities表示Android Activities Templates
  3. UI Component表示Android Object Templates中那些带有试图的UI组件模板,所以类似Service这种没有界面的组件模板中就没有这个节点了。

parameter节点:定义了图形配置界面的用户输入参数项。

参数类型由parameter节点的type属性定义,常见的类型有:
string——表现为输入框
boolean——表现为勾选框
enum——表现为下拉选择框

thumbs节点:定义了静态预览图。

对照Activity向导可以很容易的知道各个节点的意思:

6

就是将工程定义的全局变量包含进来。

表示开始执行模板渲染。

因此,template.xml的结构和作用可以描述为:

7

globals.xml.ftl

这个文件的目的只有一个,就是提供全局变量[Global Values],简单示例:

<global id="resOut" value="res" />
<global id="menuName" value="${classToResource(activityClass)}" />

其他文件中的引用方式就是${resOut}以及${menuName}等等

recipe.xml.ftl

菜单模板,名字挺形象的,定义流程执行的步骤,一个典型的recipe.xml.ftl文件:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<recipe>
<merge from="AndroidManifest.xml.ftl" />

<copy from="res/values-v14/styles_ics.xml"
to="res/values-v14/styles.xml" />

<instantiate from="res/menu/main.xml.ftl"
to="res/menu/${menuName}.xml" />

<open file="res/layout/${layoutName}.xml" />
</recipe>

可以看到recipe.xml.ftl使用了许多变量[后文称之为模板变量],那么这些变量来自那些地方呢?主要来自两个方面:

  1. UI Parameters
  2. Global Values

模板变量数据流向

8

定制化自己的ADT模板,简单上手

  1. 我们将所有的模板帮助提示都改成中文,在template.xml文件中,主要是修改description的属性值:
1
2
3
4
5
6
7
8
<parameter
id="activityClass"
name="Activity名称"
type="string"
constraints="class|unique|nonempty"
suggest="${layoutToActivity(layoutName)}"
default="MainActivity"
help="Activity的类名" />
  1. 一般我不会直接使用android默认的titlebar,而会自己定义一个TextView来定制title,因此我希望在向导中添加一个Page Title配置项,在template.xml添加下面的内容:
1
2
3
4
5
6
7
8
<parameter
id="pageTitle"
name="My Page Title"
type="string"
constraints="nonempty"
default="默认标题"
suggest="${activityClass}_page_title"
help="自定义页面的标题" />

9

在默认的activity布局文件[可以是root/res/layout/activity_simple.xml.ftl]中添加一个TextView

1
2
3
4
5
<TextView
android:background="#ff5500"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="${pageTitle}" />

3.app中会引用其他的一些库,这些库通常也会带有很多activity布局文件,为了和自己的布局文件去分开,所以我通常在自己的布局文件前面添加一个前缀,可以这么修改:

1. 定义一个前缀全局变量 
2. 分别在template.xml和recipe.xml.ftl修改相应的名称

globals.xml.ftl

1
2
3
4
5
6
<global id="xe_prefix" value="xe" />
recipe.xml.ftl:
<instantiate from="res/menu/main.xml.ftl"
to="${resOut}/menu/${xe_prefix}_${menuName}.xml" />
<instantiate from="res/layout/activity_simple.xml.ftl"
to="${resOut}/layout/${xe_prefix}_${layoutName}.xml" />

root/src/app_package/SimpleActivity.java.ftl:

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${xe_prefix}_${layoutName});
<#if parentActivityClass != "">
// Show the Up button in the action bar.
setupActionBar();
</#if>
}

附录

  1. classToResource等方法定义在ADT插件中,具体源码:
    https://android.googlesource.com/platform/sdk/+/7dd444ea0125e50a5e88604afb6de43e80b7c270/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt
  2. FreeMarker参考 http://freemarker.org/index.html
    中文文档: http://jaist.dl.sourceforge.net/project/freemarker/chinese-manual/FreeMarker_Manual_zh_CN.pdf
    参考 《Custom Android Code Templates》

Android meta-data小结

meta-data就像其名一样,主要用来定义一些组件相关的配置值。

按照官方定义,metadata是一组供父组件使用的名值对(name-value pair),因此相应的meta-data元素应该定义在相应的组件中。
即如果想在activity中使用metadata,那么meta-data必须定义在AndroidManifest.xml的activity声明中。

使用

所有的名值对被包装成Bundle供组件使用,因此使用方式同Bundle。metadata普通值由value属性给出,资源ID由resource属性给出。比如我们定义资源:

1
<string name="x_key">resource key</string>

//R

1
public static final int ic_launcher=0x7f020000;

定义metadata

1
2
3
4
5
6
7
8
9
<meta-data
android:name="com.xesam.key_1"
android:value="x_key" />
<meta-data
android:name="com.xesam.key_2"
android:value="@string/x_key" />
<meta-data
android:name="com.xesam.img"
android:resource="@drawable/ic_launcher" />

那么有:

metadata.getString("com.xesam.key_1") ==> "x_key"
metadata.getString("com.xesam.key_2") ==> "resource key"
metadata.getInt("com.xesam.img")      ==> 0x7f020000

由于resource指向资源ID,因此用metadata可以定义一些稍微复杂的值。

比如要定义一副图片,则可以用这个,然后在代码中用getInt()取出图片的ID:

1
2
int imageId = meta.getInt("com.xesam.img");
((ImageView) findViewById(R.id.img)).setImageResource(imageId);

使用问题

形如:

1
2
3
<meta-data
android:name="com.xesam.key_1"
android:value="000" />

类似这样的值如果使用bundle.getString()的话是不起作用的,因为Bundle中使用的是形如:

1
return (String) o;

的代码获取一个StringValue值的,但是在将metadata包装成bundle的时候,”000”被解析成整数0,
因此bundle.getString(“com.xesam.key_1”)返回的是(String)0,显然,java是不允许这样的,因此最后得到的是null。 话说android为什么不是用String.valueOf()或者obj.toString()呢?
为了避免这种情况:
1,可以在形如000的字符串前面放个\0空字符,强迫android按照字符串解析000。
2,在资源文件中指定需要的值,然后在metadata的value中引用此值。

示例代码

附:

//在Activity应用元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ActivityInfo info = this.getPackageManager()
.getActivityInfo(getComponentName(),PackageManager.GET_META_DATA);
info.metaData.getString("meta_name");

//在application应用<meta-data>元素。
ApplicationInfo appInfo = this.getPackageManager()
.getApplicationInfo(getPackageName(),PackageManager.GET_META_DATA);
appInfo.metaData.getString("meta_name");

//在service应用<meta-data>元素。
ComponentName cn = new ComponentName(this, MetaDataService.class);
ServiceInfo info = this.getPackageManager().getServiceInfo(cn, PackageManager.GET_META_DATA);
info.metaData.getString("meta_name");

//在receiver应用<meta-data>元素。
ComponentName cn = new ComponentName(context, MetaDataReceiver.class);
ActivityInfo info = context.getPackageManager().getReceiverInfo(cn, PackageManager.GET_META_DATA);
info.metaData.getString("meta_name");

meta-data官方地址 http://developer.android.com/reference/android/os/Bundle.html