Android4.x 思维导图
作者:李旺成
时间:2016年4月7日
AndroidStudyDemo 系列篇章开始了!!!
AndroidStudyDemo 你值得拥有
AndroidStudyDemo之Android4.x介绍
AndroidStudyDemo之Android5.x新API介绍(一)
AndroidStudyDemo之Android5.x新API介绍(二)
今天给大家介绍一下我的 AndroidStudyDemo 下的 Android4Study 中的相关内容,先看下面的动态图,演示了该 Demo 中所包含的内容。
提示:该 Demo 会以单独项目的的形式提供,并且会集成到 AndroidStudyDemo 项目当中(周末),你可以根据自己的需要选取。
Android4新控件
下面我将阐述一下该 Demo 中所涉及的知识点。
目录
一、 Switch
二、 Space
三、 PopupMenu
四、 GlidLayout
五、 TextureView
一、Switch
Switch 是一个可以在两种状态切换的开关控件,它只有两个状态:开和关;和 CheckBox 挺像。
Google 的 Nexus 系列的升级到 4.x(汗,就是原生系统),在系统设置页面可以看到很多打开设备都使用了该控件。以前羡慕 iOS 上提供了漂亮的 UISwitch控件,OK,Google 也为广大 Android 程序员提供了类似的开关(别高兴的太早,设计师们会告诉你,我要的不是这个效果~~哈哈,我们早已习惯了不是,但是不妨碍我们自己用...)。
来看看效果图:
Switch 控件
Switch 类关系图
学习一个新控件的时候,我的习惯是先看看该控件的继承结构(在 AndroidStudio 光标置类名上,点击 F4 按钮即可查看该类的继承结构)。为什么?如果该控件是继承自你熟悉的控件,那简单了,看看是不是多加了几个属性就好了;如果不是,那可能就需要花点时间来熟悉下了。好了,不多说这些,上图:
Switch 继承结构
Button,熟悉吧!CompoundButton,这个不是很眼熟,那好来看看下面这个段代码:
mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO
}
});
没错 CheckBox 的 setOnCheckedChangeListener 用的就是 CompoundButton 中的 OnCheckedChangeListener 接口,CompoundButton 出现了,还和 CheckBox 扯上了关系。好吧,这个时候要么去看看 CompoundButton,要么去看看 CheckBox,看你喜好。我选熟悉的 —— CheckBox,如下图:
CheckBox 继承结构
Switch 的简单使用
这下清晰了吧!CheckBox 和 Switch 是兄弟。好了绕了这么一大圈,其实只是想分享下我一般怎么去学习一个新控件。
既然是兄弟,那应该很像,那直接当成 CheckBox 试一下啊!好,试一下设置 textXXX 相关的属性,直接看提示有哪些 textXXX 属性:
Switch 的 TextXXX 属性
上图用红色框圈出来的有点面生,看下 CheckBox 的,类比学习嘛,上图:
CheckBox 的 TextXXX 属性
上面那几个在 CheckBox 中没有,嗯,看着字面上就是“关的文本”和“开的文本”的意思(方法二:猜),实践出真知,多简单的事。上代码:
<Switch
android:id="@+id/switch_test1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOn="开"
android:textOff="关"/>
好了,布局文件搞定了,来监听下事件:
this.mTest1Switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO
}
});
毫无违和感,和 CheckBox 一样。看到这里应该清楚了,这就是 CheckBox 的另一个版本,做成了开关的样式而已,多加了几个属性,用法和 CheckBox 基本类似。
Switch 自定义样式
android:thumb : 设置滑块图片
android:thumbTextPadding : 设置背景图片和文字之间的间距,可用来控制 Switch 整体宽度
android:track : 设置背景图
提示:等你高高兴兴的把自己的 .9 图都设置上去后来看效果,怎么看都感觉不对劲。为什么?高度好像有点不听使唤,layout_height 和 minHeight 齐上,还是不管用。好吧!这不是本文重点(咱就用原生的,要不就自己去实现一个,网上多的是仿 iOS 开关的自定义控件)
好了,Switch 的介绍到这里了。
二、Space
Space 是一个空白控件,那有什么用 —— 占位。要效果图,好吧,上图:
Space效果图.png
Space 类关系图
继承结构很简单,看图:
Space 继承结构
对,就是继承自 View,熟悉吧!但是,这回没辙了,View 基本上算是所有控件的基类了,这回看你怎么类比学习。确实,龙生九子各有不同,甚至很多都不像龙了。那就不类比了,直接点击进去看看,看什么是个问题 —— 这还用说,当然是看 draw() 方法,上图:
Space 的 draw()方法
坑!空方法,什么都没有。对了,就是什么都没有,所以才高效(没什么要 draw 的当然高效了)。因为 Space 的定位就是空白控件,哥们就是用来占位的,不要想多了。
Space 的简单使用
侮辱我的智商吗?好吧,为了队形整齐,看代码吧:
<Space
android:id="@+id/space_test1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ff00ff"/>
background,这是几个意思,别介,只是验证一下,哥们就是个空白控件...
好了,Space 结束。
三、PopupMenu
PopupMenu 是弹出式菜单,它会在指定组件上方或下方弹出菜单。
PopupMenu 有没有感觉和 PopupWindow 名字上很像,先看看效果图:
PopupMenu 效果图
PopupMenu 类关系图
PopupMenu 继承结构
继承自 Object,这个更熟悉了,但是没用...
那在看看它的基友 PopupWindow,上图:
PopupWindow 继承结构
没辙了,只能看文档了,看下 PopupMenu 的官方文档:
PopupMenu 文档
没什么可说的,先看看怎么让它显示出来。
PopupMenu 的简单使用
使用 PopupMenu 创建菜单的步骤:
- 调用new PopupMenu(Context context,View anchor)创建下拉菜单,anchor代表要激发该弹出菜单的组件。
- 调用MenuInflater的inflate()方法将菜单资源填充到PopupMenu中。
- 调用PopupMenu的show()方法显示弹出式菜单。
看代码,源码之下,一切都无所遁形:
// 创建 PopupMenu
private void createPopupMenuByCode() {
mPopupMenu1 = new PopupMenu(this, findViewById(R.id.btn_popupmenu1));
Menu menu = mPopupMenu1.getMenu();
// 通过代码添加菜单项
menu.add(Menu.NONE, MENU_ITEM_COPY_ID, 0, "唐僧");
menu.add(Menu.NONE, MENU_ITEM_PASTE_ID, 1, "孙悟空");
}
private void createPopupMenuFromXML() {
mPopupMenu2 = new PopupMenu(this, findViewById(R.id.btn_popupmenu2));
Menu menu = mPopupMenu2.getMenu();
// 通过XML文件添加菜单项
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.popupmenu, menu);
}
private void createPopupMenuFromMixture() {
mPopupMenu3 = new PopupMenu(this, findViewById(R.id.btn_popupmenu3));
Menu menu = mPopupMenu3.getMenu();
// 通过代码添加菜单项
menu.add(Menu.NONE, MENU_ITEM_COPY_ID, 0, "唐僧");
menu.add(Menu.NONE, MENU_ITEM_PASTE_ID, 1, "孙悟空");
// 通过XML文件添加菜单项
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.popupmenu, menu);
}
// 显示 PopupMenu
private void showPopupMenu1() {
mPopupMenu1.show();
}
private void showPopupMenu2() {
mPopupMenu2.show();
}
private void showPopupMenu3() {
mPopupMenu3.show();
}
很简单吧,这里就不和 PopupWindow 对比了,没什么意思,也不是本文重点,PopupMenu 介绍到这里了。项目源码在文末会给出 GitHub 地址。
四、GlidLayout
GridLayout 是网格布局,为解决嵌套而生,将布局以行和列进行分割。同样的先看效果图:
GlidLayout 效果图
上图将网格的行列显示的很清晰,对人如其名(错了,控件如其名),这货就是个网格布局,使用虚细线将布局划分为行,列和单元格——支持一个控件在行,列上都有交错排列。(参考自:廖煜嵘——“Android 4.0新增Space及GridLayout初谈”)
前面说了,GridLayout 是为了解决嵌套而生(嵌套,不用说了吧!不好,不仅影响性能,还增加了布局文件的复杂度),如果仅仅是这个可能还体现不了 GlidLayout 的强大。拿 LinearLayout 开刀(谁让它最容易写出嵌套,不找它找谁),在一些复杂的布局中,有时会遇到这样一个问题 —— 在 LinearLayout 中不能同时在 X,Y 轴方向上进行控件的对齐。
如下图描述了这个缺点(引自:廖煜嵘——“Android 4.0新增Space及GridLayout初谈”):
LinearLayout 的缺点
这里,当 Email address 这个标签的文本发生变化时,既要保持跟其右边控件的下部的基线对齐,又要保持跟下面的控件的右边缘对齐,而用嵌套布局的方式是不能实现的,因为不能够同时在 X,Y 轴上进行控件的对齐。于是我们便需要引入新的布局方式 GridLayout。(引自:廖煜嵘——“Android 4.0新增Space及GridLayout初谈”)
GlidLayout 类关系图
GlidLayout 继承结构
不出意料,没有继承自常见的那五大布局,那只能是 ViewGroup 了。来看看官方文档:
GildLayout 文档
一眼扫去,加了几个行列相关的属性,那就试试吧!
GlidLayout 简单使用
简易计算器布局
先看效果图:
GlidLayout 实现计算器布局
布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alignmentMode="alignBounds"
android:columnCount="4"
android:orientation="horizontal"
android:rowCount="5">
<!-- 第一行 -->
<Button
android:id="@+id/one"
android:text="1"/>
<Button
android:id="@+id/two"
android:text="2" />
<Button
android:id="@+id/three"
android:text="3" />
<Button
android:id="@+id/devide"
android:text="/" />
<!-- 第二行 -->
<Button
android:id="@+id/four"
android:text="4" />
<Button
android:id="@+id/five"
android:text="5" />
<Button
android:id="@+id/six"
android:text="6" />
<Button
android:id="@+id/multiply"
android:text="×"
android:layout_gravity="fill" />
<!-- 第三行 -->
<Button
android:id="@+id/seven"
android:text="7" />
<Button
android:id="@+id/eight"
android:text="8" />
<Button
android:id="@+id/nine"
android:text="9" />
<Button
android:id="@+id/minus"
android:text="-"
android:layout_gravity="fill"/>
<!-- 第四行 -->
<Button
android:id="@+id/zero"
android:layout_columnSpan="2"
android:layout_gravity="fill"
android:text="0" />
<Button
android:id="@+id/point"
android:text="." />
<Button
android:id="@+id/plus"
android:layout_gravity="fill"
android:layout_rowSpan="2"
android:text="+" />
<!-- 第五行 -->
<Button
android:id="@+id/equal"
android:layout_columnSpan="3"
android:layout_gravity="fill"
android:text="=" />
</GridLayout>
简单解释一下
GridLayout 提供了和 LinearLayout 类似的 API(降低使用门槛,它们也算兄弟不是),只是布局的思路不大一样,GridLayout 有点像屏幕显示的原理,占用不同的单元格来形成不同的图案(当然只是简单的类比一下,不要较真,为了便于理解而已)。
GridLayout的布局策略简单分为以下三个部分:
首先,它与LinearLayout布局一样,也分为水平和垂直两种方式,默认是水平布局,一个控件挨着一个控件从左到右依次排列,但是通过指定android:columnCount 设置列数的属性后,控件会自动换行进行排列。另一方面,对于 GridLayout 布局中的子控件,默认按照 wrap_content 的方式设置其显示,这只需要在 GridLayout 布局中显式声明即可。
其次,若要指定某控件显示在固定的行或列,只需设置该子控件的android:layout_row 和 android:layout_column 属性即可,但是需要注意:android:layout_row=”0”表示从第一行开始,android:layout_column=”0”表示从第一列开始,这与编程语言中一维数组的赋值情况类似。
最后,如果需要设置某控件跨越多行或多列,只需将该子控件的android:layout_rowSpan 或者 layout_columnSpan 属性设置为数值,再设置其 layout_gravity 属性为 fill 即可,前一个设置表明该控件跨越的行数或列数,后一个设置表明该控件填满所跨越的整行或整列。
(参考自:李响——"浅谈android4.0开发之GridLayout布局")
LinearLayout 之殇
上面说了,LinearLayout 容易嵌套(那你不会用 RelativeLayout啊,“剧情”需要,不要较真),而且不能同时在X,Y轴方向上进行控件的对齐。好,GridLayout 闪亮登出的时刻到了,先看效果图:
GridLayout 解决 LinearLayout 的问题
直接上代码:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alignmentMode="alignBounds"
android:columnCount="4"
android:columnOrderPreserved="false"
android:useDefaultMargins="true">
<!-- 第一行跨4列 -->
<TextView
android:layout_columnSpan="4"
android:layout_gravity="center_horizontal"
android:text="@string/text_emailsetup"
android:textSize="22sp" />
<!-- 第二行跨4列 -->
<TextView
android:layout_columnSpan="4"
android:layout_gravity="left"
android:text="@string/text_emailsetup_tip"
android:textSize="14sp" />
<!-- 第三行一列 -->
<TextView
android:layout_gravity="right"
android:text="@string/text_emailaddress_tip" />
<!-- 第三行二列 -->
<EditText android:ems="8" />
<!-- 第四行一列 -->
<TextView
android:layout_column="0"
android:layout_gravity="right"
android:text="@string/text_emailpassword_tip" />
<!-- 第四行二列 -->
<EditText android:ems="6" />
<!-- 第五行跨三列 -->
<Space
android:layout_row="4"
android:layout_column="0"
android:layout_columnSpan="3"
android:layout_gravity="fill" />
<!-- 第六行四列 -->
<Button
android:layout_row="5"
android:layout_column="3"
android:text="@string/text_nextstep" />
</GridLayout>
看到 layout_row、layout_column 和 layout_columnSpan,有没有觉得和 TabLayout 有点像,对,在这两个布局中这些属性的作用是相似的,看看注释,自己再去试试,相信这没什么难的。(Android 4.0新增Space及GridLayout初谈 中有较详细的说明,在这我就不赘述了)
GridLayout 的布局方向
在 GridLayout 中,默认的布局是水平方向的,即将控件从左到右,从上到下进行排列,比如下图中的文本“1-1”即放置在第1行第1列中,以此类推。
要使用垂直的布局,很简单,和 LinearLayout 一样,它也有 orientation 属性,设置为“vertical”即可,注意查看控件排列的变化。
直接看图:
GridLayout 的默认布局为水平
GridLayout 使用垂直布局
GridLayout 中子控件 layout_gravity 和 gravity 使用
要实现控件间的对齐,可以通过设置android:layout_gravity="fill_horizontal",来,看看效果:
GridLayout 子控件的 layout_gravity 使用
使用android:layout_gravity="fill",可以使子控件伸展充满被包含的父控件,看效果:
GridLayout 子控件的 layout_gravity / gravity 使用
GridLayout 中子控件的 xxxWeight 属性
xxxWeight,与 LinearLayout 中子控件的 layout_weight 属性功能类似,可以实现行或列平均分配,上图:
GridLayout 中 xxxWeight 属性.png
一切都在代码里,你懂的:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gl_gridlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="4"
android:rowCount="7">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eee"
android:text="0"
android:textColor="#000"
android:textSize="50sp"
android:layout_columnSpan="4" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Clear"
android:layout_columnSpan="4" />
<Button
android:id="@+id/one"
android:text="1"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/two"
android:text="2"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/three"
android:text="3"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/devide"
android:text="/"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/four"
android:text="4"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/five"
android:text="5"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/six"
android:text="6"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/multiply"
android:text="×"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/seven"
android:text="7"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/eight"
android:text="8"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/nine"
android:text="9"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/minus"
android:text="-"
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/zero"
android:text="0"
android:layout_columnSpan="2"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:layout_rowWeight="1" />
<Button
android:id="@+id/point"
android:text="."
android:layout_columnWeight="1"
android:layout_rowWeight="1" />
<Button
android:id="@+id/plus"
android:text="+"
android:layout_columnWeight="1"
android:layout_rowSpan="2"
android:layout_rowWeight="1" />
<Button
android:id="@+id/equal"
android:text="="
android:layout_width="match_parent"
android:layout_columnSpan="4"
android:layout_rowWeight="1" />
</GridLayout>
好了,GridLayout 的介绍就到这里了。
五、TextureView
TextureView 作为 SurfaceView 的兄弟,可弥补其不足。老规矩了,先上图:
TextureView 效果图
如果你想显示一段在线视频或者任意的数据流比如视频或者 OpenGL 场景,你可以用 android 中的 TextureView 做到。
TextureView 的兄弟 SurfaceView
应用程序的视频或者opengl内容往往是显示在一个特别的UI控件中:SurfaceView。SurfaceView 的工作方式是创建一个置于应用窗口之后的新窗口。这种方式的效率非常高,因为 SurfaceView 窗口刷新的时候不需要重绘应用程序的窗口(Android 普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次,因此效率非常低下,不过满足普通应用界面的需求还是绰绰有余),但是 SurfaceView 也有一些非常不便的限制。因为 SurfaceView 的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在 ListView 或者 ScrollView 中,不能使用 UI 控件的一些特性比如 View.setAlpha()。
为了解决这个问题 Android 4.0 中引入了 TextureView。
(原谅我吧,上述内容引自: Android TextureView 简易教程,该作者确实说得不错,我没什么好补充的了,哈哈,还有个原因就是有点累了,偷个懒)
TextureView 类关系图
看看继承结构,如下图:
TextureView 继承结构
继承自 View,那没什么说的,看看它的兄弟 SurfaceView,见图:
SurfaceView 继承结构
还真是好兄弟,一个“德性”。好吧,那看看文档:
TextureView 文档节选
(Tip:一个文档还节选,别说屏幕不够长,就是不愿折腾...呵呵)
不多说了,官方文档上直接给了示例代码,OK,那就用它的代码试试(试试又不会**不是)。
TextureView 的简单使用
TextureView 可以像一般的 View 一样执行一些变换操作,设置透明度等。这里为了演示方便,我使用了两个下拉列表来改变透明度和变换角度,看代码吧:
public class TextureViewDemoActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
private static final String TAG = "TextureViewDemoActivity";
private Spinner mSPSetAlpha;
private Spinner mSPSetRotation;
private TextureView mTTVTest;
private Camera mCamera;
private Float[] mAlphaValueArr = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f};
private Float[] mRotationValueArr = {15.0f, 30.0f, 45.0f, 60.0f, 90.0f};
private float mAlphaValue = 1.0f;
private float mRotationValue = 90.0f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_textureviewdemo);
initView();
initData();
initListener();
}
private void initView() {
this.mSPSetAlpha = (Spinner) this.findViewById(R.id.sp_setalphavalue);
this.mSPSetRotation = (Spinner) this.findViewById(R.id.sp_setrotationvalue);
this.mTTVTest = (TextureView) this.findViewById(R.id.ttv_test);
}
private void initData() {
ArrayAdapter<Float> setAlphaAdapter = new ArrayAdapter<Float>(this,android.R.layout.simple_list_item_1, mAlphaValueArr);
setAlphaAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
this.mSPSetAlpha.setAdapter(setAlphaAdapter);
ArrayAdapter<Float> setRotationAdapter = new ArrayAdapter<Float>(this,android.R.layout.simple_list_item_1, mRotationValueArr);
setRotationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
this.mSPSetRotation.setAdapter(setRotationAdapter);
}
private void initListener() {
this.mTTVTest.setSurfaceTextureListener(this);
this.mSPSetAlpha.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAlphaValue = mAlphaValueArr[position];
mTTVTest.setAlpha(mAlphaValue);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
this.mSPSetRotation.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mRotationValue = mRotationValueArr[position];
mTTVTest.setRotation(mRotationValue);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mCamera = Camera.open();
try {
mCamera.setPreviewTexture(surface);
} catch (IOException t) {
Log.e(TAG, t.getMessage());
}
mCamera.startPreview();
mTTVTest.setAlpha(mAlphaValue);
mTTVTest.setRotation(mRotationValue);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
布局很简单,这里就不贴了!
这里只是一个简单的用法展示,具体的使用中有没有坑,还未知,你要是遇到了,请告诉我。
推荐:一个不用FQ的在线 Android 文档网站:踏得网
总结到这,讲的比较简单,你要是感觉有用,那就帮忙点个喜欢,祝好...
附录:
Andorid4.x 新控件介绍思维导图
Andorid4.x 新控件介绍思维导图.png
参考:
http://blog.csdn.net/pku_android/article/details/7343258
http://blog.csdn.net/pku_android/article/details/7343258
http://blog.csdn.net/xerophyte000/article/details/46045041
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1213/2153.html
http://tech.it168.com/a2011/1122/1277/000001277274.shtml
http://blog.csdn.net/zhangzeyuaaa/article/details/40711047
http://www.tuicool.com/articles/Abu6ji
文/diygreen(简书作者)
原文链接:http://www.jianshu.com/p/7ca6525fddcb
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。