现在市面上常用的一些拨号软件的一个功能,来电归属地。拨号的时候,会在拨号界面出现一个号码归属地的小框框。效果如下:而且这个小窗体还可以自定义风格,并且可以自由移动。这里大概讲下实现的过程。
这个小框框其实就是一个自定义的吐司Toast。吐司是一个特殊的窗体,显示在所有窗体的最上方。归属地查询,其实就是自定义一个吐司,然后注册一个服务,后台监听响铃状态,响铃的时候显示吐司,就达到了归属地的效果。我们知道,吐司默认的界面是黑色的小框体,那么怎么样才能做成这种自定义的透明的加图标的吐司呢?
让我们先来查看一下吐司的源代码。
Toast的里面的最重要的一个方法就是MakeText方法。它的源码如下:
- public static Toast makeText(Context context, CharSequence text, int duration) {
- Toast result = new Toast(context);
- LayoutInflater inflate = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
- TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
- tv.setText(text);
- result.mNextView = v;
- result.mDuration = duration;
- return result;
- }
可以看到吐司的界面view是由布局文件transient_notification inflate来的,也就是说吐司的界面就是在transient_notification中定义的。
下面就去看transient_notification的源码。
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="?android:attr/toastFrameBackground">
- <TextView
- android:id="@android:id/message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@style/TextAppearance.Small"
- android:textColor="@color/bright_foreground_dark"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
- </LinearLayout>
可以看到吐司的一些参数,比如背景图,字体颜色,宽高等。更改这里面的一些参数就可以更改吐司的样式。自定义一些我们比较喜欢的样式。
吐司是怎么显示到屏幕上面的呢?源码里面还有这么一段代码。
- mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
- mParams.gravity = gravity;
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
- mParams.horizontalWeight = 1.0f;
- }
- if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
- mParams.verticalWeight = 1.0f;
- }
- mParams.x = mX;
- mParams.y = mY;
- mParams.verticalMargin = mVerticalMargin;
- mParams.horizontalMargin = mHorizontalMargin;
- mWM.addView(mView, mParams);
- <p><span style="font-size: 18px;">这一段代码就是实现将吐司显示在屏幕上面的。其中的mWM就是窗体管理器,两个参数分别是要显示的view对象和view对象显示在窗体上面需要的一些参数。</span></p><p>
- </p><p></p><p></p><p><span style="font-size: 18px;">下面我们就仿照源码来具体实现一下自定义的来电归属地小窗体的功能。</span></p><p><span style="font-size: 18px;">先自定义窗体的布局文件</span></p>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:orientation="horizontal"
- android:background="@drawable/call_locate_white"
- >
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="50dip"
- android:src="@drawable/toast" />
- <TextView
- android:id="@+id/tv_toast_address"
- android:layout_width="wrap_content"
- android:layout_height="50dip"
- android:text="toast"
- android:textColor="#ffffff"
- android:gravity="center_vertical"
- android:textSize="25sp" />
- </LinearLayout>
然后用布局文件生产view对象
- view = View.inflate(this, R.layout.activity_toast_address, null);
定义一个窗体管理器
- wm = (WindowManager) getSystemService(WINDOW_SERVICE);
根据上面的吐司源码的介绍要将一个view对象添加到窗体,要使用addView方法
- TextView tv_toast_address = (TextView) view.findViewById(R.id.tv_toast_address);
- tv_toast_address.setText(text);//Text为传入的归属地地址
- wm.addView(view, params);//将自定义吐司添加到窗体上
view已经有了,params也可以参考源码里面的params,并且可以自己进行一些修改。
- params = new WindowManager.LayoutParams();//new一个params对象
- params.gravity = Gravity.LEFT + Gravity.TOP;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT; //
- params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- params.format = PixelFormat.TRANSLUCENT;
- params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
- params.setTitle("Toast");
- params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
按照上面的步骤,定义好一个归属地窗体了,但是这个窗体在调用removeView方法前,会一直显示在屏幕上。如何让窗体只在来去电的时候显示呢?
将上面的代码写在服务中,开机启动服务就可以了。但是,这个窗体现在会一直显示在所有界面上面,因为吐司是一个特殊的窗体,会显示在所有窗体的上面。
下面根据来去电两种情况分别进行处理。
来电时:
- // 监听响铃事件 有响铃就吐司
- tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
- listener = new MyPhonestateListener();
- // 监听电话呼叫状态变化
- tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE)
- private class MyPhonestateListener extends PhoneStateListener {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- super.onCallStateChanged(state, incomingNumber);
- switch (state) {
- // 挂断手机时
- case TelephonyManager.CALL_STATE_IDLE:
- if (view != null) { // 移除添加的小窗体
- wm.removeView(view);
- view = null;
- }
- break;
- // 手机响铃时
- case TelephonyManager.CALL_STATE_RINGING:
- String location = AddressDBDao.getAddress(incomingNumber);
- // Toast.makeText(PhoneAddressService.this, location,
- // 1).show();
- showMyToast(location);
- break;
- }
- }
- }
这样就可以在来电响铃的时候显示归属地窗体了。在挂断手机的时候,将归属地窗体移除。
去电,也就是拨号时,系统会发出一个广播,接收这个广播,并在onReceive方法中对归属地小窗体的显示进行控制就可以了
在service服务类中创建一个内部类的广播接收者 当接收到拨号广播时就显示归属地小窗体
- // 定义一个广播接收者
- class InnerReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String number = getResultData();
- String location = AddressDBDao.getAddress(number);
- // Toast.makeText(context, location, 1).show();
- showMyToast(location);
- }
- }
然后在onCreate方法中对广播接收者进行注册。
- // 用代码注册一个广播接收者
- receiver = new InnerReceiver();
- IntentFilter filter = new IntentFilter("android.intent.action.NEW_OUTGOING_CALL");
- registerReceiver(receiver, filter);
根据上面的步骤,就完成了来去电显示归属地小窗体的功能了。
但是目前,这个小窗体还不能移动,只能在上面params中定义好的位置,要使窗体能够移动,还要对窗体的view进行处理。
窗体移动的原理其实就是手指在屏幕上移动的时候分别记录手指在x轴,y轴移动的距离,同时将归属地窗体也移动相应的距离,然后更新窗体的实时位置,并初始化手机的位置。最后还要对窗体离边框的距离进行处理。否则,归属地窗体会移出x轴,不符合实际情况。对窗体的坐标进行一些逻辑判断,最后代码如下:
- // 为自定义窗体设置一个触摸监听器
- view.setOnTouchListener(new OnTouchListener() {
- private int startX = 0;
- private int startY = 0;
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:// 手指触摸到屏幕时执行的方法
- startX = (int) event.getRawX();
- startY = (int) event.getRawY();
- break;
- case MotionEvent.ACTION_MOVE:// 手指在屏幕上移动时执行的方法
- // 计算手指在屏幕上移动的位移
- int newX = (int) event.getRawX();
- int newY = (int) event.getRawY();
- int dx = newX - startX;
- int dy = newY - startY;
- // 将框体也移动相应的位置即可
- if(params.x<0){
- params.x = 0;
- }
- if(params.y<0){
- params.y = 0;
- }
- if(params.x > (wm.getDefaultDisplay().getWidth()-params.width)){
- params.x = wm.getDefaultDisplay().getWidth()-params.width;
- }
- if(params.y >(wm.getDefaultDisplay().getWidth()-params.width)){
- params.y = wm.getDefaultDisplay().getWidth()-params.width;
- }
- params.x += dx;
- params.y += dy;
- wm.updateViewLayout(view, params);//更新窗体位置
- // 初始化手指的位置
- startX = (int) event.getRawX();
- startY = (int) event.getRawY();
- break;
- case MotionEvent.ACTION_UP:// 手指离开屏幕时执行的方法
- break;
- default:
- break;
- }
- return false;
- }
- });
当然还可以设置一个变量值,根据不同的值为窗体设置不同的背景,这就是换肤功能。这里就不具体说明了。
最后,服务结束的时候,还要取消注册监听器和广播接收者。
- public void onDestroy() {
- super.onDestroy();
- tm.listen(listener, PhoneStateListener.LISTEN_NONE);
- listener = null;
- unregisterReceiver(receiver);
- receiver = null;
- }
到这里,一个可移动的来去电归属地小窗体的功能就实现了。
效果图:
- <pre code_snippet_id="147480" snippet_file_name="blog_20140108_4_4003010"></pre>
- <pre></pre>