#常见布局
###线性布局
* 有一个布局方向,水平或者竖直
* 在竖直布局下,左对齐、右对齐,水平居中生效
* 在水平布局下,顶部对齐、底部对齐、竖直居中生效
* 权重:按比例分配屏幕的剩余宽度或者高度
###相对布局
* 组件默认位置都是左上角,组件之间可以重叠
* 可以相对于父元素上下左右对齐,相对于父元素,水平居中、竖直居中、水平竖直同时居中
* 可以相对于其他组件上下左右对齐
* 可以布局于其他组件的上方、下方、左边、右边
###帧布局
* 组件默认位置都是左上角,组件之间可以重叠
* 可以设置上下左右对齐,水平竖直居中,设置方式与线性布局一样
###表格布局
* 每有一个TableRow子节点表示一行,该子节点的每一个子节点都表示一列
* TableLayout的一级子节点默认宽都是匹配父元素
* TableRow的子节点默认宽高都是包裹内容
---
#Logcat
###等级
* verbose:冗余,最低等级
* debug:调试
* info:正常等级的信息
* warn:警告
* error:错误
---
#Android的存储
###内部存储空间
* RAM内存:运行内存,相当于电脑的内存
* ROM内存:存储内存,相当于电脑的硬盘
###外部存储空间
* SD卡:相当于电脑的移动硬盘
* 2.2之前,sd卡路径:sdcard
* 4.3之前,sd卡路径:mnt/sdcard
* 4.3开始,sd卡路径:storage/sdcard
* 所有存储设备,都会被划分成若干个区块,每个区块有固定的大小
* 存储设备的总大小 = 区块大小 * 区块数量
---
#文件访问权限
* 指的是谁能访问这个文件
* 在Android中,每一个应用,都是一个独立的用户
* 使用10个字母表示
* drwxrwxrwx
* 第一个字母:
* d:表示文件夹
* -:表示文件
* 第一组rwx:表示的是文件拥有者(owner)对文件的权限
* r:read,读
* w:write
* x:execute
* 第二组rwx:表示的是跟文件拥有者属于同一用户组的用户(grouper)对文件的权限
* 第三组rwx:表示的其他用户(other)对文件的权限
---
#SharedPreference
* 非常适合用来保存零散的简单的数据
#常见布局
###相对布局
#####RelativeLayout
* 组件默认左对齐、顶部对齐
* 设置组件在指定组件的右边
android:layout_toRightOf="@id/tv1"
* 设置在指定组件的下边
android:layout_below="@id/tv1"
* 设置右对齐父元素
android:layout_alignParentRight="true"
* 设置与指定组件右对齐
android:layout_alignRight="@id/tv1"
###线性布局
#####LinearLayout
* 指定各个节点的排列方向
android:orientation="horizontal"
* 设置右对齐
android:layout_gravity="right"
* 当竖直布局时,只能左右对齐和水平居中,顶部底部对齐竖直居中无效
* 当水平布局时,只能顶部底部对齐和竖直居中
* 使用match_parent时注意不要把其他组件顶出去
* 线性布局非常重要的一个属性:权重
android:layout_weight="1"
* 权重设置的是按比例分配剩余的空间
###帧布局
#####FrameLayout
* 默认组件都是左对齐和顶部对齐,每个组件相当于一个div
* 可以更改对齐方式
android:layout_gravity="bottom"
* 不能相对于其他组件布局
###表格布局
#####TableLayout
* 每个<TableRow/>节点是一行,它的每个子节点是一列
* 表格布局中的节点可以不设置宽高,因为设置了也无效
* 根节点<TableLayout/>的子节点宽为匹配父元素,高为包裹内容
* <TableRow/>节点的子节点宽为包裹内容,高为包裹内容
* 以上默认属性无法修改
* 根节点中可以设置以下属性,表示让第1列拉伸填满屏幕宽度的剩余空间
android:stretchColumns="1"
###绝对布局
#####AbsoluteLayout
* 直接指定组件的x、y坐标
android:layout_x="144dp"
android:layout_y="154dp"
---
#logcat
* 日志信息总共分为5个等级
* verbose
* debug
* info
* warn
* error
* 定义过滤器方便查看
* System.out.print输出的日志级别是info,tag是System.out
* Android提供的日志输出api
Log.v(TAG, "加油吧,童鞋们");
Log.d(TAG, "加油吧,童鞋们");
Log.i(TAG, "加油吧,童鞋们");
Log.w(TAG, "加油吧,童鞋们");
Log.e(TAG, "加油吧,童鞋们");
----
#文件读写操作
* Ram内存:运行内存,相当于电脑的内存
* Rom内存:内部存储空间,相当于电脑的硬盘
* sd卡:外部存储空间,相当于电脑的移动硬盘
###在内部存储空间中读写文件
>小案例:用户输入账号密码,勾选“记住账号密码”,点击登录按钮,登录的同时持久化保存账号和密码
#####1. 定义布局
#####2. 完成按钮的点击事件
* 弹土司提示用户登录成功
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
#####3. 拿到用户输入的数据
* 判断用户是否勾选保存账号密码
CheckBox cb = (CheckBox) findViewById(R.id.cb);
if(cb.isChecked()){
}
#####4. 开启io流把文件写入内部存储
* 直接开启文件输出流写数据
//持久化保存数据
File file = new File("data/data/com.sg31.rwinrom/info.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write((name + "##" + pass).getBytes());
fos.close();
* 读取数据前先检测文件是否存在
if(file.exists())
* 读取保存的数据,也是直接开文件输入流读取
File file = new File("data/data/com.sg31.rwinrom/info.txt");
FileInputStream fis = new FileInputStream(file);
//把字节流转换成字符流
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String text = br.readLine();
String[] s = text.split("##");
* 读取到数据之后,回显至输入框
et_name.setText(s[0]);
et_pass.setText(s[1]);
* 应用只能在自己的包名目录下创建文件,不能到别人家去创建
###直接复制项目
* 需要改动的地方:
* 项目名字
* 应用包名
* R文件重新导包
###使用路径api读写文件
* getFilesDir()得到的file对象的路径是data/data/com.sg31.rwinrom2/files
* 存放在这个路径下的文件,只要你不删,它就一直在
* getCacheDir()得到的file对象的路径是data/data/com.sg31.rwinrom2/cache
* 存放在这个路径下的文件,当内存不足时,有可能被删除
* 系统管理应用界面的清除缓存,会清除cache文件夹下的东西,清除数据,会清除整个包名目录下的东西
-----
#在外部存储读写数据
###sd卡的路径
* sdcard:2.3之前的sd卡路径
* mnt/sdcard:4.3之前的sd卡路径
* storage/sdcard:4.3之后的sd卡路径
* 最简单的打开sd卡的方式
File file = new File("sdcard/info.txt");
* 写sd卡需要权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
* 读sd卡,在4.0之前不需要权限,4.0之后可以设置为需要
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
* 使用api获得sd卡的真实路径,部分手机品牌会更改sd卡的路径
Environment.getExternalStorageDirectory()
* 判断sd卡是否准备就绪
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
-----
#查看源代码查找获取sd卡剩余容量的代码
* 导入Settings项目
* 查找“可用空间”得到
<string name="memory_available" msgid="418542433817289474">"可用空间"</string>
* 查找"memory_available",得到
<Preference android:key="memory_sd_avail"
style="?android:attr/preferenceInformationStyle"
android:title="@string/memory_available"
android:summary="00"/>
* 查找"memory_sd_avail",得到
//这个字符串就是sd卡剩余容量
formatSize(availableBlocks * blockSize) + readOnly
//这两个参数相乘,得到sd卡以字节为单位的剩余容量
availableBlocks * blockSize
* 存储设备会被分为若干个区块,每个区块有固定的大小
* 区块大小 * 区块数量 等于 存储设备的总大小
-------
#Linux文件的访问权限
* 在Android中,每一个应用是一个独立的用户
* drwxrwxrwx
* 第1位:d表示文件夹,-表示文件
* 第2-4位:rwx,表示这个文件的拥有者用户(owner)对该文件的权限
* r:读
* w:写
* x:执行
* 第5-7位:rwx,表示跟文件拥有者用户同组的用户(grouper)对该文件的权限
* 第8-10位:rwx,表示其他用户组的用户(other)对该文件的权限
----
#openFileOutput的四种模式
* MODE_PRIVATE:-rw-rw----
* MODE_APPEND:-rw-rw----
* MODE_WORLD_WRITEABLE:-rw-rw--w-
* MODE_WORLD_READABLE:-rw-rw-r--
----
#SharedPreference
>用SharedPreference存储账号密码
* 往SharedPreference里写数据
//拿到一个SharedPreference对象
SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
//拿到编辑器
Editor ed = sp.edit();
//写数据
ed.putBoolean("name", name);
ed.commit();
* 从SharedPreference里取数据
SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
//从SharedPreference里取数据
String name = sp.getBoolean("name", "");
---
#生成XML文件备份短信
* 创建几个虚拟的短信对象,存在list中
* 备份数据通常都是备份至sd卡
###使用StringBuffer拼接字符串
* 把整个xml文件所有节点append到sb对象里
sb.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>");
//添加smss的开始节点
sb.append("<smss>");
.......
* 把sb写到输出流中
fos.write(sb.toString().getBytes());
###使用XMl序列化器生成xml文件
* 得到xml序列化器对象
XmlSerializer xs = Xml.newSerializer();
* 给序列化器设置输出流
File file = new File(Environment.getExternalStorageDirectory(), "backupsms.xml");
FileOutputStream fos = new FileOutputStream(file);
//给序列化器指定好输出流
xs.setOutput(fos, "utf-8");
* 开始生成xml文件
xs.startDocument("utf-8", true);
xs.startTag(null, "smss");
......
---
#pull解析xml文件
* 先自己写一个xml文件,存一些天气信息
###拿到xml文件
InputStream is = getClassLoader().getResourceAsStream("weather.xml");
###拿到pull解析器
XmlPullParser xp = Xml.newPullParser();
###开始解析
* 拿到指针所在当前节点的事件类型
int type = xp.getEventType();
* 事件类型主要有五种
* START_DOCUMENT:xml头的事件类型
* END_DOCUMENT:xml尾的事件类型
* START_TAG:开始节点的事件类型
* END_TAG:结束节点的事件类型
* TEXT:文本节点的事件类型
* 如果获取到的事件类型不是END_DOCUMENT,就说明解析还没有完成,如果是,解析完成,while循环结束
while(type != XmlPullParser.END_DOCUMENT)
* 当我们解析到不同节点时,需要进行不同的操作,所以判断一下当前节点的name
* 当解析到weather的开始节点时,new出list
* 当解析到city的开始节点时,创建city对象,创建对象是为了更方便的保存即将解析到的文本
* 当解析到name开始节点时,获取下一个节点的文本内容,temp、pm也是一样
case XmlPullParser.START_TAG:
//获取当前节点的名字
if("weather".equals(xp.getName())){
citys = new ArrayList<City>();
}
else if("city".equals(xp.getName())){
city = new City();
}
else if("name".equals(xp.getName())){
//获取当前节点的下一个节点的文本
String name = xp.nextText();
city.setName(name);
}
else if("temp".equals(xp.getName())){
String temp = xp.nextText();
city.setTemp(temp);
}
else if("pm".equals(xp.getName())){
String pm = xp.nextText();
city.setPm(pm);
}
break;
* 当解析到city的结束节点时,说明city的三个子节点已经全部解析完了,把city对象添加至list
case XmlPullParser.END_TAG:
if("city".equals(xp.getName())){
citys.add(city);
}
#测试
* 黑盒测试
* 测试逻辑业务
* 白盒测试
* 测试逻辑方法
* 根据测试粒度
* 方法测试:function test
* 单元测试:unit test
* 集成测试:integration test
* 系统测试:system test
* 根据测试暴力程度
* 冒烟测试:smoke test
* 压力测试:pressure test
------
#单元测试junit
* 定义一个类继承AndroidTestCase,在类中定义方法,即可测试该方法
* 在指定指令集时,targetPackage指定你要测试的应用的包名
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.sg31.junit"
></instrumentation>
* 定义使用的类库
<uses-library android:name="android.test.runner"></uses-library>
* 断言的作用,检测运行结果和预期是否一致
* 如果应用出现异常,会抛给测试框架
-----
#SQLite数据库
* 轻量级关系型数据库
* 创建数据库需要使用的api:SQLiteOpenHelper
* 必须定义一个构造方法:
//arg1:数据库文件的名字
//arg2:游标工厂
//arg3:数据库版本
public MyOpenHelper(Context context, String name, CursorFactory factory, int version){}
* 数据库被创建时会调用:onCreate方法
* 数据库升级时会调用:onUpgrade方法
###创建数据库
//创建OpenHelper对象
MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1);
//获得数据库对象,如果数据库不存在,先创建数据库,后获得,如果存在,则直接获得
SQLiteDatabase db = oh.getWritableDatabase();
* getWritableDatabase():打开可读写的数据库
* getReadableDatabase():在磁盘空间不足时打开只读数据库,否则打开可读写数据库
* 在创建数据库时创建表
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("create table person (_id integer primary key autoincrement, name char(10), phone char(20), money integer(20))");
}
---
#数据库的增删改查
###SQL语句
* insert into person (name, phone, money) values ('张三', '159874611', 2000);
* delete from person where name = '李四' and _id = 4;
* update person set money = 6000 where name = '李四';
* select name, phone from person where name = '张三';
###执行SQL语句实现增删改查
//插入
db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"张三", 15987461, 75000});
//查找
Cursor cs = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"张三"});
* 测试方法执行前会调用此方法
protected void setUp() throws Exception {
super.setUp();
// 获取虚拟上下文对象
oh = new MyOpenHelper(getContext(), "people.db", null, 1);
}
###使用api实现增删改查
* 插入
//以键值对的形式保存要存入数据库的数据
ContentValues cv = new ContentValues();
cv.put("name", "刘能");
cv.put("phone", 1651646);
cv.put("money", 3500);
//返回值是改行的主键,如果出错返回-1
long i = db.insert("person", null, cv);
* 删除
//返回值是删除的行数
int i = db.delete("person", "_id = ? and name = ?", new String[]{"1", "张三"});
* 修改
ContentValues cv = new ContentValues();
cv.put("money", 25000);
int i = db.update("person", cv, "name = ?", new String[]{"赵四"});
* 查询
//arg1:要查询的字段
//arg2:查询条件
//arg3:填充查询条件的占位符
Cursor cs = db.query("person", new String[]{"name", "money"}, "name = ?", new String[]{"张三"}, null, null, null);
while(cs.moveToNext()){
// 获取指定列的索引值
String name = cs.getString(cs.getColumnIndex("name"));
String money = cs.getString(cs.getColumnIndex("money"));
System.out.println(name + ";" + money);
}
###事务
* 保证多条SQL语句要么同时成功,要么同时失败
* 最常见案例:银行转账
* 事务api
try {
//开启事务
db.beginTransaction();
...........
//设置事务执行成功
db.setTransactionSuccessful();
} finally{
//关闭事务
//如果此时已经设置事务执行成功,则sql语句生效,否则不生效
db.endTransaction();
}
---
#把数据库的数据显示至屏幕
1. 任意插入一些数据
* 定义业务bean:Person.java
* 读取数据库的所有数据
Cursor cs = db.query("person", null, null, null, null, null, null);
while(cs.moveToNext()){
String name = cs.getString(cs.getColumnIndex("name"));
String phone = cs.getString(cs.getColumnIndex("phone"));
String money = cs.getString(cs.getColumnIndex("money"));
//把读到的数据封装至Person对象
Person p = new Person(name, phone, money);
//把person对象保存至集合中
people.add(p);
}
* 把集合中的数据显示至屏幕
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
for(Person p : people){
//创建TextView,每条数据用一个文本框显示
TextView tv = new TextView(this);
tv.setText(p.toString());
//把文本框设置为ll的子节点
ll.addView(tv);
}
* 分页查询
Cursor cs = db.query("person", null, null, null, null, null, null, "0, 10");
---
#ListView
* 就是用来显示一行一行的条目的
* MVC结构
* M:model模型层,要显示的数据 ————people集合
* V:view视图层,用户看到的界面 ————ListView
* c:control控制层,操作数据如何显示 ————adapter对象
* 每一个条目都是一个View对象
###BaseAdapter
* 必须实现的两个方法
* 第一个
//系统调用此方法,用来获知模型层有多少条数据
@Override
public int getCount() {
return people.size();
}
* 第二个
//系统调用此方法,获取要显示至ListView的View对象
//position:是return的View对象所对应的数据在集合中的位置
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView方法调用" + position);
TextView tv = new TextView(MainActivity.this);
//拿到集合中的元素
Person p = people.get(position);
tv.setText(p.toString());
//把TextView的对象返回出去,它会变成ListView的条目
return tv;
}
* 屏幕上能显示多少个条目,getView方法就会被调用多少次,屏幕向下滑动时,getView会继续被调用,创建更多的View对象显示至屏幕
###条目的缓存
* 当条目划出屏幕时,系统会把该条目缓存至内存,当该条目再次进入屏幕,系统在重新调用getView时会把缓存的条目作为convertView参数传入,但是传入的条目不一定是之前被缓存的该条目,即系统有可能在调用getView方法获取第一个条目时,传入任意一个条目的缓存
---
#对话框
###确定取消对话框
* 创建对话框构建器对象,类似工厂模式
*
AlertDialog.Builder builder = new Builder(this);
* 设置标题和正文
*
builder.setTitle("警告");
builder.setMessage("若练此功,必先自宫");
* 设置确定和取消按钮
builder.setPositiveButton("现在自宫", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, "恭喜你自宫成功,现在程序退出", 0).show();
}
});
builder.setNegativeButton("下次再说", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, "若不自宫,一定不成功", 0).show();
}
});
* 使用构建器创建出对话框对象
AlertDialog ad = builder.create();
ad.show();
###单选对话框
AlertDialog.Builder builder = new Builder(this);
builder.setTitle("选择你的性别");
* 定义单选选项
*
final String[] items = new String[]{
"男", "女", "其他"
};
//-1表示没有默认选择
//点击侦听的导包要注意别导错
builder.setSingleChoiceItems(items, -1, new OnClickListener() {
//which表示点击的是哪一个选项
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您选择了" + items[which], 0).show();
//对话框消失
dialog.dismiss();
}
});
builder.show();
###多选对话框
AlertDialog.Builder builder = new Builder(this);
builder.setTitle("请选择你认为最帅的人");
* 定义多选的选项,因为可以多选,所以需要一个boolean数组来记录哪些选项被选了
*
final String[] items = new String[]{
"赵帅哥",
"赵师哥",
"赵老师",
"侃哥"
};
//true表示对应位置的选项被选了
final boolean[] checkedItems = new boolean[]{
true,
false,
false,
false,
};
builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() {
//点击某个选项,如果该选项之前没被选择,那么此时isChecked的值为true
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
checkedItems[which] = isChecked;
}
});
builder.setPositiveButton("确定", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
StringBuffer sb = new StringBuffer();
for(int i = 0;i < items.length; i++){
sb.append(checkedItems[i] ? items[i] + " " : "");
}
Toast.makeText(MainActivity.this, sb.toString(), 0).show();
}
});
builder.show();
#测试
###按岗位划分
* 黑盒测试:测试逻辑业务
* 白盒测试:测试逻辑方法
###按测试粒度分
* 方法测试:function test
* 单元测试:unit test
* 集成测试:integration test
* 系统测试:system test
###按测试的暴力程度分
* 冒烟测试:smoke test
* 压力测试:pressure test
---
#单元测试
* junit
* 在清单文件中指定指令集
<instrumentation
android:name="android.test.InstrumentationTestRunner"
//指定该测试框架要测试哪一个项目
android:targetPackage="com.sg31.junit"
></instrumentation>
* 定义使用的类库
<uses-library android:name="android.test.runner"/>
---
#SQLite
###事务
> 保证所有sql语句要么一起成功,要么一起失败
---
#ListView
###MVC架构
* M:模型层 ---- javaBean ---- personList
* V:视图层 ---- jsp ---- ListView
* C:控制层 ---- servlet ---- Adapter
###Adapter
* ListView的每个条目都是一个View对象
#网络请求
###主线程阻塞
* UI停止刷新,应用无法响应用户操作
* 耗时操作不应该在主线程进行
* ANR
* application not responding
* 应用无响应异常
* 主线程阻塞时间过长,就会抛出ANR
* 主线程又称UI线程,因为只有在主线程中,才能刷新UI
###消息队列机制
* 主线程创建时,系统会同时创建消息队列对象(MessageQueue)和消息轮询器对象(Looper)
* 轮询器的作用,就是不停的检测消息队列中是否有消息(Message)
* 消息队列一旦有消息,轮询器会把消息对象传给消息处理器(Handler),处理器会调用handleMessage方法来处理这条消息,handleMessage方法运行在主线程中,所以可以刷新ui
* 总结:只要消息队列有消息,handleMessage方法就会调用
* 子线程如果需要刷新ui,只需要往消息队列中发一条消息,触发handleMessage方法即可
* 子线程使用处理器对象的sendMessage方法发送消息
#网络图片查看器
* 确定图片的网址
* 发送http请求
URL url = new URL(address);
//获取连接对象,并没有建立连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置连接和读取超时
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//设置请求方法,注意必须大写
conn.setRequestMethod("GET");
//建立连接,发送get请求
//conn.connect();
//建立连接,然后获取响应吗,200说明请求成功
conn.getResponseCode();
* 服务器的图片是以流的形式返回给浏览器的
//拿到服务器返回的输入流
InputStream is = conn.getInputStream();
//把流里的数据读取出来,并构造成图片
Bitmap bm = BitmapFactory.decodeStream(is);
* 把图片设置为ImageView的显示内容
ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setImageBitmap(bm);
* 添加权限
###主线程不能被阻塞
* 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
* 主线程阻塞时间过长,系统会抛出ANR异常
* ANR:Application Not Response;应用无响应
* 任何耗时操作都不可以写在主线程
* 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程
###只有主线程能刷新ui
* 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
* 如果需要在子线程中刷新ui,使用消息队列机制
#####消息队列
* Looper一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler对象,Handler会调用自己的handleMessage方法来处理这条消息
* handleMessage方法运行在主线程
* 主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建
//消息队列
Handler handler = new Handler(){
//主线程中有一个消息轮询器looper,不断检测消息队列中是否有新消息,如果发现有新消息,自动调用此方法,注意此方法是在主线程中运行的
public void handleMessage(android.os.Message msg) {
}
};
* 在子线程中往消息队列里发消息
//创建消息对象
Message msg = new Message();
//消息的obj属性可以赋值任何对象,通过这个属性可以携带数据
msg.obj = bm;
//what属性相当于一个标签,用于区分出不同的消息,从而运行不能的代码
msg.what = 1;
//发送消息
handler.sendMessage(msg);
* 通过switch语句区分不同的消息
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
//如果是1,说明属于请求成功的消息
case 1:
ImageView iv = (ImageView) findViewById(R.id.iv);
Bitmap bm = (Bitmap) msg.obj;
iv.setImageBitmap(bm);
break;
case 2:
Toast.makeText(MainActivity.this, "请求失败", 0).show();
break;
}
}
###加入缓存图片的功能
* 把服务器返回的流里的数据读取出来,然后通过文件输入流写至本地文件
//1.拿到服务器返回的输入流
InputStream is = conn.getInputStream();
//2.把流里的数据读取出来,并构造成图片
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024];
int len = 0;
while((len = is.read(b)) != -1){
fos.write(b, 0, len);
}
* 创建bitmap对象的代码改成
Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
* 每次发送请求前检测一下在缓存中是否存在同名图片,如果存在,则读取缓存
---
#获取开源代码的网站
* code.google.com
* github.com
* 在github搜索smart-image-view
* 下载开源项目smart-image-view
* 使用自定义组件时,标签名字要写包名
<com.loopj.android.image.SmartImageView/>
* SmartImageView的使用
SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
siv.setImageUrl("http://192.168.1.102:8080/dd.jpg");
---
#Html源文件查看器
* 发送GET请求
URL url = new URL(path);
//获取连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置连接属性
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//建立连接,获取响应吗
if(conn.getResponseCode() == 200){
}
* 获取服务器返回的流,从流中把html源码读取出来
byte[] b = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = is.read(b)) != -1){
//把读到的字节先写入字节数组输出流中存起来
bos.write(b, 0, len);
}
//把字节数组输出流中的内容转换成字符串
//默认使用utf-8
text = new String(bos.toByteArray());
###乱码的处理
* 乱码的出现是因为服务器和客户端码表不一致导致
//手动指定码表
text = new String(bos.toByteArray(), "gb2312");
---
#提交数据
###GET方式提交数据
* get方式提交的数据是直接拼接在url的末尾
final String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;
* 发送get请求,代码和之前一样
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
if(conn.getResponseCode() == 200){
}
* 浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码
String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
###POST方式提交数据
* post提交数据是用流写给服务器的
* 协议头中多了两个属性
* Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
* Content-Length: 32,描述提交的数据的长度
//给请求头添加post多出来的两个属性
String data = "name=" + URLEncoder.encode(name) + "&pass=" + pass;
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", data.length() + "");
* 设置允许打开post请求的流
conn.setDoOutput(true);
* 获取连接对象的输出流,往流里写要提交给服务器的数据
OutputStream os = conn.getOutputStream();
os.write(data.getBytes());
#HttpClient
###发送get请求
* 创建一个客户端对象
HttpClient client = new DefaultHttpClient();
* 创建一个get请求对象
HttpGet hg = new HttpGet(path);
* 发送get请求,建立连接,返回响应头对象
HttpResponse hr = hc.execute(hg);
* 获取状态行对象,获取状态码,如果为200则说明请求成功
if(hr.getStatusLine().getStatusCode() == 200){
//拿到服务器返回的输入流
InputStream is = hr.getEntity().getContent();
String text = Utils.getTextFromStream(is);
}
###发送post请求
//创建一个客户端对象
HttpClient client = new DefaultHttpClient();
//创建一个post请求对象
HttpPost hp = new HttpPost(path);
* 往post对象里放入要提交给服务器的数据
//要提交的数据以键值对的形式存在BasicNameValuePair对象中
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
BasicNameValuePair bnvp = new BasicNameValuePair("name", name);
BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);
parameters.add(bnvp);
parameters.add(bnvp2);
//创建实体对象,指定进行URL编码的码表
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
//为post请求设置实体
hp.setEntity(entity);
---
#异步HttpClient框架
###发送get请求
//创建异步的httpclient对象
AsyncHttpClient ahc = new AsyncHttpClient();
//发送get请求
ahc.get(path, new MyHandler());
* 注意AsyncHttpResponseHandler两个方法的调用时机
class MyHandler extends AsyncHttpResponseHandler{
//http请求成功,返回码为200,系统回调此方法
@Override
public void onSuccess(int statusCode, Header[] headers,
//responseBody的内容就是服务器返回的数据
byte[] responseBody) {
Toast.makeText(MainActivity.this, new String(responseBody), 0).show();
}
//http请求失败,返回码不为200,系统回调此方法
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
Toast.makeText(MainActivity.this, "返回码不为200", 0).show();
}
}
###发送post请求
* 使用RequestParams对象封装要携带的数据
//创建异步httpclient对象
AsyncHttpClient ahc = new AsyncHttpClient();
//创建RequestParams封装要携带的数据
RequestParams rp = new RequestParams();
rp.add("name", name);
rp.add("pass", pass);
//发送post请求
ahc.post(path, rp, new MyHandler());
---
#多线程下载
>原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源
###确定每条线程下载多少数据
* 发送http请求至下载地址
String path = "http://192.168.1.102:8080/editplus.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
* 获取文件总长度,然后创建长度一致的临时文件
if(conn.getResponseCode() == 200){
//获得服务器流中数据的长度
int length = conn.getContentLength();
//创建一个临时文件存储下载的数据
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
//设置临时文件的大小
raf.setLength(length);
raf.close();
* 确定线程下载多少数据
//计算每个线程下载多少数据
int blockSize = length / THREAD_COUNT;
###计算每条线程下载数据的开始位置和结束位置
for(int id = 1; id <= 3; id++){
//计算每个线程下载数据的开始位置和结束位置
int startIndex = (id - 1) * blockSize;
int endIndex = id * blockSize - 1;
if(id == THREAD_COUNT){
endIndex = length;
}
//开启线程,按照计算出来的开始结束位置开始下载数据
new DownLoadThread(startIndex, endIndex, id).start();
}
###再次发送请求至下载地址,请求开始位置至结束位置的数据
String path = "http://192.168.1.102:8080/editplus.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
//向服务器请求部分数据
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.connect();
* 下载请求到的数据,存放至临时文件中
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
//指定从哪个位置开始存放数据
raf.seek(startIndex);
byte[] b = new byte[1024];
int len;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
}
raf.close();
}
---
#带断点续传的多线程下载
* 定义一个int变量记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件
//用来记录当前线程总的下载长度
int total = 0;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
//每次下载都把新的下载位置写入缓存文本文件
RandomAccessFile raf2 = new RandomAccessFile(threadId + ".txt", "rwd");
raf2.write((startIndex + total + "").getBytes());
raf2.close();
}
* 下次下载开始时,先读取缓存文件中的值,得到的值就是该线程新的开始位置
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String text = br.readLine();
int newStartIndex = Integer.parseInt(text);
//把读到的值作为新的开始位置
startIndex = newStartIndex;
fis.close();
* 三条线程都下载完毕之后,删除缓存文件
RUNNING_THREAD--;
if(RUNNING_THREAD == 0){
for(int i = 0; i <= 3; i++){
File f = new File(i + ".txt");
f.delete();
}
}
---
#手机版的断点续传多线程下载器
* 把刚才的代码直接粘贴过来就能用,记得在访问文件时的路径要改成Android的目录,添加访问网络和外部存储的路径
###用进度条显示下载进度
* 拿到下载文件总长度时,设置进度条的最大值
//设置进度条的最大值
pb.setMax(length);
* 进度条需要显示三条线程的整体下载进度,所以三条线程每下载一次,就要把新下载的长度加入进度条
* 定义一个int全局变量,记录三条线程的总下载长度
int progress;
* 刷新进度条
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
//把当前线程本次下载的长度加到进度条里
progress += len;
pb.setProgress(progress);
* 每次断点下载时,从新的开始位置开始下载,进度条也要从新的位置开始显示,在读取缓存文件获取新的下载开始位置时,也要处理进度条进度
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String text = br.readLine();
int newStartIndex = Integer.parseInt(text);
//新开始位置减去原本的开始位置,得到已经下载的数据长度
int alreadyDownload = newStartIndex - startIndex;
//把已经下载的长度设置入进度条
progress += alreadyDownload;
###添加文本框显示百分比进度
tv.setText(progress * 100 / pb.getMax() + "%");
---
#HttpUtils的使用
>HttpUtils本身就支持多线程断点续传,使用起来非常的方便
* 创建HttpUtils对象
HttpUtils http = new HttpUtils();
* 下载文件
http.download(url, //下载请求的网址
target, //下载的数据保存路径和文件名
true, //是否开启断点续传
true, //如果服务器响应头中包含了文件名,那么下载完毕后自动重命名
new RequestCallBack<File>() {//侦听下载状态
//下载成功此方法调用
@Override
public void onSuccess(ResponseInfo<File> arg0) {
tv.setText("下载成功" + arg0.result.getPath());
}
//下载失败此方法调用,比如文件已经下载、没有网络权限、文件访问不到,方法传入一个字符串参数告知失败原因
@Override
public void onFailure(HttpException arg0, String arg1) {
tv.setText("下载失败" + arg1);
}
//在下载过程中不断的调用,用于刷新进度条
@Override
public void onLoading(long total, long current, boolean isUploading) {
super.onLoading(total, current, isUploading);
//设置进度条总长度
pb.setMax((int) total);
//设置进度条当前进度
pb.setProgress((int) current);
tv_progress.setText(current * 100 / total + "%");
}
});
#Android四大组件
* Activity
* BroadCastReceiver
* Service
* ContentProvider
#创建第二个activity
* 新创建的activity,必须在清单文件中做配置,否则系统找不到,在显示时会直接报错
<activity android:name="com.itheima.createactivity.SecondActivity"></activity>
* 只要有以下代码,那么就是入口activity,就会生成快捷图标
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
* 如果Activity所在的包跟应用包名同名,那么可以省略不写
1. 创建class类继承Activity
2. 创建布局文件,作为Activity的显示内容
3. 在清单文件中注册Activity
#Activity的跳转
###隐式跳转
* 一个Activity如果需要隐式跳转,那么在清单文件中必须添加以下子节点
<intent-filter >
<action android:name="com.itheima.sa"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
* action节点的name是自己定义的,定义好之后,这个name的值就会成为这个activity动作,在隐式启动Activity时,意图中设置的action必须跟"com.itheima.sa"是完全匹配的
###应用场景
* 显示意图:启动同一个应用中的Activity
* 隐式意图:启动不同应用中的Activity
* 再启动效率上,隐式远远低于显式
* 如果系统中有多个Activity与意图设置的Action匹配,那么在启动Activity时,会弹出一个对话框,里面包含所有匹配的Activity
----
#Activity生命周期
* oncreate:Activity对象创建完毕,但此时不可见
* onstart:Activity在屏幕可见,但是此时没有焦点
* onResume:Activity在屏幕可见,并且获得焦点
* onPause:Activity此时在屏幕依然可见,但是已经没有焦点
* onStop:Activity已经不可见了,但此时Activity的对象还在内存中
* onDestroy:Activity对象被销毁
###内存不足
* 内存不足时,系统会优先杀死后台Activity所在的进程,都杀光了,如果内存还是不足,那么就会杀死暂停状态的Activity所在的进程,如果还是不够,有可能杀死前台进程
* 如果有多个后台进程,在选择杀死的目标时,采用最近最少使用算法(LRU)
---
###Activity任务栈
* 应用运行过程中,内存中可能会打开多个Activity,那么所有打开的Activity都会被保存在Activity任务栈
* 栈:后进先出,最先进栈,就会最后出栈
###Activity的启动模式
* 标准模式:默认就是这个模式
* singleTop:如果目标Activity不在栈顶,那么就会启动一个新的Activity,如果已经在栈顶了,那么就不会再启动了
* singleTask:如果栈中没有该Activity,那么启动时就会创建一个该Activity,如果栈中已经有该Activity的实例存在了,那么在启动时,就会杀死在栈中处于该Activity上方的所有Activity全部杀死,那么此时该Activity就成为了栈顶Activity。
* singleTask的作用:保证整个栈中只有一个该Activity的实例
* singleInstance:设为此模式的Activity会有一个自己独立的任务栈,该Activity的实例只会创建一个,保存在独立的任务栈中
* singleInstance的作用:保证整个系统的内存中只有一个该Activity的实例
---
#横竖屏的切换
* Activity在横竖屏切换时会销毁重建,目的就是为了读取新的布局文件
* 写死方向,不允许切换
android:screenOrientation="portrait"
android:screenOrientation="landscape"
* 配置Activity时添加以下属性,横竖屏切换时就不会销毁重建
android:configChanges="orientation|keyboardHidden|screenSize"
---
#Activity返回时传递数据
* 请求码:用来区分数据来自于哪一个Activity
* 结果码:用来区分,返回的数据时属于什么类型
#创建第二个Activity
* 需要在清单文件中为其配置一个activity标签
* 标签中如果带有这个子节点,则会在系统中多创建一个快捷图标
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
* 一个应用程序可以在桌面创建多个快捷图标。
* activity的名称、图标可以和应用程序的名称、图标不相同
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
---
#Activity的跳转
>Activity的跳转需要创建Intent对象,通过设置intent对象的参数指定要跳转Activity
>
>通过设置Activity的包名和类名实现跳转,称为显式意图
>
>通过指定动作实现跳转,称为隐式意图
###显式意图
* 跳转至同一项目下的另一个Activity,直接指定该Activity的字节码即可
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
startActivity(intent);
* 跳转至其他应用中的Activity,需要指定该应用的包名和该Activity的类名
Intent intent = new Intent();
//启动系统自带的拨号器应用
intent.setClassName("com.android.dialer", "com.android.dialer.DialtactsActivity");
startActivity(intent);
###隐式意图
* 隐式意图跳转至指定Activity
Intent intent = new Intent();
//启动系统自带的拨号器应用
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);
* 要让一个Activity可以被隐式启动,需要在清单文件的activity节点中设置intent-filter子节点
<intent-filter >
<action android:name="com.itheima.second"/>
<data android:scheme="asd" android:mimeType="aa/bb"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
* action 指定动作(可以自定义,可以使用系统自带的)
* data 指定数据(操作什么内容)
* category 类别 (默认类别,机顶盒,车载电脑)
* 隐式意图启动Activity,需要为intent设置以上三个属性,且值必须与该Activity在清单文件中对三个属性的定义匹配
* intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可
#####获取通过setData传递的数据
//获取启动此Activity的intent对象
Intent intent = getIntent();
Uri uri = intent.getData();
###显式意图和隐式意图的应用场景
* 显式意图用于启动同一应用中的Activity
* 隐式意图用于启动不同应用中的Activity
* 如果系统中存在多个Activity的intent-filter同时与你的intent匹配,那么系统会显示一个对话框,列出所有匹配的Activity,由用户选择启动哪一个
---
#Activity跳转时的数据传递
* Activity通过Intent启动时,可以通过Intent对象携带数据到目标Activity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("maleName", maleName);
intent.putExtra("femaleName", femaleName);
startActivity(intent);
* 在目标Activity中取出数据
Intent intent = getIntent();
String maleName = intent.getStringExtra("maleName");
String femaleName = intent.getStringExtra("femaleName");
---
#Activity生命周期
###void onCreate()
* Activity已经被创建完毕
###void onStart()
* Activity已经显示在屏幕,但没有得到焦点
###void onResume()
* Activity得到焦点,可以与用户交互
###void onPause()
* Activity失去焦点,无法再与用户交互,但依然可见
###void onStop()
* Activity不可见,进入后台
###void onDestroy()
* Activity被销毁
###void onRestart()
* Activity从不可见变成可见时会执行此方法
###使用场景
* Activity创建时需要初始化资源,销毁时需要释放资源;或者播放器应用,在界面进入后台时需要自动暂停
###完整生命周期(entire lifetime)
onCreate-->onStart-->onResume-->onPause-->onStop-->onDestory
###可视生命周期(visible lifetime)
onStart-->onResume-->onPause-->onStop
###前台生命周期(foreground lifetime)
onResume-->onPause
---
#Activity的四种启动模式
>每个应用会有一个Activity任务栈,存放已启动的Activity
>
>Activity的启动模式,修改任务栈的排列情况
* standard 标准启动模式
* singleTop 单一顶部模式
* 如果任务栈的栈顶存在这个要开启的activity,不会重新的创建activity,而是复用已经存在的activity。保证栈顶如果存在,不会重复创建。
* 应用场景:浏览器的书签
* singeTask 单一任务栈,在当前任务栈里面只能有一个实例存在
* 当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。保证整个任务栈里面只有一个实例存在
* 应用场景:浏览器的activity
* 如果一个activity的创建需要占用大量的系统资源(cpu,内存)一般配置这个activity为singletask的启动模式。webkit内核 c代码
* singleInstance启动模式非常特殊, activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在
* 如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance
* 应用场景: 电话拨打界面
---
##横竖屏切换的生命周期
>默认情况下 ,横竖屏切换, 销毁当前的activity,重新创建一个新的activity
>
> 快捷键ctrl+F11
在一些特殊的应用程序常见下,比如游戏,不希望横竖屏切换activity被销毁重新创建
需求:禁用掉横竖屏切换的生命周期
1. 横竖屏写死
android:screenOrientation="landscape"
android:screenOrientation="portrait"
2. 让系统的环境 不再去敏感横竖屏的切换。
android:configChanges="orientation|screenSize|keyboardHidden"
---
#掌握开启activity获取返回值
###从A界面打开B界面, B界面关闭的时候,返回一个数据给A界面
步骤:
1. 开启activity并且获取返回值
startActivityForResult(intent, 0);
2. 在新开启的界面里面实现设置数据的逻辑
Intent data = new Intent();
data.putExtra("phone", phone);
//设置一个结果数据,数据会返回给调用者
setResult(0, data);
finish();//关闭掉当前的activity,才会返回数据
3. 在开启者activity里面实现方法
onActivityResult(int requestCode, int resultCode, Intent data)
通过data获取返回的数据
4. 根据请求码和结果码确定业务逻辑
#广播
* 广播的概念
* 现实:电台通过发送广播发布消息,买个收音机,就能收听
* Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。
Android系统在运行的过程中,会产生很多事件,比如开机、电量改变、收发短信、拨打电话、屏幕解锁
---
#IP拨号器
> 原理:接收拨打电话的广播,修改广播内携带的电话号码
* 定义广播接收者接收打电话广播
public class CallReceiver extends BroadcastReceiver {
//当广播接收者接收到广播时,此方法会调用
@Override
public void onReceive(Context context, Intent intent) {
//拿到用户拨打的号码
String number = getResultData();
//修改广播内的号码
setResultData("17951" + number);
}
}
* 在清单文件中定义该广播接收者接收的广播类型
<receiver android:name="com.itheima.ipdialer.CallReceiver">
<intent-filter >
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
* 接收打电话广播需要权限
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
* 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程
---
#短信拦截器
>系统收到短信时会产生一条广播,广播中包含了短信的号码和内容
* 定义广播接收者接收短信广播
public void onReceive(Context context, Intent intent) {
//拿到广播里携带的短信内容
Bundle bundle = intent.getExtras();
Object[] objects = (Object[]) bundle.get("pdus");
for(Object ob : objects ){
//通过object对象创建一个短信对象
SmsMessage sms = SmsMessage.createFromPdu((byte[])ob);
System.out.println(sms.getMessageBody());
System.out.println(sms.getOriginatingAddress());
}
}
* 系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
* 清单文件中配置广播接收者接收的广播类型,注意要设置优先级属性,要保证优先级高于短信应用,才可以实现拦截
<receiver android:name="com.itheima.smslistener.SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
* 添加权限
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
* 4.0以后广播接收者安装以后必须手动启动一次,否则不生效
* 4.0以后广播接收者如果被手动关闭,就不会再启动了
---
#监听SD卡状态
* 清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播
<receiver android:name="com.itheima.sdcradlistener.SDCardReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<data android:scheme="file"/>
</intent-filter>
</receiver>
* 广播接收者的定义
public class SDCardReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 区分接收到的是哪个广播
String action = intent.getAction();
if(action.equals("android.intent.action.MEDIA_MOUNTED")){
System.out.println("sd卡就绪");
}
else if(action.equals("android.intent.action.MEDIA_UNMOUNTED")){
System.out.println("sd卡被移除");
}
else if(action.equals("android.intent.action.MEDIA_REMOVED")){
System.out.println("sd卡被拔出");
}
}
}
---
#勒索软件
* 接收开机广播,在广播接收者中启动勒索的Activity
* 清单文件中配置接收开机广播
<receiver android:name="com.itheima.lesuo.BootReceiver">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
* 权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
* 定义广播接收者
@Override
public void onReceive(Context context, Intent intent) {
//开机的时候就启动勒索软件
Intent it = new Intent(context, MainActivity.class);
context.startActivity(it);
}
* 以上代码还不能启动MainActivity,因为广播接收者的启动,并不会创建任务栈,那么没有任务栈,就无法启动activity
* 手动设置创建新任务栈的flag
it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
---
#监听应用的安装、卸载、更新
> 原理:应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名
* 清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播
<receiver android:name="com.itheima.app.AppReceiver">
<intent-filter >
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>
* 广播接收者的定义
public void onReceive(Context context, Intent intent) {
//区分接收到的是哪种广播
String action = intent.getAction();
//获取广播中包含的应用包名
Uri uri = intent.getData();
if(action.equals("android.intent.action.PACKAGE_ADDED")){
System.out.println(uri + "被安装了");
}
else if(action.equals("android.intent.action.PACKAGE_REPLACED")){
System.out.println(uri + "被更新了");
}
else if(action.equals("android.intent.action.PACKAGE_REMOVED")){
System.out.println(uri + "被卸载了");
}
}
---
#广播的两种类型
* 无序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到)
* 有序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,但是会按照广播接收者的优先级来决定接收的先后顺序
* 优先级的定义:-1000~1000
* 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
* abortBroadCast:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截
---
#Service
* 就是默默运行在后台的组件,可以理解为是没有前台的activity,适合用来运行不需要前台界面的代码
* 服务可以被手动关闭,不会重启,但是如果被自动关闭,内存充足就会重启
* startService启动服务的生命周期
* onCreate-onStartCommand-onDestroy
* 重复的调用startService会导致onStartCommand被重复调用
---
# 进程优先级
1. 前台进程:拥有前台activity(onResume方法被调用)
2. 可见进程:拥有可见activity(onPause方法被调用)
3. 服务进程:不到万不得已不会被回收,而且即便被回收,内存充足时也会被重启
4. 后台进程:拥有后台activity(activity的onStop方法被调用了),很容易被回收
5. 空进程:没有运行任何activity,很容易被回收
---
#电话窃听器
* 电话状态:空闲、响铃、接听
* 获取电话管理器,设置侦听
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
* 侦听对象的实现
class MyPhoneStateListener extends PhoneStateListener{
//当电话状态改变时,此方法调用
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE://空闲
if(recorder != null){
recorder.stop();
recorder.release();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK://摘机
if(recorder != null){
recorder.start();
}
break;
case TelephonyManager.CALL_STATE_RINGING://响铃
recorder = new MediaRecorder();
//设置声音来源
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置音频文件格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setOutputFile("sdcard/haha.3gp");
//设置音频文件编码
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
recorder.prepare();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
}
#广播接收者
* 现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息
* Android中:系统在运行过程中,会产生会多事件,那么某些事件产生时,比如:电量改变、收发短信、拨打电话、屏幕解锁、开机,系统会发送广播,只要应用程序接收到这条广播,就知道系统发生了相应的事件,从而执行相应的代码。使用广播接收者,就可以收听广播
###创建广播接收者
1. 定义java类继承BroadcastReceiver
2. 在清单文件中定义receiver节点,定义name属性,指定广播接收者java类的全类名
3. 在intent-filter的节点中,指定action子节点,action的值必须跟要接受的广播中的action匹配,比如,如果要接受打电话广播,
那么action的值必须指定为
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
* 因为打电话广播中所包含的action,就是"android.intent.action.NEW_OUTGOING_CALL",所以我们定义广播接收者时,
action必须与其匹配,才能收到这条广播
* 即便广播接收者所在进程已经被关闭,当系统发出的广播中的action跟该广播接收者的action匹配时,系统会启动该广播接收者所在的进程,
并把广播发给该广播接收者
###短信防火墙
* 系统发送短信广播时,是怎么把短信内容存入广播的,我们就只能怎么取出来
* 如果短信过长,那么发送时会拆分成多条短信发送,那么短信广播中就会包含多条短信
* 4.0之后,广播接收者所在进程如果从来没启动过,那么广播接收者不会生效
* 4.0之后,如果系统自动关闭广播接收者所在进程,在广播中的action跟该广播接收者的action匹配时,系统会启动该广播接收者所在的进程,但是如果是用户手动关闭该进程,
那么该进程会进入冻结状态,再也不会启动了,直到用户下一次手动启动该进程
###广播的分类
#####无序广播
* 所有与广播中的action匹配的广播接收者都可以收到这条广播,并且是没有先后顺序,视为同时收到
#####有序广播
* 所有与广播中的action匹配的广播接收者都可以收到这条广播,但是是有先后顺序的,按照广播接收者的优先级排序
---
#服务
* Service
* 运行于后台的一个组件,用来运行适合运行在后台的代码,服务是没有前台界面,可以视为没有界面的activity
###进程优先级
1. 前台进程:拥有一个正在与用户交互的Activity(onResume方法被调用)的进程
2. 可见进程:拥有一个可见但是没有焦点的Activity(onPause方法被调用)
3. 服务进程:拥有一个通过startService方法启动的服务
4. 后台进程:拥有一个不可见的Activity(onStop方法被调用)的进程
5. 空进程:没有拥有任何活动的应用组件的进程
###电话录音机
#####电话的状态
* 空闲状态
* 响铃状态
* 摘机状态
#####录音机
* 音频文件的编码和格式不是一一对应的
#服务两种启动方式
* startService:服务被启动之后,跟启动它的组件没有一毛钱关系
* bindService:跟启动它的组件同生共死
* 绑定服务和解绑服务的生命周期方法:onCreate->onBind->onUnbind->onDestroy
---
#找领导办证
* 把服务看成一个领导,服务中有一个banZheng方法,如何才能访问?
* 绑定服务时,会触发服务的onBind方法,此方法会返回一个Ibinder的对象给MainActivity,通过这个对象访问服务中的方法
* 绑定服务
Intent intent = new Intent(this, BanZhengService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
* 绑定服务时要求传入一个ServiceConnection实现类的对象
* 定义这个实现类
class MyServiceconn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
zjr = (PublicBusiness) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
* 创建实现类对象
conn = new MyServiceconn();
* 在服务中定义一个类实现Ibinder接口,以在onBind方法中返回
class ZhongJianRen extends Binder implements PublicBusiness{
public void QianXian(){
//访问服务中的banZheng方法
BanZheng();
}
public void daMaJiang(){
}
}
* 把QianXian方法抽取到接口PublicBusiness中定义
---
#两种启动方法混合使用
* 用服务实现音乐播放时,因为音乐播放必须运行在服务进程中,可是音乐服务中的方法,需要被前台Activity所调用,所以需要混合启动音乐服务
* 先start,再bind,销毁时先unbind,在stop
---
##使用服务注册广播接收者
* Android四大组件都要在清单文件中注册
* 广播接收者比较特殊,既可以在清单文件中注册,也可以直接使用代码注册
* 有的广播接收者,必须代码注册
* 电量改变
* 屏幕锁屏和解锁
* 注册广播接收者
//创建广播接收者对象
receiver = new ScreenOnOffReceiver();
//通过IntentFilter对象指定广播接收者接收什么类型的广播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
//注册广播接收者
registerReceiver(receiver, filter);
* 解除注册广播接收者
unregisterReceiver(receiver);
* 解除注册之后,广播接收者将失去作用
##本地服务:服务和启动它的组件在同一个进程
##远程服务:服务和启动它的组件不在同一个进程
* 远程服务只能隐式启动,类似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点
#AIDL
* Android interface definition language
* 安卓接口定义语言
* 作用:跨进程通信
* 应用场景:远程服务中的中间人对象,其他应用是拿不到的,那么在通过绑定服务获取中间人对象时,就无法强制转换,使用aidl,就可以在其他应用中拿到中间人类所实现的接口
##支付宝远程服务
1. 定义支付宝的服务,在服务中定义pay方法
2. 定义中间人对象,把pay方法抽取成接口
3. 把抽取出来的接口后缀名改成aidl
4. 中间人对象直接继承Stub对象
5. 注册这个支付宝服务,定义它的intent-Filter
##需要支付的应用
1. 把刚才定义好的aidl文件拷贝过来,注意aidl文件所在的包名必须跟原包名一致
2. 远程绑定支付宝的服务,通过onServiceConnected方法我们可以拿到中间人对象
3. 把中间人对象通过Stub.asInterface方法强转成定义了pay方法的接口
4. 调用中间人的pay方法
##五种前台进程
1. activity执行了onresume方法,获得焦点
2. 拥有一个跟正在与用户交互的activity绑定的服务
3. 拥有一个服务执行了startForeground()方法
4. 拥有一个正在执行onCreate()、onStart()或者onDestroy()方法中的任意一个的服务
5. 拥有一个正在执行onReceive方法的广播接收者
##两种可见进程
1. activity执行了onPause方法,失去焦点,但是可见
2. 拥有一个跟可见或前台activity绑定的服务
#服务
###开启方式
* startService
* 该方法启动的服务所在的进程属于服务进程
* Activity一旦启动服务,服务就跟Activity一毛钱关系也没有了
* bindService
* 该方法启动的服务所在进程不属于服务进程
* Activity与服务建立连接,Activity一旦死亡,服务也会死亡
* 服务的混合调用
* 先开始、再绑定,先解绑、再停止
#使用代码配置广播接收者
* 可以使用清单文件注册
* 广播一旦发出,系统就会去所有清单文件中寻找,哪个广播接收者的action和广播的action是匹配的,如果找到了,就把该广播接收者的进程启动起来
* 可以使用代码注册
* 需要使用广播接收者时,执行注册的代码,不需要时,执行解除注册的代码
###特殊的广播接收者
* 安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的
1. 屏幕锁屏和解锁
2. 电量改变
###服务的分类
* 本地服务:指的是服务和启动服务的activity在同一个进程中
* 远程服务:指的是服务和启动服务的activity不在同一个进程中
###AIDL
* Android interface definition language
* 进程间通信
1. 把远程服务的方法抽取成一个单独的接口java文件
2. 把接口java文件的后缀名改成aidl
3. 在自动生成的PublicBusiness.java文件中,有一个静态抽象类Stub,它已经继承了binder类,实现了publicBusiness接口,这个类就是新的中间人
4. 把aidl文件复制粘贴到06项目,粘贴的时候注意,aidl文件所在的包名必须跟05项目中aidl所在的包名一致
5. 在06项目中,强转中间人对象时,直接使用Stub.asInterface()
###进程优先级
* 前台进程
* 拥有一个正在与用户交互的activity(onResume调用)的进程
* 拥有一个与正在和用户交互的activity绑定的服务的进程
* 拥有一个正在“运行于前台”的服务——服务的startForeground方法调用
* 拥有一个正在执行以下三个生命周期方法中任意一个的服务(onCreate(), onStart(), or onDestroy())
* 拥有一个正在执行onReceive方法的广播接收者的进程
* 可见进程
* 拥有一个不在前台,但是对用户依然可见的activity(onPause方法调用)的进程
* 拥有一个与可见(或前台)activity绑定的服务的进程
#内容提供者
* 应用的数据库是不允许其他应用访问的
* 内容提供者的作用就是让别的应用访问到你的数据库
* 自定义内容提供者,继承ContentProvider类,重写增删改查方法,在方法中写增删改查数据库的代码,举例增方法
@Override
public Uri insert(Uri uri, ContentValues values) {
db.insert("person", null, values);
return uri;
}
* 在清单文件中定义内容提供者的标签,注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址
<provider android:name="com.itheima.contentprovider.PersonProvider"
android:authorities="com.itheima.person"
android:exported="true"
></provider>
* 创建一个其他应用,访问自定义的内容提供者,实现对数据库的插入操作
public void click(View v){
//得到内容分解器对象
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put("name", "小方");
cv.put("phone", 138856);
cv.put("money", 3000);
//url:内容提供者的主机名
cr.insert(Uri.parse("content://com.itheima.person"), cv);
}
###UriMatcher
* 用于判断一条uri跟指定的多条uri中的哪条匹配
* 添加匹配规则
//指定多条uri
um.addURI("com.itheima.person", "person", PERSON_CODE);
um.addURI("com.itheima.person", "company", COMPANY_CODE);
//#号可以代表任意数字
um.addURI("com.itheima.person", "person/#", QUERY_ONE_PERSON_CODE);
* 通过Uri匹配器可以实现操作不同的表
@Override
public Uri insert(Uri uri, ContentValues values) {
if(um.match(uri) == PERSON_CODE){
db.insert("person", null, values);
}
else if(um.match(uri) == COMPANY_CODE){
db.insert("company", null, values);
}
else{
throw new IllegalArgumentException();
}
return uri;
}
* 如果路径中带有数字,把数字提取出来的api
int id = (int) ContentUris.parseId(uri);
---
#短信数据库
* 只需要关注sms表
* 只需要关注4个字段
* body:短信内容
* address:短信的发件人或收件人号码(跟你聊天那哥们的号码)
* date:短信时间
* type:1为收到,2为发送
* 读取系统短信,首先查询源码获得短信数据库内容提供者的主机名和路径,然后
ContentResolver cr = getContentResolver();
Cursor c = cr.query(Uri.parse("content://sms"), new String[]{"body", "date", "address", "type"}, null, null, null);
while(c.moveToNext()){
String body = c.getString(0);
String date = c.getString(1);
String address = c.getString(2);
String type = c.getString(3);
System.out.println(body+";" + date + ";" + address + ";" + type);
}
* 插入系统短信
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put("body", "您尾号为XXXX的招行储蓄卡收到转账1,000,000人民币");
cv.put("address", 95555);
cv.put("type", 1);
cv.put("date", System.currentTimeMillis());
cr.insert(Uri.parse("content://sms"), cv);
* 插入查询系统短信需要注册权限
---
#联系人数据库
* raw\_contacts表:
* contact_id:联系人id
* data表:联系人的具体信息,一个信息占一行
* data1:信息的具体内容
* raw\_contact_id:联系人id,描述信息属于哪个联系人
* mimetype_id:描述信息是属于什么类型
* mimetypes表:通过mimetype_id到该表查看具体类型
###读取联系人
* 先查询raw\_contacts表拿到联系人id
Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null);
* 然后拿着联系人id去data表查询属于该联系人的信息
Cursor c = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, "raw_contact_id = ?", new String[]{contactId}, null);
* 得到data1字段的值,就是联系人的信息,通过mimetype判断是什么类型的信息
while(c.moveToNext()){
String data1 = c.getString(0);
String mimetype = c.getString(1);
if("vnd.android.cursor.item/email_v2".equals(mimetype)){
contact.setEmail(data1);
}
else if("vnd.android.cursor.item/name".equals(mimetype)){
contact.setName(data1);
}
else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
contact.setPhone(data1);
}
}
###插入联系人
* 先查询raw\_contacts表,确定新的联系人的id应该是多少
* 把确定的联系人id插入raw\_contacts表
cv.put("contact_id", _id);
cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), cv);
* 在data表插入数据
* 插3个字段:data1、mimetype、raw\_contact_id
cv = new ContentValues();
cv.put("data1", "赵六");
cv.put("mimetype", "vnd.android.cursor.item/name");
cv.put("raw_contact_id", _id);
cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
cv = new ContentValues();
cv.put("data1", "1596874");
cv.put("mimetype", "vnd.android.cursor.item/phone_v2");
cv.put("raw_contact_id", _id);
cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
-----
#内容观察者
* 当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知
cr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler()));
class MyObserver extends ContentObserver{
public MyObserver(Handler handler) {
super(handler);
// TODO Auto-generated constructor stub
}
//内容观察者收到数据库发生改变的通知时,会调用此方法
@Override
public void onChange(boolean selfChange) {
}
}
* 在内容提供者中发通知的代码
ContentResolver cr = getContext().getContentResolver();
//发出通知,所有注册在这个uri上的内容观察者都可以收到通知
cr.notifyChange(uri, null);
#ContentProvider
* 四大组件之一
* 内容提供者的作用:把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用
###短信数据库
* sms表
* body:短信内容
* date:短信时间
* address:对方号码
* type:发送还是接收
###联系人数据库
* raw_contacts表
* contact_id:联系人id
* data表:存放联系人的详细的信息,每行数据是单独的一条联系人信息
* data1:联系人的具体的信息
* raw_contact_id:该行信息属于哪个联系人
* mimetype_id:该行信息属于什么类型
* mimetypes表:mimetype_id对应的类型的字符串
##帧动画FrameAnimation
* 多张图片快速切换,形成动画效果
* 帧动画使用xml定义
##补间动画
* 组件由原始状态向终极状态转变时,为了让过渡更自然,而自动生成的动画
###位移动画
TranslateAnimation ta = new TranslateAnimation(10, 100, 20, 200);
* 10:表示的x坐标起始位置
* iv的真实x + 10
* 100:表示x坐标的结束位置
* iv的真实x + 100
* 20:表示y坐标的起始位置
* iv的真实y + 20
* 200:表示y坐标的结束位置
* iv的真实y + 200
TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
* Animation.RELATIVE_TO_SELF, 1:x坐标的初始位置
* iv的真实x + 1 * iv宽
* Animation.RELATIVE_TO_SELF, 0.5f:y坐标的起始位置
* iv的真实y + 0.5 * iv高
###缩放动画
ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, iv.getWidth() / 2, iv.getHeight() / 2);
* 0.5f:表示x坐标缩放的初始位置
* 0.5 * iv宽
* 2:表示x坐标缩放的结束位置
* 2 * iv宽
* iv.getWidth() / 2:表示缩放点的x坐标
* iv的真实x + iv.getWidth() / 2
ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
* Animation.RELATIVE_TO_SELF, 0.5f:表示缩放点的x坐标
* iv的真实x + 0.5 * iv宽
###透明动画
AlphaAnimation aa = new AlphaAnimation(0, 0.5f);
* 0表示动画的起始透明度
* 0.5f表示动画的结束透明度
#Fragment
* 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容
* 生命周期方法跟Activity一致,可以理解把其为就是一个Activity
* 定义布局文件作为Fragment的显示内容
//此方法返回的View就会被显示在Fragment上
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
//用布局文件填充成一个View对象,返回出去,那么就显示在Fragment上了
View v = inflater.inflate(R.layout.fragment01, null);
return v;
}
* 把Fragment显示至指定ViewGroup中
//把fragment显示至界面
//new出fragment对象
Fragment01 fg = new Fragment01();
FragmentManager fm = getFragmentManager();
//开启事务
FragmentTransaction ft = fm.beginTransaction();
//把fragment对象显示到指定资源id的组件里面
ft.replace(R.id.fl, fg);
ft.commit();
###生命周期
* fragment切换时旧fragment对象会销毁,新的fragment对象会被创建
###低版本兼容
* 在support-v4.jar包中有相关api,也就是说fragment可以在低版本模拟器运行
---
#动画
###帧动画
> 一张张图片不断的切换,形成动画效果
* 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="@drawable/g1" android:duration="200" />
<item android:drawable="@drawable/g2" android:duration="200" />
<item android:drawable="@drawable/g3" android:duration="200" />
</animation-list>
* 在屏幕上播放帧动画
ImageView iv = (ImageView) findViewById(R.id.iv);
//把动画文件设置为imageView的背景
iv.setBackgroundResource(R.drawable.animations);
AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
//播放动画
ad.start();
###补间动画
* 原形态变成新形态时为了过渡变形过程,生成的动画就叫补间动画
* 位移、旋转、缩放、透明
#####位移:
* 参数10指的是X的起点坐标,但不是指屏幕x坐标为10的位置,而是imageview的 真实X + 10
* 参数150指的是X的终点坐标,它的值是imageview的 真实X + 150
//创建为位移动画对象,设置动画的初始位置和结束位置
TranslateAnimation ta = new TranslateAnimation(10, 150, 20, 140);
* x坐标的起点位置,如果相对于自己,传0.5f,那么起点坐标就是 真实X + 0.5 * iv宽度
* x坐标的终点位置,如果传入2,那么终点坐标就是 真实X + 2 * iv的宽度
* y坐标的起点位置,如果传入0.5f,那么起点坐标就是 真实Y + 0.5 * iv高度
* y坐标的终点位置,如果传入2,那么终点坐标就是 真实Y + 2 * iv高度
TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
* 动画播放相关的设置
//设置动画持续时间
ta.setDuration(2000);
//动画重复播放的次数
ta.setRepeatCount(1);
//动画重复播放的模式
ta.setRepeatMode(Animation.REVERSE);
//动画播放完毕后,组件停留在动画结束的位置上
ta.setFillAfter(true);
//播放动画
iv.startAnimation(ta);
#####缩放:
* 参数0.1f表示动画的起始宽度是真实宽度的0.1倍
* 参数4表示动画的结束宽度是真实宽度的4倍
* 缩放的中心点在iv左上角
ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4);
* 参数0.1f和4意义与上面相同
* 改变缩放的中心点:传入的两个0.5f,类型都是相对于自己,这两个参数改变了缩放的中心点
* 中心点x坐标 = 真实X + 0.5 * iv宽度
* 中心点Y坐标 = 真实Y + 0.5 * iv高度
ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
#####透明:
* 0为完全透明,1为完全不透明
AlphaAnimation aa = new AlphaAnimation(0, 0.5f);
#####旋转:
* 20表示动画开始时的iv的角度
* 360表示动画结束时iv的角度
* 默认旋转的圆心在iv左上角
RotateAnimation ra = new RotateAnimation(20, 360);
* 20,360的意义和上面一样
* 指定圆心坐标,相对于自己,值传入0.5,那么圆心的x坐标:真实X + iv宽度 * 0.5
* 圆心的Y坐标:真实Y + iv高度 * 0.5
RotateAnimation ra = new RotateAnimation(20, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
#####所有动画一起飞
//创建动画集合
AnimationSet set = new AnimationSet(false);
//往集合中添加动画
set.addAnimation(aa);
set.addAnimation(sa);
set.addAnimation(ra);
iv.startAnimation(set);
---
#属性动画
* 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变
###位移:
* 第一个参数target指定要显示动画的组件
* 第二个参数propertyName指定要改变组件的哪个属性
* 第三个参数values是可变参数,就是赋予属性的新的值
* 传入0,代表x起始坐标:当前x + 0
* 传入100,代表x终点坐标:当前x + 100
//具有get、set方法的成员变量就称为属性
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 100) ;
###缩放:
* 第三个参数指定缩放的比例
* 0.1是从原本高度的十分之一开始
* 2是到原本高度的2倍结束
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "scaleY", 0.1f, 2);
###透明:
* 透明度,0是完全透明,1是完全不透明
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "alpha", 0.1f, 1);
###旋转
* rotation指定是顺时针旋转
* 20是起始角度
* 270是结束角度
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotation", 20, 270);
* 属性指定为rotationX是竖直翻转
* 属性指定为rotationY是水平翻转
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotationY", 20, 180);
###可变参数
* 第三个参数可变参数可以传入多个参数,可以实现往回位移(旋转、缩放、透明)
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 70, 30, 100) ;
###所有动画一起飞
//创建动画师集合
AnimatorSet set = new AnimatorSet();
//设置要播放动画的组件
set.setTarget(bt);
//所有动画有先后顺序的播放
//set.playSequentially(oa, oa2, oa3, oa4);
//所有动画一起播放
set.playTogether(oa, oa2, oa3, oa4);
set.start();
下拉刷新-------
1.addHeaderView必须在setAdapter之前调用
2.将paddingTop设置一个headerView高度的负值去隐藏它
getHeight()和getMeasuredHeight()的区别:
getMeasuredHeight():获取测量完的高度,只要在onMeasure方法执行完,就可以用
它获取到宽高,在自定义控件内部多使用这个
使用view.measure(0,0)方法可以主动通知系统去测量,然后就
可以直接使用它获取宽高
getHeight():必须在onLayout方法执行完后,才能获得宽高
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int headerViewHeight = headerView.getHeight();
//直接可以获取宽高
}
});
3.setSelection(position);将对应位置的item放置到屏幕顶端
侧滑菜单---
github-SlidingMenu
1.在ViewGroup中,让自己内容移动有以下几个方法:
layout(l,t,r,b);
offsetTopAndBottom(offset)和offsetLeftAndRight(offset);
scrollTo和scrollBy方法;
注意:滚动的并不是viewgroup内容本身,而是它的矩形边框
它是瞬间移动,
2.在自定义ViewGroup中一般不需要去实现onMeasure,
我们去实现系统已有的ViewGroup,比如FrameLayout,
它会帮我们区实现onMeasure方法
3.让view在一段时间内移动到某个位置
a.使用自定义动画(让view在一段时间内做某件事)
b.使用Scroller(模拟一个执行流程,)