Day05##
5.1复习#
5.2.根据号码查询号码归属地数据库操作# **
前边将手机防盗模块功能全部实现了,开始做“查询号码归属地”模块
根据号码查询号码归属地一般用在拨打电话时,腾讯管家,360 金山卫士都会弹出对话框提醒
例:133 3333 3333 河北秦皇岛
第一位是:1
第二位是:34578
第三位是:0-9
中国:前三位可确定号码运营商:131联通,133电信,134移动
第三位开始往后四位,可确定号码归属地:陕西
号码前7位就可确定号码归属地:陕西移动
这个由好心网友做的,不是移动联通电信
原理:将号码和归属地存到数据库,根据号码查归属地,淘宝10元可买数据库,做手机安全软件需要
android中一般能不用数据库就不用数据库,因为android都是即点即先操作,点了按钮要立刻显示,数据库是耗时操作,做再好总有速度上的缺陷,不同机型上显示快慢也不一样
这个数据库准备好了,上课资料小米数据库文件夹,有个address.db,telocation.db
用数据库帮助软件SQLiteExport打开telocation.db,里边有个mob_location看下
这个数据库不好:
在location这,上海电信有很多相同数据,没必要显示多条,产生冗余数据,占用了数据库空间
工作中对数据库优化:
根据号码查询归属地,data1和data2通过外键关联,先查询出外键
select outkey from data1 where id=1300001
运行这个sql语句,查询出外键是496,data2中的id和data1中的outkey关联,所以id来个496
select location from data2 where id=496
运行一下,出来个location为江苏常州联通,这样就可以查询出号码归属地
查询时用了两次数据库,浪费资源,写一个关联查询:
select location from data2 where id=(select outkey from data1 where id=1300001)
运行打印location是:江苏常州联通,只需要查询一次,比较节省资源
这个操作在android中算最复杂了,android中数据库主要功能是存储,最简单的增删改查
都是andorid系统设定好的,比如requery,把表名写一下,查询字段以参数形式传一下就好
像上边这种sql语句写法很少见,开发时也就总共遇到过3次
把上边三条sql语句写到笔记中:
select outkey from data1 where id=1300001
select location from data2 where id=496
select location from data2 where id=(select outkey from data1 where id=1300001)
5.3拷贝数据库# ***
1.将数据库address.db导入项目assets目录
assets目录下的资源在程序编译时不会去编译,这个目录中的文件在R.java中不会生成标识,程序运行时直接生成到内存中去使用,数据库万年不动,数据库不需要编译,编译也没有用
assets目录下的数据库不能直接打开,必须将数据库拷贝到手机目录中,通过打开手机目录中的数据库来实现查询数据库操作,但是可以打开assets目录下存放的html文件
DDMS中的data/databases中的某个数据库删掉,重新打开金山卫士直接又生成一个,这是splash界面作用讲的拷贝数据库功能
拷贝数据库是将assets目录下的数据库拷贝到工程的DDMS中的data/database或files目录下,一般是在files目录下,files目录下的路径比较好拿,再去打开数据库时,不是打开assets目录下的数据库,而是打开存放在DDMS下的data/files目录下的数据库
2.拷贝assets目录下的数据库到手机目录中
市面软件拷贝数据库都是在刚打开应用的欢迎界面做的
SplashActivity的onCreate中写一个方法copyDb();,在copyDb()去拷贝数据库
数据库存放在assets目录下,先通过getAssets()获取assets管理者AssetManager
3.通过assets管理者打开数据库,返回读出流流信息InputSteam是一个字节流,异常捕获下:
4.new一个写入流FileOutputSteam
读出流流信息InputStream有了,再来个写入流FileOutputSteam(file)
参数file:new File(dir,name)
参数dir:需要一个文件dir存放目录,getCacheDir()意思是获取缓存路径
如把数据库保存到缓存路径,清理缓存时会清掉
还有一个方法getFileDir() 获取文件路径
参数name:文件名称,写成“address.db”,后缀.db一定要加:
5.进行读写操作并关流
因为是字节流,所以创建字节数组,来个1024,作为缓冲区,再来个int len = -1;
接下来可以while循环进行读写操作
如果不等于-1,可以进行写入操作
out.write(buffer,byteOffset,byteCount),将缓存区写入,从0开始,到len
最后关闭输入输出流,在finally关流
关流也有异常,如不想捕获异常可用XUtils的IOUtils,用IOUtils关流需要把out,in设置成一个值:
IOUtils源码:
开源框架中找到xutils,library/src/com/lidroid/xutils/util/IOUtils.java
打开发现它里边也是close操作,我们不用捕获异常是因为它已经捕获了
它进行了一下封装,才起名叫closeable,closeable把java中的流操作进行了封装,不管是字节流,字符流,全都可以采用这种方式去关闭
6.添加是否拷贝数据库判断
第一次打开欢迎页时进行拷贝数据库操作,当再次打开时手机目录中已有这个数据库了,就没有必要再执行拷贝数据库操作
判断file是否存在,不存在就去拷贝数据库
拷贝数据库完整流程如下:(我们是写在SplashActivity中的)
private void copyDb() {
File file = new File(getFilesDir(), "address.db");
//5.判断file是否存在,存在不去拷贝
if (!file.exists()) {
//1.获取assets管理者
AssetManager assets = getAssets();
InputStream in = null;
FileOutputStream out = null;
try {
//2.通过assets管理者打开数据库
in = assets.open("address.db");
//getCacheDir() : 获取缓存路径,getFilesDir():获取文件的路径
out = new FileOutputStream(file);
//3.读写操作
//缓冲区
byte[] b = new byte[1024];
int len = -1;
while((len = in.read(b)) != -1){
out.write(b, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally{//有没有异常都会执行
//out.close();
//in.close();
//4.关流
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
}
}
运行程序,打开DDMS中data/data/cn.itcast.mobilesafexian02,里边有个files目录,它里边有个address.db
说明拷贝数据库操作已完成
5.4打开数据库,查询号码归属地# ***
接下来就可以打开拷贝到手机目录file中的数据库address.db,并查询数据了
基础班时一般新建XXXOpenHelper继承自SQLiteOpenHelper,实现构造方法,onCreate中创建命名结构:
public class XXXOpenHelper extends SQLiteHelper{
public XXXOpener(){
super(context,"info.db",null,1);
}
@Override
public void onCreate(SQLiteDatabase db){
}
}
这个XXXOpenHelper是创建数据库时使用,数据库address.db已经有了,不用创建数据库,所以不用写它
1.创建AddressDao
数据库address.db我们有,那创建一个数据库操作的AddressDao.java,放在.db或者.db.dao包中,我们放在.db包
AddressDao中创建方法queryAddress,用来查询号码归属地
查询号码归属地是根据号码来查询,并返回一个号码归属地:
2.在queryAddress中打开数据库
用SQLiteDatabase调用openDatabase(path,factory,flags),打开数据库
参数path:数据库路径,参数factory:游标工厂参数,flags:权限标签
数据库路径写什么
在SplashActivity中的copyDb()中拷贝数据库时,有个file文件,拿过来之后用file去获取绝对路径
游标factory不用,写成null
第三个参数指定权限标签,有读写权限,只读权限,只是查询只读OPEN_READONLY就可以
3.在queryAddress中查询数据库
可以用query(),rawquery()两种方式查询数据库
1)query()方式(没办法实现sql语句关联查询)
database.query(table,columns,selection,selectionArgs,groupBy,having,orderBy);
参数table:表名,columns:字段,selection查询的条件,selectionArgs条件参数,groupBy是否分组,orderBy:排序
2)rawQuery方式:(可以用sql语句关联查询)
database.rawQuery(sql,selectionArgs);
参数sql:sql语句
参数selectionArgs:查询条件的参数
演示查询时用的sql语句,query()方式没办法实现sql语句关联查询,这里选用第二种rawQuery查询
参数sql:把上边实现关联查询的sql语句拿过来
select location from data2 where id=(select outkey from data1 where id=1300001
我们是根据这个id去查询的,所以应该写成id=?,即:
select location from data2 where id=(select outkey from data1 where id=?
参数selectionArgs:new一个String[],放个num,在查询时id是一个7位的号码对,输入时一般输入的是11位,需要截取一下,subString截取字符串包含头不包含尾,实际上它是0-6,刚好七位
根据号码对去查询归属地,一个号码对应一个归属地,用while浪费,来个if判断,当cursor.moveToNext()时,用cursor.getString(0)去获取数据,返回一个String类型数据,把它声明出来并初始化为null,起名为location并返回
打开数据库,根据号码查询号码归属地完整代码如下:
public class AddressDao{
public String queryAddress(String num,Context context){
String location = "";
File file = new File(context.getFilesDir(), "address.db");
//1.打开数据库
//path : 数据库的路径
//factory : 游标工厂
//flags : 权限标签
//file.getAbsolutePath() : 获取绝对路径
SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
//2.查询号码归属地,substring:包含头不包含尾 0-6
Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});
//3.解析cursor
if (cursor.moveToNext()) {
location = cursor.getString(0);
}
return location;
}
}
凡是和数据库,业务相关操作,都需要单元测试
在TestMobilesafexian02项目的cn.itcast.mobilesafexian02.test包下,重新创建单元测试类TestAddress.java,继承自AndroidTestCase,创建一个testAddress()
方法中new一个AddressDao,调用queryAddress(num,context)方法
参数num:来个13121646499
参数context:来个getContext,返回一个String类型归属地数据queryAddress,进行判空并输出
也可以做一个判空处理:
public class TestAddress extends AndroidTestCase{
public void testAddress(){
AdressDao addressDao = new AddressDao();
String queryAddress = addressDao.queryAddress("13121646499",getContext());
if(!TextUtils.isEmpty(queryAddress)){
System.out.println(queryAddress);
}
}
}
选中testAddress方法,右键Run As,LogCat中输出:北京联通
5.5查询号码归属地界面# **
查询号码归属地操作已实现,只进行了下单元测试,接下来给这个操作来个界面
在高级工具来个按钮,点击跳转查询号码界面可输入号码,再来个按钮点击查询可查询出归属地
HomeActivity给高级工具条目增加点击事件,通过intent跳转到AToolsActivity
case 7://高级工具
Intent intent7 = new Intent(HomeActivity.this,AToolsActivity.class);
startActivity(intent7);
break;
将ToolsActivity创建出来,清单文件注册,回到AToolsActivity重写onCreate
onCreate中通过setContentView加载布局activity_atool.xml
运行程序,点击高级工具条目就进入到高级工具界面了
在高级工具界面activity_atool.xml添加按钮跳转到查询号码归属地界面
这个查询号码归属地button可添加状态选择器,模仿选择联系人按钮,在drawable文件夹下创建状态选择器
selector_contact_button.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/btn_green_pressed" /> <!-- pressed 按下-->
<item android:drawable="@drawable/btn_green_normal" /> <!-- default 默认图片-->
</selector>
回到AToolActivity中实现点击事件address,跳转到查询号码归属地界面AddressActivity:
public void address(View v){
//跳转到查询号码归属地界面
Intent intent = new Intent(this,AddressActivity.class);
startActivity(intent);
}
创建AddressActivity
在布局文件activity_address.xml中来个“请输入查询的号码”的输入框,接着来个“查询”button,再来个textView显示号码归属地,到AddressActivity中加载布局
运行点击“查询号码归属地”按钮,直接跳转到“查询号码归属地”页面了
要的效果是在EditText输入电话号码,点击“查询”,将号码归属地显示到textView上
给"查询"按钮增加点击事件queryaddress,查询之前下边的textView不显示,把textView默认文字删除掉就看不出来了
接下来在AddressActivity中实现点击事件queryaddress()
在onCreate中把EidtText,textView初始化出来
点击事件queryaddress()中getText()获取输入的内容,判断输入的号码是否为空
为空,提醒用户“请输入要查询的号码”,不为空,根据输入的号码查询归属地
查询号码归属地要拿到addressDao,用queryAddress(num,context)来查询号码归属地,返回一个String类型的号码归属地
对返回的号码归属地queryAddress判空,不为空显示到TextView上
public class AddressActivity extends Activity {
private EditText et_address_queryphone;
private TextView tv_address_queryaddress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_address);
et_address_queryphone = (EditText) findViewById(R.id.et_address_queryphone);
tv_address_queryaddress = (TextView) findViewById(R.id.tv_address_queryaddress);
}
public void queryaddress(View v){
//1.获取输入的内容
String phone = et_address_queryphone.getText().toString().trim();
//2.判断号码是否为空
if (!TextUtils.isEmpty(phone)) {
//3.查询号码归属地
String queryAddress = addressDao.queryAddress(phone, getApplicationContext());
//4.判断查询的归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
tv_address_queryaddress.setText(queryAddress);
}else{
Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();
return;
}
}
}
运行程序,输入号码为13121646499,点击查询,显示:北京联通
5.6电话号码查询逻辑处理# ***
输入123456,点击查询,直接奔溃了,原因:
Cause by:java.lang.StringIndexOutOfBoundsException:length:6,regionStart=7;
角标越界问题,它说长度是6,截取时截取的是7,在AddressDao中的截取字符串处,截取的是6位,肯定没法截取第7位,所以报错
1.用正则表达式匹配身份证
身份证前17位都可以是0-9
写成[0-9]或者\d都可以,即: ^[0-9]$
它要来个17次,即: ^[0-9]{17}$
那后边还要来个0-9或者x:^[0-9]{17}[0-9x]$,这个只是简单的匹配了下18位数字和字母
2.用正则表达式匹配电话号码
打开资料中的正则表达式语法.html
看到是以^开始,以$符号结尾
用[xyz]表示:字符集合,匹配所包含的任意一个字符
[a-z],表示字符范围
\d,表示匹配一个数字字符,等价于[0-9]
{n},n表示是一个非负整数,匹配确定的n次
第一位肯定是1
第二位有可能是34578之一,34578之一用[34578]表达
第三位0-9都可以,用[0123456789]表达
这样写可以,但是有点low
正则表达式语法文档中有个[a-z],表示字符范围
所以可以将[0123456789]写成[0-9]
\d,表示匹配一个数字字符,等价于[0-9],所以还可以写成\d
后边还有9位也可以0-9,那后边还要写9个\d,这样写又太low
文档中上边还有一个{n},n表示是一个非负整数,匹配确定的n次,既然加上第三位后边总共有9位都是可以0-9,那我们就写成:/d{9}
用正则表达式匹配出来的电话号码: ^1[34578]\d{9}$
在AddressDao的queryAddress中的第2步查询号码归属地上边判断
num.matches(regularExpression),参数regularExpression:将电话号码的正则表达式拷贝过来:
if (num.matches("^1[34578]\d{9}$")) {
}
这样写会报错,因为\是转义字符,应该再加个\表示是转义字符:
if (num.matches("^1[34578]\d{9}$")) {
}
如果匹配这个格式才去查询号码归属地,最后关闭database
匹配号码是否11位,110不是11位但也是电话,所以当不是11位时else对num长度判断:
if (num.matches("^1[34578]\d{9}$")) {
//2.查询号码归属地,substring:包含头不包含尾 0-6
Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});
//3.解析cursor
if (cursor.moveToNext()) {
location = cursor.getString(0);
}
cursor.close();
}else{
switch (num.length()) {
case 3://110 120 119
location = "特殊电话";
break;
case 4://5556 5554
location = "虚拟电话";
break;
case 5://10086 10010 10000 95588
location = "客服电话";
break;
case 7://本地电话
case 8://本地电话
location = "本地电话";
break;
default://长途电话 010 1234567 10位 010 12345678 11位 0372 12345678 12位
//startsWith : 是否以哪个字符开头
if (num.length() >= 10 && num.startsWith("0")) {
//长途电话
//根据区号查询相应归属地,3位和4位
//3位
//截取区号
String result = num.substring(1, 3);//010 -> 10
//根据区号查看归属地
Cursor cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});
//解析cursor
if (cursor.moveToNext()) {
location = cursor.getString(0);
location = location.substring(0, location.length()-2);
cursor.close();
}else{
//当查询三位查询不出来,直接查询四位
//复用前面三位区号使用的变量,可以减少内存的使用
result = num.substring(1, 4);
cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});
if (cursor.moveToNext()) {
location = cursor.getString(0);
location = location.substring(0, location.length()-2);
cursor.close();
}
}
}
break;
}
}
在default中如果num.length()大于等于10并且num以0开头,判定为长途电话
因为要有3位或4位,先来判断3位,num截取区号,数据库中第一位0都省略没写,那区号第一位都是0
从1开始,到3结束,比如010,截取后就变成了10这种情况
它会返回一个result: String result = num.substring(1,3);
区号有了,根据区号查询归属地:
database.rawQuery(sql,selectionArgs);
这里需要获取区号的sql,用区号查询号码归属地的sql语句也写下:
select location from data2 where area=10
运行这段语句,查询出来了,但有3个,先把它查询出来,再解决出现3个的问题,把sql语句拷贝过来:
String result = num.substring(1,3);
Cursor cursor = database.rawQuery("select location from data2 where area=?",new String[]{result});
接下来解析cursor,如果cursor.moveToNext(),等于下一个就直接取出来放到location里边:
if (cursor.moveToNext()) {
location = cursor.getString(0);
cursor.close();
}
这是3位情况,4位情况是当3位情况查询不出时,就去查询4位,这个操作和三位操作相似,但是注意将3改成4,这样写会将原来的三位和原来三位的cursor都覆盖掉:
else{
//当查询三位查询不出来,直接查询四位
//复用前面三位区号使用的变量,可以减少内存使用
result = num.substring(1, 4);
cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});
if (cursor.moveToNext()) {
location = cursor.getString(0);
location = location.substring(0, location.length()-2);
cursor.close();
}
}
前边3位时都定义了一个String类型result,和Cursor类型cursor
查询3位时如果没查询出来,那String result,Cursor cursor都没用了
查询4位时,既然和3位是一样操作,就直接拿查询3位时定义的String result,Cursor cursor类型用
这样可减少占用空间和资源,查询4位时的result和cursor是把查询3位时的变量复用了一下,可减少内存使用,因为使用时使用了同一个内存地址
运行看下,输入556,点击查询不会再奔溃了,出来归属地是虚拟电话,不会再出现奔溃
本地电话里边有一些规则,匹配起来就需要一些算法,这个先不写了
查询1234567是本地电话,输入长途电话0101234567,点击查询出来北京电信
sql语句查询时有3个结果,分别是:北京电信,北京移动,北京联通
根据区号查询出来的号码,比如长途电话,有可能是北京电信,也有可能是北京移动,北京联通
都是北京,直接截取字符串,显示北京,截取时不是从0-2了,不要想着只是两个字,看到数据库中还有四个字的“云南临沧”,倒着来,让location长度减去2,截取的都是最后两个字
把这两个字截取掉就相当于从0开始截取,截取长度是总长度减去后边两个字长度:
location = location.substring(0,location.length()-2);
运行程序,输入01012345678,点击查询只显示北京,这样就没有问题了
5.7动态显示号码归属地的操作# ***
上边已实现输入电话号码,点击查询按钮可查询出号码归属地,用户体验不好,现在是用户输入完号码后不点查询直接显示
输入完号码后直接显示归属地,监听输入框输入状态变化的操作 addTextChangedListener(watcher):
onTextChanged(CharSequence s, int start, int before, int count)
参数CharSequence:ctrl+t查看继承树,String类型直接实现了CharSequence,它是一个接口,String类型实现了CharSequence接口,就是一个String类型
参数int start,表示新文本从哪个位置开始输入
参数int before,表示旧文本长度
参数int count,表示新文本替换旧文本的字符个数
参数连起来:新文本从哪个位置开始替换旧文本中哪个个数,旧文本指的是旧文本长度,从新文本哪个位置开始去替换旧文本长度里的字符个数
后边三个参数一般用不到,要用的是第一个参数,toString()是获取输入内容,指新内容
用addressDao查询号码归属地queryAddress(num,context),参数num是输入的号码,得到查询出来的结果queryAddress,判断结果不为空直接给textView赋值即可
//监听输入框输入状态变化的操作
et_address_queryphone.addTextChangedListener(new TextWatcher() {
//当输入内容改变完成时调用
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//获取输入新内容
String phone = s.toString();
//查询号码归属地
String queryAddress = addressDao.queryAddress(phone, getApplicationContext());
if (!TextUtils.isEmpty(queryAddress)) {
tv_address_queryaddress.setText(queryAddress);
}
}
//输入之前调用
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
//输入之后调用
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
运行程序,输入110查询出来归属地为特殊电话了
onTextChanged意思是只要输入一个,就表示输入完成了,就会调用onTextChanged
onTextChanged哪里最常用
商城项目或小商品的搜索,点击搜索,下边会弹出搜索列表,就是通过这种形式实现的,在这里边判断一下输入的内容,获取一下查询一下数据库或网络再显示出来,都是在onTextChanged中做的
5.7抖动的效果# **
APIDemos是android各种控件效果,打开Views/Animation/shake,只要往EditText输入内容就会有抖动效果
EditText抖动效果:
1.拷贝抖动代码到AddressActivity点击事件方法queryaddress的号码为空,提示“请输入要查询的号码”下方:
public void queryaddress(View v){
//1.获取输入的内容
String phone = et_address_queryphone.getText().toString().trim();
//2.判断号码是否为空
if (!TextUtils.isEmpty(phone)) {
//3.查询号码归属地
String queryAddress = addressDao.queryAddress(phone, getApplicationContext());
//4.判断查询的归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
tv_address_queryaddress.setText(queryAddress);
}
}else{
Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();
//抖动
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
// //代码实现动画插入器
// shake.setInterpolator(new Interpolator() {
//
// @Override
// public float getInterpolation(float x) {
// return 0;//根据x的值去获取y的值 y=x^2 y=x-k
// }
// });
et_address_queryphone.startAnimation(shake);
//振动
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(100);//振动100毫秒
return;
}
}
它还需要一个shake.xml文件,也拷贝到anim文件夹下:
2.拷贝shake.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- interpolator : 动画插入器 -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXDelta="0"
android:interpolator="@anim/cycle_7"
android:toXDelta="10" />
发现shake.xml报错,它需要一个cycle_7.xml
interpolator是动画插入器,在模拟器APIDemos找到interpators效果,就是一些特殊动画效果,有由慢到快,由快到慢,想使用这些特殊效果,可以查下文本,到源码中直接查找相应代码,既然少了一个cycle_7.xml,也把它拷贝到anim下
3.拷贝cycle_7.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- cycles : 执行频率 -->
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="7" />
这个是动画插入器,cycles是执行频率,来回的形式
运行程序,查看效果,实现了EditText抖动效果
再来看一下拷贝到AdressActivity中抖动代码,Animation是用AnimationUtils调用loadAnimation获取动画,用动画插入器设置了动画插入效果,返回shake后startAnimation运行动画
动画中还有一个操作:
shake.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float x) {
return 0;//根据x的值去获取y的值 y=x^2 y=x-k
}
});
动画插入器不只可用cycle_7.xml布局实现,也可用代码实现,用代码实现动画插入器操作一般不用,因为要涉及数学上的语言,比如正弦余弦等算法,一般搞开发的,如果不是数学学得特别好都不用这玩意,用也是用布局文件形式
它里边有getInterpolation方法,参数float input,input相当于x值,最后return返回的就是根据x值获取y值,比如y=x^2 y=x-k这些情况,这个其实和正弦余弦有关系,比如画一个坐标系,里边画一条y=x线,y=x-k线,y=x^2,这个就是动画插入器效果
5.8让手机振动的效果# **
例子工程“振动”,包名为com.example.vibrate
MainActivity获取振动管理者Vibrator,都是通过getSystemService获取
Vibrator中vibrate(milliseconds),milliseconds是振动持续时间,设置成Long.MAX_VALUE,手机会振动到废掉:
有些手机这样设置完后,还是只振动一次,比如小米
vibrator还有一个方法vibrate(pattern,repeat),参数pattern:振动频率,repeat:是否重复 -1不重复,非-1重复
pattern是long类型数组,new一个long类型数组,来个50l,再来个20l,再来个50l,再来个20l,第二个参数来个-1,这时振动会先按照50频率振动一下再按照20频率振动一下,再按50频率振动一下,再按20频率震动一下:
public class MainActivity extends Activity{
@Override
portected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取振动的管理者
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
//振动
vibrator.vibrate(Long.MAX_VALUE);
vibrator.vibrate(new long[]{50l,20l,50l,20l}, -1);
}
}
市面很多软件会用到这个操作,振动需要添加权限,打开清单文件选择Permissions,添加android.permissions.VIBRATE
在浏览器中输入www.javaapk.com,应用汇搜索按摩器,效果图上有个一档二档档位就是按上边代码设置
添加振动频率时它是long类型数组,所以在添加不同频率时,给它后边加个l,表示是long类型
运行振动项目,模拟器没有振动效果,用真机
将振动效果移植到手机卫士
AddressActivity输入内容为空时,不止可抖动还可振动:
else{
Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();
//抖动
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
// //代码去实现动画插入器
// shake.setInterpolator(new Interpolator() {
//
// @Override
// public float getInterpolation(float x) {
// return 0;//根据x的值去获取y的值 y=x^2 y=x-k
// }
// });
et_address_queryphone.startAnimation(shake);
//振动
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(100);//振动100毫秒
return;
}
添加振动权限:
<uses-permission android:name="android.permission.VIBRATE"/>
运行程序,进入高级工具,点击查询按钮,EditText来回抖动了,提醒“请输入要查询的号码”,振动功能也实现了,只是因为模拟器没法查看振动效果
5.10 来电显示号码归属地#
查询号码归属地操作已做完,让用户点进这个界面,输入号码才自动显示归属地,这种功能一般用户不会去用
市面安全软件拨打电话,来电时,会在拨打电话,来电界面显示归属地,挂断消失
监听来电显示状态,监听状态都用广播接收者,这里有些例外,可以用TelephoneManager监听电话的来电显示状态
监听是时时刻刻都去监听,那就要写在服务中,开启服务,不管什么时候来电,都能显示号码归属地
已经在service包下写过一个GPSService服务
在service包下新建AddressService服务继承自service,紧接着到清单文件配置:
<service android:name="cn.itcast.mobilesafexian02.service.AddressService" >
</service>
AddressService中重写onCreate,onCreate中就可以去获取电话管理者telephonyManager,获取管理者都是通过getSystemService(name)获取,把它声明成成员变量,方便其他地方使用:
telephonyManager中有个listen方法,电话状态的监听器,把监听创建出来继承自PhoneStateListener
listen第二个参数events是一个事件表示要监听哪个事件,要监听的是电话来电状态
既然是电话状态,PhoneStatelistener中有个LISTEN_CALL_STATE是监听电话状态
此外还有一个LISTEN_NONE,NONE表示不监听任何状态,要监听的是电话的状态
开启服务时要去监听电话状态,关闭时不去监听任何状态
onDestroy中设置下listen,第一个参数也需要MyPhoneStateListener,onCreate的listen的第一个参数不能那么去写,把它拿出来声明成成员变量,onCreate和onDestory就都可以使用myPhoneStateListener了,在onDestroy中服务关闭不监听任何状态了,将onDestory中listen第二个参数改为LISTEN_NONE
到MyPhoneStateListener处理,选中LISTEN_CALL_STATE,标识出来你应该要实现的方法是onCallStateChanged
在onCallStateChanged中就可去实现电话状态,参数state是电话状态,incomingNumber是来电号码
CALL_STATE_IDLE是空闲的状态
CALL_STATE_RINGING是响铃的状态
CALL_STATE_OFFHOOK是通话的状态
响铃时显示号码归属地,需要去查询号码归属地了,把AddressDao在onCreate中new出来申明成成员变量调用queryAddress(num,context),参数num是incomingNumber,返回归属地queryAddress,对返回的归属地判空,不为空就用Toast显示号码归属地:
public class AddressService extends Service{
private MyPhoneStateListener myPhoneStateListener;
@Override
public void onCreate() {
super.onCreate();
addressDao = new AddressDao();
//1.获取电话的管理者
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
myPhoneStateListener = new MyPhoneStateListener();
telephonyManager.listen(myPhoneStateListener,PhoneStateListener.LISTEN_CALL_STATE)
}
}
private class MyPhoneStateListener extends PhoneStatelistener{
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
//4.判断电话的状态
switch (state) {
case TelephonyManager.CALL_STATE_IDLE://空闲的状态
break;
case TelephonyManager.CALL_STATE_RINGING://响铃的状态
//5.查询显示号码归属地
String queryAddress = addressDao.queryAddress(incomingNumber, getApplicationContext());
if (!TextUtils.isEmpty(queryAddress)) {
Toast.makeText(getApplicationContext(), queryAddress, 0).show();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK://通话的状态
break;
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
//6.取消监听状态
telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);//不去监听任何状态
}
之所以用Toast是因为Toast有跨界面显示效果
服务已经写完了,要时时刻刻监听,到SplashActivity的onCreate中开启服务(调用):
// 开启服务
Intent intent = new Intent(this,AddressService.class);
startService(intent);
运行程序,AddressService服务有没有开启,打开settings/Apps/找到手机卫士西安2号,看到有一个服务开启,而且是AddressService,那现在打开DDMS拨打下电话,Incoming number为:18888888888,点击call,模拟器来电话18888888888了,toast提示:“北京移动”
总结“来电显示号码归属地”步骤:
1.创建一个addressService,清单文件配置
2.通过TelephoneManager实现监听来电显示操作
3.在spalshactivity中开启服务
5.11 自定义Toast# **
使用Toast显示来电归属地,样式不太好,自定义一下toast样式
toast中View.inflate和LayoutInflater.from(context)的区别:
View.inflate(打开View.class源码)里边是一个LayoutInflater.from(context);
LayoutInflater.from(context)相当于还是通过cotext.getSystemService
它两是一样的效果,一般我喜欢用的是View.inflate,因为简单
到AddressService中,在响铃状态处调用showToast(),在下边创建showToast
获取windowmanager调用addView(view,params)添加view,既然是view就来个简单的TextView:
给textView设置text,这个文本显示的就是查询的号码归属地,在showToast的参数上给它传过来,在AddressService中的"5.查询显示号码归属地"处调用的showToast(),此时也需要把它改为showToast(queryAddress):
紧接着再给它设置字体大小20,textColor为RED,把textView设置给addView()
addView还有一个参数params,是一个LayoutParams,到源码Toast.class中的mWM.addView(mView,mParams)中找到TN(),里边就有mParams,把这段全部拷贝过来:
public void showToast(String queryAddress){
// 获取windowmanager
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
TextView textView = new TextView(getApplicationContext);
textView.setText(queryAddress);
textView.setTextSize(20);
textView.setTextColor(Color.RED);
// 设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则
// LayoutParams:设置属性
params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;// 高度包裹内容
params.width = WindowManager.LayoutParams.WRAP_CONTENT;// 宽度包裹内容
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不可获取焦点
// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮
params.format = PixelFormat.TRANSLUCENT; // 透明
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;// 控件的类型,显示窗体上的类型,toast天生没有触摸事件
// 设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = sp.getInt("x", 100);// x表示的不是坐标,而是距离边框的距离,根据Gravity来设置的,LEFT就是距离左边框的距离,ringht就是距离右边框的距离
params.y = sp.getInt("y", 100);// y和x是相同的含义,根据top或者bottom来时设置距离顶部或者底部的距离
windowManager.addView(textView, params);
}
windowManager.addView(textView, params);意思是先把属性params设置给textview,然后将textview添加到窗体上,上边params属性height为包裹内容,width为包裹内容,flags标签为不可获取焦点,不可触摸
保持屏幕常量,透明的,toast都是透明, 看到后边是个黑色,其实是设置了背景,控件类型type尤为重要
这里类型指的是显示到窗体上的类型,是TYPE_TOAST类型,只有设置成吐司才能像吐司那样去显示
实际上吐司能显示也是因为它设置了TYPE_TOAST属性,这个就是设置TextView的属性
这里是WindowManager.LayoutParams,你的控件在哪个父控件上显示就要获取哪个父控件的LayoutParams,表示这个控件符合父控件属性规则,因为每个控件里边设置的LayoutParams属性都不一样,这样才能显示出来
运行程序,打开DDMS点击call拨打电话显示出北京移动了,它没有像吐司一样消失,但当挂断电话后还不消失
再次拨打电话时,又会出现“北京移动”,而且和前边没有消失的那个重叠了,它好像镶嵌到屏幕上一样
再次查看Toast.class源码,它有一个handleHide()隐藏,它做了mWM.removeView(mView);操作
刚才addView是让显示,那removeView是让移除
回到AddressService写个hideToast是用来隐藏Toast的,就是调用了windowManager中的一个removeView()
还需要判断下windowManager和textView是否为空,为空就没必要remove:
最后需要将windowManager和textView置为空, 这是为了避免用户直接在这里调用隐藏Toast的方法,再移除的话就会报空指针异常,同时也为下次显示toast做准备,隐藏Toast之后将windowManager,textView置为空,当再次去使用Toast调用showToast时,得到windowmanger,并会再次new一个TextView出来:
public void hideToast(){
//隐藏toast
//判断windowManager和textview是否为空,如果为空就不去remove
if (windowManager != null && textView != null) {
windowManager.removeView(textView);
//避免用户再次使用隐藏报出view不在window的异常,同时也是为下次显示toast做准备
windowManager = null;
textView = null;
}
}
挂断电话之后就可以去隐藏toast了,挂断电话就是空闲状态,即来到AddressService.java中的CALL_STATE_IDLE中调用hideToast即可
运行程序,拨打电话,显示归属地为“北京联通”,挂断电话后,归属地直接就消除了
自定义Toast具体流程整理如下:
显示toast:
1.获取一个WindowManager
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
2.创建一个textview
textView = new TextView(getApplicationContext());
textView.setText(queryAddress);
textView.setTextSize(100);
textView.setTextColor(Color.RED);
3.设置toast的属性
//设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;//高度包裹内容
params.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽度包裹内容
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//不可获取焦点
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮
params.format = PixelFormat.TRANSLUCENT; // 透明
params.type = WindowManager.LayoutParams.TYPE_TOAST;//控件的类型,显示窗体上的类型
4.将textivew添加到window
windowManager.addView(textView, params);//先把属性设置给textview,然后将textview添加到窗体上
隐藏toast:
/**
-
隐藏toast的操作
*/
public void hideToast(){
//隐藏toast
//判断windowManager和textview是否为空,如果为空就不去remove
if (windowManager != null && textView != null) {
windowManager.removeView(textView);
//避免用户再次使用隐藏报出view不在window的异常,同时也是为下次显示toast做准备
windowManager = null;
textView = null;
}
}WindowManager.LayoutParams这个上边讲的很重要,后边还会遇到LinearLayout.LayoutParams,你的控件在哪个父控件上显示,就要获取哪个父控件的LayoutParams表示你这个控件符合父控件属性规则 textView里边有这样一个操作,setLayoutParams(params)将params属性设置给textView: textView.setLayoutParams(params); 这里不直接使用setLayoutParams,因为下边有个windowManager.addView(textView, params),通过addView将属性添加给了textView才设置给windowManager
5.12服务的开启# ***
AddressService服务是在Splash界面打开时就去开启这个服务,用户体验不好,现在软件是在设置中心增加条目,点击显示号码归属地,再点击隐藏号码归属地,由用户控制显示不显示
小米这些手机都内置了号码归属地显示,有些用户不想要你app中的号码归属地提醒,所以现在安全软件都会让用户选择打开还是关闭
这个操作和设置中心已经做过的打开/关闭提示更新一模一样
来到设置中心SettingActivity中,将打开/关闭提示更新布局复制一份出来,这个就是为什么要封装设置中心布局中的属性原因
这样复制一份下来特别少,将复制的控件中title改为“显示号码归属地”,将des_on改为“打开显示号码归属地”,将des_off改为“关闭显示号码归属地”,并将id改为sv_setting_address:
activity_settting.xml
<cn.itcast.mobilesafexian02.ui.SettingView
android:id="@+id/sv_setting_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
itcast:des_off="关闭显示号码归属地"
itcast:des_on="打开显示号码归属地"
itcast:title="显示号码归属地" >
</cn.itcast.mobilesafexian02.ui.SettingView>
到SettingActivity初始化控件添加点击事件,具体流程和更新的一模一样,将更新操作封装到一个方法中,不然在onCreate中会越写越多,选中需要抽取的代码,alt+shift+m,给Method name起名为update点击ok,onCreate中调用了update方法,在下边直接抽取出了update方法
归属地也一样,onCreate中调用address()生成address方法
在这个方法中就可以给它增加点击事件setOnClickListener()
和提示更新操作一模一样,判断下checkbox值,等于true表示已开启服务,再点击表示关闭服务
开启服务用startService(intent),关闭服务用stopService(intent),同时要更改checkbox值:
private void address() {
sv_setting_address.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SettingActivity.this,AddressService.class);
if (sv_setting_address.isChecked()) {
//关闭服务
stopService(intent);
sv_setting_address.setChecked(false);
}else{
//打开服务
startService(intent);
sv_setting_address.setChecked(true);
}
}
}
在SettingActivity中去开启/关闭AddressService服务了,那就将SplashActivity中开启服务的代码注释掉
运行代码,进入设置中心点击显示号码归属地条目,显示打开显示号码归属地了,到setting/Apps/running中,点击手机卫士西安2号,看到AddressService服务已开启了
再点击设置中心的显示号码归属地条目,显示关闭显示号码归属地了,再来到settting下看不到这个服务了
5.13 服务的开启2_动态获取服务是否开启 # ***
打开“显示号码归属地”,再次回到设置中心又变成关闭显示号码归属地,但是在settings中AddressService服务是开启的,所以要对设置中心的“显示号码归属地”条目进行回显操作
问题:
以前都保存在sp中,这次不能保存在sp了,因为在settings/apps/running/手机卫士西安2号处有个stop,点击stop可以把服务关闭掉
比如点击设置中心的“显示号码归属地”开启了AddressService并保存到sp中,用户到settings/apps/running/手机卫士西安2号处,手动点击stop,这时服务关闭了,但是sp中保存的还是开启状态,再次进去设置中心还是会显示“已打开号码归属地”,这就显示不准确了
解决: 动态获取服务是否开启
utils包下创建工具类AddressUtils,再创建方法isRunningService实现动态获取服务是否开启操作
需要传一个全类名过来,判断服务是否开启,返回值写boolean
ActivityManager是通过getSystemService(name)获取
getSystemService是通过context获取, 所以方法的参数还需要一个上下文:
ActivityManager别误解为Activity管理者,它是进程管理者或活动管理者
看它里边操作:
getLargeMemoryClass() 获取最大内存
getMemoryInfo() 获取内存的一些信息
getProcessesInErrorState() 获取进程错误的状态
getProcessesMemoryInfo() 获取进程内存的一些信息
getRunningServices(maxNum) 获取正在运行的服务,maxNum是返回的上限,就是最多返回多少个正在运行的服务
这里来个1000,不是返回1000个正在运行的服务,而是返回小于等于1000个正在运行的服务,超过1000个的不返回
手机运行内存普遍应该是3MB,不可能有1000个正在运行的服务,开100个内存都会溢出,写1000就是一个上限值
返回一个list集合,里边存放的是RunningServiceInfo,xxxInfo都是bean类,用来存放信息
遍历集合并用runningServiceInfo调用.service,返回组件标示ComponentName
ComponentName在超级管理员权限处讲过,是组件标识,名称,通过它可以调用:
getClass()获取字节码文件
getClassName()获取全类名
getPackageName()获取包名
getShortClassName()获取短类名
我们获取下全类名,返回一个服务的全类名,判断获取的服务的全类名是否和传递过来的全类名相同
相同表示服务已开启,不相同表示服务没有开启
public class AddressUtils {
public static boolean isRunningService(String className,Context context){
//1.进程的管理者,活动的管理者
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//2.获取正在运行的服务
List<RunningServiceInfo> runningServices = activityManager.getRunningServices(1000);//maxNum : 返回上限,最多返回多少个服务
//3.遍历集合
for (RunningServiceInfo runningServiceInfo : runningServices) {
//4.获取服务的标示
//ComponentName : 组件的标示
ComponentName componentName = runningServiceInfo.service;
//5.根据标示获取服务的全类名
String className2 = componentName.getClassName();
//6.判断获取的服务的全类名是否和传递过来的全类名相同,相同表示服务已经开启,不相同表示服务没有开启
if (className.equals(className2)) {
return true;
}
}
return false;
}
}
给isRunningService方法添加static,方便调用
回到SettingActivity的address方法中,判断isRunningService(className,context)
参数className需要AddressService全类名
为true表示服务已开启,else表示服务没开启
private void address(){
//回显
//动态的获取服务是否开启
if (AddressUtils.isRunningService("cn.itcast.mobilesafexian02.service.AddressService", this)) {
sv_setting_address.setChecked(true);
}else{
sv_setting_address.setChecked(false);
}
}
运行程序,设置中心点击开启显示号码归属地,到setttings/apps/running下找到手机卫士西安2号,看到AddressService服务已开启,再次进入设置中心还是开启显示号码归属地
再点击关闭显示号码归属地,到settings/apps/running下找到手机卫士西安2号,发现没有AddressService服务(关闭了),再回到设置中心,还是关闭显示号码归属地
还有一个问题:
在settings/apps/running下找到手机卫士西安2号,手动点击stop关闭AddressService,回到设置中心发现还是打开显示号码归属地,退出设置中心再进来才显示关闭显示号码归属地
相当于没及时更新界面,切换“设置中心”和settings时,按的是home键,界面只是不可见了,始终没有退出界面,再次回来还是“设置中心”界面,在可见和不可见时没有更新“设置中心”界面
onStart是界面可见时调用,SettingActivity中重写onStart,把在onCreate中调用的address()移动到onStart中调用:
@Override
protected void onStart(){
super.onStart();
address();
}
运行程序,重复上边步骤再回到设置中心时就及时更新了条目状态
服务开启步骤总结如下:
alt+shift+m : 将相应代码抽取方法中
1.在设置中心中增加显示号码归属地条目
2.给条目增加点击事件
/**
-
显示号码归属地
*/
private void address() {
sv_setting_address.setOnClickListener(new OnClickListener() {@Override public void onClick(View v) { Intent intent = new Intent(SettingActivity.this,AddressService.class); if (sv_setting_address.isChecked()) { //关闭服务 stopService(intent); sv_setting_address.setChecked(false); }else{ //打开服务 startService(intent); sv_setting_address.setChecked(true); } } }); } 3.回显操作,动态获取服务是否开启 *** a.创建一个工具类 public class AddressUtils { /**
-
动态获取服务是否开启
-
@param className
-
@return
*/
public static boolean isRunningService(String className,Context context){
//1.进程的管理者,活动的管理者
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//2.获取正在运行的服务
List runningServices = activityManager.getRunningServices(1000);//maxNum : 返回上限,最多返回多少个服务,10M 10000M 10G
//3.遍历集合
for (RunningServiceInfo runningServiceInfo : runningServices) {
//4.获取服务的标示
//ComponentName : 组件的标示
ComponentName componentName = runningServiceInfo.service;
//5.根据标示获取服务的全类名
String className2 = componentName.getClassName();
//6.判断我们获取的服务的全类名是否和我们传递过来的全类名相同,相同就是表示服务已经开启,不相同就表示服务没有开启
if (className.equals(className2)) {
return true;
}
}
return false;
}
}
b.在activity中调用(SettingActivity.java)private void address(){ /动态的获取服务是否开启 if (AddressUtils.isRunningService("cn.itcast.mobilesafexian02.service.AddressService", this)) { sv_setting_address.setChecked(true); }else{ sv_setting_address.setChecked(false); } }
5.12修改toast样式# **
服务的开启上边已讲完,再回过头来看下代码,在AddressService.java服务里边去显示Toast时,用的TextView去显示
在windowManager.addView(View view,LayoutParams params)时,来个View.Inflate,需要布局文件
将布局文件toast_custom.xml创建出来,先来个ImageView,并给LinearLayout设置背景图片,借鉴apk资料/金山卫士/drawable下的图片,它有5个不同颜色的图片,全部都拷贝到项目的drawble-hdpi目录,在LinearLayout中给它默认设置绿色图片:call_locate_blue,再来个TextView控件,设置默认文字为“北京电信”:
toast_custom.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/call_locate_blue">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/tv_toastcustom_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="北京电信"
android:textSize="18sp"
android:textColor="#000000"/>
</LinearLayout>
回到AddressService中加载布局,会返回一个View对象,初始化下Textview(号码归属地),并给它setText,将号码归属地设置给布局中的TextView,将下边单独写的TextView对象注释掉,会有好几个地方因为注释掉textView而报错,将报错地方的textCView分别替换成view,到这修改Toast样式就做完了:
/**
-
显示toast
*/
public void showToast(String queryAddress) {
int[] bgcolor = new int[] { R.drawable.call_locate_white,
R.drawable.call_locate_orange, R.drawable.call_locate_blue,
R.drawable.call_locate_gray, R.drawable.call_locate_green };
// 获取windowmanager
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// 将布局文件转化成view对象之后,view对象就是控件的最外层的控件
view = View.inflate(getApplicationContext(), R.layout.toast_custom,
null);
TextView tv_toastcustom_address = (TextView) view
.findViewById(R.id.tv_toastcustom_address);
tv_toastcustom_address.setText(queryAddress);
// 设置view对象的背景,根据保存的选项索引从bgcolor图片数组中获取相应的图片
view.setBackgroundResource(bgcolor[sp.getInt(“which”, 0)]);// textView = new TextView(getApplicationContext()); // // textView.setTextSize(100); // textView.setTextColor(Color.RED); // android.view.ViewGroup.LayoutParams layoutParams = // textView.getLayoutParams();//获取他在父控件中的属性 // textView.setLayoutParams(params);//设置属性 // 设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则 // LayoutParams:设置属性 params = new WindowManager.LayoutParams(); params.height = WindowManager.LayoutParams.WRAP_CONTENT;// 高度包裹内容 params.width = WindowManager.LayoutParams.WRAP_CONTENT;// 宽度包裹内容 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不可获取焦点 // | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮 params.format = PixelFormat.TRANSLUCENT; // 透明 params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;// 控件的类型,显示窗体上的类型,toast天生没有触摸事件 // 设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边 params.gravity = Gravity.LEFT | Gravity.TOP; params.x = sp.getInt("x", 100);// x表示的不是坐标,而是距离边框的距离,根据Gravity来设置的,LEFT就是距离左边框的距离,ringht就是距离右边框的距离 params.y = sp.getInt("y", 100);// y和x是相同的含义,根据top或者bottom来时设置距离顶部或者底部的距离 setTouch(); windowManager.addView(view, params);// 先把属性设置给textview,然后将textview添加到窗体上 }
运行程序,打开来电归属地,拨打号码,出现了一个蓝色Tost,提示北京移动
5.12修改toast样式# **
总结略