需要权限
1 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> |
示例代码
1 | Dialog dialog = new AlertDialog.Builder(ctx).create(); |
注意事项
当activity不在前台的时候,部分手机(比如小米2s,系统4.1.1)不支持直接弹出dialog,之后在显示activity之后,才会看到dialog。
1 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> |
1 | Dialog dialog = new AlertDialog.Builder(ctx).create(); |
当activity不在前台的时候,部分手机(比如小米2s,系统4.1.1)不支持直接弹出dialog,之后在显示activity之后,才会看到dialog。
网易公开课虽然提供了下载链接,但是下载之后的文件名并不正确,需要下载后手动修改文件名,非常不方便。所以本扩展的目标就是在下载的时候将视频名称正确对应上去。
拦截原网页上的下载链接,在获取正确的文件名称后直接调用chrome的download接口来下载视频文件。
解决Android官网被墙后离线文档查看不是很方便的问题。
因为虽然是离线文档,但是实际上在连接网络的情况下,离线页面还是会去google的网站上下载一部分资源,典型的就是会下载实现搜索功能的js。现在的问题是,google被和谐了,当打开离线文档的时候,页面会被加载中的资源阻塞,以至于浏览器在很长时间内都是一片空白,除非每次都手动点击浏览器的停止按钮,强制浏览器结束资源载入,才能看到文档的其他内容。
所以为了更方便的查看离线文档,可以采用几种方法:
1.查看的时候断开机器网络,这个不是很可取。
2.用脚本替换掉所有文档中不可加载的js和css等资源,网上貌似有现成的脚本可以达到这个目的。问题就是Android离线文档有600多M,每次更新都得进行处理,而且在可以访问google的环境下,搜索功能依旧不可用。
3.鉴于以上的问题,我写了个简单的chrome插件,可以在查看离线文档的时候选择是否屏蔽google的资源请求,在屏蔽google资源请求的情况下不会阻塞页面,从而避免修改实际页面的缺陷。
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 | public class MainActivity extends Activity implements CompoundButton.OnCheckedChangeListener, |
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
RadioGroup check简化如下:
1 | public void check(int newid) { |
RadioGroup setCheckedStateForView简化如下:
1 | private void setCheckedStateForView(int viewId, boolean checked) { |
RadioGroup setCheckedId简化如下:
1 | private void setCheckedId(int id) { |
同时RadioGroup内部有一个check状态跟踪器,简化一下如下:
1 | private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener { |
当每一个RadioButton被添加到RadioGroup的时候,就给每一个RadioButton设置一个checked状态监听
1 | childRadioButton.mOnCheckedChangeWidgetListener = mCheckedStateTracker; |
所以每当RadioButton的check状态发生变化,都会触发check状态跟踪器。
RadioButton的setChecked(boolean)简化如下:
1 | public void setChecked(boolean checked) { |
所以结合开头的例子,调用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的参数在实现上是名不副实的。
对于Listiew来说,getViewTypeCount 和getItemViewType主要用于为不同的列表项目提供不同的视图view,
主要用法在有人已经在《ListView 和 Adapter 的基础》中描述得比较清楚了,
但是文章有一点没说,就是下面这三行:
1 | private static final int TYPE_ITEM = 0; |
这三行是非常重要的,因为TYPE_ITEM和TYPE_SEPARATOR的值最后是被当作一个数组的索引来使用的。
我们已经知道ListView的隐藏列表行开始被显示的时候,行View是被缓存在RecycleBin中的,那么RecycleBin缓存的顺序是什么样的呢?以上面的代码为例,大致过程如下(简化过程,并不代表实际有这些类):
1 | mRecycler[RecycleBin] |
可以发现,一旦ITEM_N的定义不符合数组的规范,那么溢出是非常常见的。我一开始犯的错误就是TYPE_1和TYPE_2…TYPE_N都是随意定义的,
结果报的错误居然是数组溢出,查看了ListView源码之后才发现原来具体的执行过程,以前实在是忽略了。。。。
所以综合看来,google这么做实在是有些不好理解,为什么不直接使用map来存储呢?那样就容易理解得多。
一,天气综合查询
1.直接使用中国天气网官方APP接口,不过需要申请key:
http://smart.weather.com.cn/wzfw/smart/weatherapi.shtml
2.个人写的解析mobile站json测试接口,不需要key
测试项目 https://github.com/xesam/weather
二,空气质量查询
1.PM2.5开放查询接口,也需要申请key:
通常添加头部的方法是
1 | LayoutInflater lif = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
原因:
lif.inflate(R.layout.header, null)丢失了XML布局中根View的LayoutParam,应该使用的是
1 | lif.inflate(R.layout.header, mListView, false); |
OnItemClickListener接口的方法:
1 | void onItemClick(AdapterView<?> parent, View view, int position, long id) |
position通常是从0开始的,但是添加了HeaderView之后,position也会将HeaderView的数目计算进去。
几个解决办法:
1,手动计算真实的position位置:
1 | final headerCount = 1; |
2,其实上面的步骤ListView已经为我们提供了,所以可以改写为:
1 | mListView.setOnItemClickListener(new OnItemClickListener() { |
原因在源码中有比较清晰的解释:
当有headerView被添加时,实际传递给ListView的adapter被包装,parent.getAdapter()返回真实被 ListView 使用的 Adapter(HeaderViewListAdapter),HeaderViewListAdapter的getItem(int)方法处理了position的问题。
用来呼应第一个问题。LayoutInflater的作用很简单,就是将XML的布局文件“翻译”成相应的View对象,而且出于性能的考虑,LayoutInflater只能处理编译后的XML文件,而不能处理通常明文编码的XML文件。
最常用的一个方法:
1 | View inflate(int resource, ViewGroup root, boolean attachToRoot) |
其中:
resource是布局文件ID
root是父ViewGroup对象,
attachToRoot是是否将“翻译”出来的View添加到上面的root中
root和attachToRoot是共同作用的:
另外,root还有一个重要的作用就是为“翻译”得到的view添加合适的LayoutParam,并且如果并不想将得到的View添加到root的话,传递何种root是并没有要求的,比如:
1 | View view = mLayoutInflater.inflate(R.layout.header, new ListView(mContext), false); |
上面得到的View,除了view的LayoutParam分别为AbsListView.LayoutParams,LinearLayout.LayoutParams,RelativeLayout.LayoutParams之外,内容都一致。
初始化的时候调用setRawInputType来设置输入法类型
EditText editText = (EditText) findViewById(R.id.x_edit_id);
edit.setRawInputType(EditorInfo.TYPE_CLASS_NUMBER);
修改方式参考(1),但是动态修改的之后需要重启一下InputMethodManager
示例:
1 | final EditText editText = (EditText) findViewById(R.id.x_edit_1); |
setRawInputType只是修改输入法类型,不做其他的改动. setInputType除了修改输入法类型外,还会修改KeyListener.
换句话说,就是setRawInputType修改输入法的类型,setInputType修改输入框的类型.
输入法面板的显示由输入法类型类型控制,内容的过滤由KeyListener控制.所以设置下面的调用是不同的:
1 | edit.setRawInputType(EditorInfo.TYPE_CLASS_NUMBER) |
只是设置输入法类型为数字类型,于是输入法面板会弹出数字面板.
1 | setInputType(EditorInfo.TYPE_CLASS_NUMBER) |
除了设置输入法类型为数字类型,同时会将非数字的输入全部过滤掉,因此只能输入纯数字.
1 | EditText editText = (EditText) findViewById(R.id.x_edit_id); |
但是各输入法实现不是很标准,所以使用也不是非常可靠.
KEYBOARD_12KEY:设备有一个12键的物理键盘,就是以前功能机的那种数字键盘.
KEYBOARD_NOKEYS):设备有一个没有物理键盘.
KEYBOARD_QWERTY):设备有一个QWERTY键的物理键盘,比如Moto里程碑系列.
KEYBOARD_UNDEFINED):未定义
Android 在弹出 Dialog 的时候,默认在 dialog 背后会产生办透明的模糊效果(backgroundDim).这个dim层可以调整模糊值:
1 | <item name="android:backgroundDimEnabled">true</item><!-- 开启背景模糊 --> |
现在的问题是要改变此dim层的颜色,我没有找到修改这个dim color 的方法,因此只能弄点偏门了.
将对话框覆盖整个activity,然后自定义背景.
1 | class MockDimDialog extends Dialog{ |
样式:
1 | <style name="MockDimDialog" parent="android:Theme.Dialog"> |
自定义的dialog view:
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
ADT模板就是在Eclipse中使用向导新建Android工程或者Android组件的时候使用的模板。ADT模板的特点:
SDK下载完成之后,一般自带了一部分模板,模板的位置为:
$your_android_sdk_dir/tools/templates
模板的类型有:
打开新建Android Activity向导,ADT插件首先就会列出一些可选模板让我们选择,如下:
我们再打开 $your_android_sdk_dir/tools/templates/activities 文件夹,会发现正好和向导的选择一一对应,不过要指出的是,模板文件夹的名字并不是模板的名字,这里只是恰好一样而已。
至于其他的Application Templates和Object Templates的基本情况都是一样的。
下面结合Activity模板来稍微说明下。开始之前,除了必要的Eclipse + ADT plugin + Android SDK,我们需要一个辅助工具——FreeMarker IDE
FreeMarker IDE是个eclipse的插件,安装过程在FreeMarker的官网有介绍。
官方提供的模板就是最好的资料,为了避免破坏原有的模板,我们新建一个模板工程:
File -> New -> Project -> General/Project:
把新工程Xe_CustomActivity建立在了SDK的templates里面,然后将BlankActivity文件夹中的内容拷贝到新工程里面,这样就可以在eclipse里面直接使用了。
我们重复一下上面的使用向导创建Activity的步骤,会发现有两个BlankActivity,其中一个是SDK自带的,一个是我们刚才创建的,这里再次表明文件夹的名字和模板名字是两码事。
我们查看一下新工程的大致目录结构:
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 |
|
category节点:表示模板的类型,可选的值包括三种:
参数类型由parameter节点的type属性定义,常见的类型有:
string——表现为输入框
boolean——表现为勾选框
enum——表现为下拉选择框
对照Activity向导可以很容易的知道各个节点的意思:
因此,template.xml的结构和作用可以描述为:
这个文件的目的只有一个,就是提供全局变量[Global Values],简单示例:
<global id="resOut" value="res" />
<global id="menuName" value="${classToResource(activityClass)}" />
其他文件中的引用方式就是${resOut}以及${menuName}等等
菜单模板,名字挺形象的,定义流程执行的步骤,一个典型的recipe.xml.ftl文件:
1 |
|
可以看到recipe.xml.ftl使用了许多变量[后文称之为模板变量],那么这些变量来自那些地方呢?主要来自两个方面:
1 | <parameter |
1 | <parameter |
在默认的activity布局文件[可以是root/res/layout/activity_simple.xml.ftl]中添加一个TextView
1 | <TextView |
3.app中会引用其他的一些库,这些库通常也会带有很多activity布局文件,为了和自己的布局文件去分开,所以我通常在自己的布局文件前面添加一个前缀,可以这么修改:
1. 定义一个前缀全局变量
2. 分别在template.xml和recipe.xml.ftl修改相应的名称
1 | <global id="xe_prefix" value="xe" /> |
root/src/app_package/SimpleActivity.java.ftl:
1 |
|
附录
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 | <meta-data |
那么有:
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 | int imageId = meta.getInt("com.xesam.img"); |
形如:
1 | <meta-data |
类似这样的值如果使用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 | ActivityInfo info = this.getPackageManager() |
meta-data官方地址 http://developer.android.com/reference/android/os/Bundle.html