主要学习内容:
1)号码归属地查询UI设计
2)号码归属地查询的原理
3)号码归属地查询的代码实现
5)输入框抖动和震动效果
6)来电、去电号码归属地的显示
7)代码注册广播接受者的实现
8)自定义吐司显示号码归属地【实现号码归属地显示效果:加背景、加图片、加文字
首先看一下UI实现的效果:
通过UI效果可以清楚看到我们实现了哪些内容:第一幅图片的查询是查询过程中就在查数据库,动态加载电话号码信息;设置号码归属地是可选的;来电归属地显示效果通过自定义吐司实现不同效果。
查询原理:
第一种:联网查询;
第二种:把数据库 放在本地;
------1、百度上输入:手机号码归属地
-----2、数据库的来源,可以在淘宝上购买;
买的数据不一定是Android下用的数据库;
如果在Android上使用得自己做一个;把数据写到Android的数据库里;
号码归属地的查询实现
1、把数据库拷贝到assets目录并创建包com.itheima.mobilesafe.db.dao 包创建该类
file:///android_aset/address.db 这种无法访问
知识回顾:
WebView还可以加载图片String str = "file:///android_asset/icon.png";
在APP刚启动的时候,自动加载数据,将assets中的数据库复制到数据库。
private void copyDb() {
//只要你拷贝了一次,我就不要你再拷贝了
try {
File file=new File(getFilesDir(), "address.db");
if(file.exists()&&file.length()>0){
//正常了,不需要拷贝了
Log.i(TAG,"正常了,不需要拷贝了");
}else{
InputStream is=getAssets().open("address.db");
FileOutputStream fos=new FileOutputStream(file);
byte[] buffer=new byte[1024];
int len=0;
while(is.read(buffer)!=-1){
fos.write(buffer,0,len);
}
is.close();
fos.close();
}
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
关于电话号码的常识可以到百度百科中搜集资料,非常多的介绍。
关于数据库查询归属地的工具代码如下:
package com.example.mobilesafe.db.dao;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class NumberAddressQueryUtils {
private static String path="data/data/com.example.mobilesafe/files/address.db";
/*
* 产一个号码进来,返回一个归属地回去
*/
public static String queryNumber(String number){
String address=number;
//path 把address.db这个数据库拷贝到data/data/包名/files/address.db
SQLiteDatabase database=SQLiteDatabase.openDatabase(path, null,SQLiteDatabase.OPEN_READONLY);
//手机号码 13 14 15 16 18
//手机号码正则表达式
if(number.matches("^1[34568]\\d{9}$")){
//手机号码
//Cursor cursor=database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{number.substring(0, 7)});
Cursor cursor = database
.rawQuery(
"select location from data2 where id = (select outkey from data1 where id = ?)",
new String[] { number.substring(0, 7) });
while(cursor.moveToNext()){
String location=cursor.getString(0);
address=location;
System.out.println("address"+address);
}
cursor.close();
}else{
//其它的电话号码
switch(number.length()){
case 3:
address="匪警号码";
break;
case 4:
address="模拟器";
break;
//10086
case 5:
address="客服电话";
break;
case 7:
address="本地号码";
break;
case 8:
address="本地号码";
break;
default:
//处理长途电话
if(number.length()>10&&number.startsWith("0")){
Cursor cursor = database
.rawQuery(
"select location from data2 where area=?)",
new String[] { number.substring(1, 3) });
while(cursor.moveToNext()){
String location=cursor.getString(0);
address= location.substring(0, location.length()-2);
}
cursor.close();
//0855-353253445
cursor = database
.rawQuery(
"select location from data2 where area=?)",
new String[] { number.substring(1, 4) });
while(cursor.moveToNext()){
String location=cursor.getString(0);
address= location.substring(0, location.length()-2);
}
}
break;
}
}
return address;
}
}
实现输入框输入部分位数号码只能查找该号码归属地:输入框是有状态事件的
private EditText ed_phone;
ed_phone.addTextChangedListener(new TextWatcher() {
/*
* 文本發生變化的時候回調
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
if(s.length()>=3){
//查詢數據庫
String address=NumberAddressQueryUtils.queryNumber(s.toString());
result.setText(address);
}
}
/*
* 文本發生變化之前變化(non-Javadoc)
* @see android.text.TextWatcher#beforeTextChanged(java.lang.CharSequence, int, int, int)
*/
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
/*
* 文本發生變化之後回調(non-Javadoc)
* @see android.text.TextWatcher#afterTextChanged(android.text.Editable)
*/
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
输入框或者其它组件震动与抖动效果实现
抖动和振动效果_32
-----1、进入模拟器的APIDemo展示点击输入框振动效果(views/animation/shake);
//shake 动摇;摇动;震动;握手的意思
------2、找系统sdk\samples\android-16\ApiDemos 并导入工程,如果有错误就解决;
-----3、找代码搜索代码
功能--》布局文件---》代码
-----3、找到抖动代码移植;并演示;
-----4、介绍插入器(interpolator)
------5、看APIDemo里views/animation/interpolators 的各种动画
Accelerate 加速动画 decelerate 减速动画
Interpolator类似一个函数,计算动画如何播放
/**
* 系统提供的振动服务
*/
private Vibrator vibrator;
vibrator=(Vibrator) getSystemService(VIBRATOR_SERVICE);
如果输入查询号码为空时查询,实现震动效果:
if(TextUtils.isEmpty(phone)){
Toast.makeText(this, "号码为空", 0).show();
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
ed_phone.startAnimation(shake);
//当电话号码为空的时候就去震动手机提醒用户
vibrator.vibrate(1000);
/* long[] pattern={200,200,300,300,1000,2000};
vibrator.vibrate(pattern, -1);*/
return;
}
震动权限:
<uses-permission android:name="android.permission.VIBRATE"/>
来电号码归属地的显示
引入:1、演示打进电话,说明系统的拨号不能改。2、展示土司;
-----1、创建后台监听来电服务AddressService 并在功能清单文件注册;
检查是否有权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
-----1、在AddressService 服务里面注册来电状态
(TelephoneManager)
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
-------3、自定义监听类MyPhoneStateListener
private class MyListenerPhone extends PhoneStateListener{
@Override
public void onCallStateChanged(int state, String incomingNumber) {
//state:状态,incomingNumber:来电号码
super.onCallStateChanged(state, incomingNumber);
switch(state){
case TelephonyManager.CALL_STATE_RINGING: //来电铃声想起
//查询数据库的操作
String address=NumberAddressQueryUtils.queryNumber(incomingNumber);
//Toast.makeText(getApplicationContext(), address, 1).show();
myToast(address);
break;
case TelephonyManager.CALL_STATE_IDLE: //电话的空闲状态
//把这个View移出
if(view!=null){
wm.removeView(view);
}
break;
default:
break;
}
}
}
取消监听:
tm.listen(listener, PhoneStateListener.LISTEN_NONE);
listener = null;
-------3、在SettingActivity设置中心里配置设置,当点击开启的时候就启动服务,否则相反。然后演示;
布局文件:
<com.itheima.mobilesafe.ui.SettingItemView
android:id="@+id/siv_show_address"
itheima:title="设置号码归属地显示"
itheima:desc_on="号码归属地显示已经打开"
itheima:desc_off="号码归属地显示已经关闭"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
SettingActivity里的代码:
//设置号码归属地显示
//设置归属地显示控件
siv_show_address=(SettingItemView) findViewById(R.id.siv_show_address);
showAddress=new Intent(this,AddressService.class);
boolean isServiceRunning=ServiceUtils.isServiceRunning(SettingActivity.this, "com.example.mobilesafe.service.AddressService");
if(isServiceRunning){
//监听来电服务是否开启的
siv_show_address.setChecked(true);
}else{
siv_show_address.setChecked(false);
}
siv_show_address.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(siv_show_address.isChecked()){
//变为非选中状态
siv_show_address.setChecked(false);
stopService(showAddress);
}else{
//选中状态
siv_show_address.setChecked(true);
startService(showAddress);
}
}
});
------3、创建一个类ServiceStatusUtils 里面的方法isServiceRunning()校验检查一个服务是否开启;
public class ServiceUtils {
/*
* 某个服务是否还活着
*/
public static boolean isServiceRunning(Context context,String serviceName){
//校验服务是否还活着
ActivityManager am=(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> infos=am.getRunningServices(100);
for(RunningServiceInfo info:infos){
String name=info.service.getClassName();
if(serviceName.equals(name)){
return true;
}
}
return false;
}
}
去电号码归属地的显示
------1、创建广播接收者OutCallReceiver 并注册;监听电话打出去的广播,需要意图;
public class OutCallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
//这就是我们拿到的拨出去的电话号码
String phone=getResultData();
//查询数据库
String address=NumberAddressQueryUtils.queryNumber(phone);
Toast.makeText(context, address, 1).show();
}
}
<receiver android:name="com.itheima.mobilesafe.receiver.OutCallReceiver">
<intent-filter >
<action android:name="android.intent.action.NEW_OUTGOING_CALL"></action>
</intent-filter>
</receiver>
------2、需要监听具体意图和权限;
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
代码注册广播接收者
------1、演示功能:演示勾选的来电去电、 进程杀掉时的来电去电;
------2、四大组件 :
activity 、 service 、content provider、
Broadcast Receiver
都需要在功能清单文件注册。
Broadcast Receiver不仅可以在功能清单文件注册还可以用代码注册;
-------3.移植到AddressService 里和服务生命周期一样。并且演示。
注册在onCreate():
//代码注册一个广播接收者
receiver = new OutCallReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.NEW_OUTGOING_CALL")registerReceiver(receiver, filter);
取消在onDestroy():
//代码取消注册一个广播接收者
unregisterReceiver(receiver);
receiver = null;
自定义土司显示归属地
演示:目前的土司的缺陷,比如:无法控制消失、界面丑
-----1、看Toast的源代码
-----2、查看toast布局文件的背景目录:\sdk\platforms\android-16\data\res\values\themes.xml;
-----3、实现代码
private WindowManager wm;
public void showMyToast(String address) {
view = new TextView(this);
view.setTextSize(20);
view.setTextColor(Color.RED);
view.setText(address);
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;
wm.addView(view, params);
}
------3、演示发现无法把土司一直停留页面;
------4、处理电话挂断情形
case TelephonyManager.CALL_STATE_IDLE:
if(view != null){
wm.removeView(view);
view = null;
}
break;
更改归属地的背景风格
准备:安装市场类似软件(金山软件)开启另外一个模拟器
-------1、创建布局文件toast_address.xml 图片(ic_menu_call)
-------2、借用市场软件的图片背景(call_locate_gray)
<LinearLayout
android:background="@drawable/call_locate_gray"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_call" />
<TextView
android:id="@+id/tv_location"
android:text="归属地”
android:textSize="20sp"
android:textColor="#000000"
/>
</LinearLayout>
<span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">------3、演示金山的软件的</span>
-------4、基于ui_setting_item_view自定义点击条目(金山里去找\res\\drawable\jiantou1_pressed.png)
-------5、基于SettingItemView 自定义SettingClickView 并只处理标题和内容描述内容;
-------6、在SettingActivity 处理点击事件;
//设置归属地的背景
scv_changebg = (SettingClickView) findViewById(R.id.scv_changebg);
final String [] items = {"半透明","活力橙","卫士蓝","金属灰","苹果绿"};
scv_changebg.setTitle("归属地提示框风格");
int which = sp.getInt("which", 0);
scv_changebg.setDesc(items[which]);
scv_changebg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new Builder(SettingActivity.this);
builder.setTitle("归属地提示框风格");
int which = sp.getInt("which", 0);
builder.setSingleChoiceItems(items, which, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Editor editor = sp.edit();
editor.putInt("which", which);
scv_changebg.setDesc(items[which]);
editor.commit();
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
});
----------7、在实现具体的逻辑
int which = sp.getInt("which", 0);
// "半透明","活力橙","卫士蓝","金属灰","苹果绿"
int [] bgs = {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};
view = (View) View.inflate(this, R.layout.toast_address, null);
view.setBackgroundResource(bgs[which])