主要学习内容:
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 } });
输入框或者其它组件震动与抖动效果实现
-----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>
<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])