android来电归属地提醒

现在市面上常用的一些拨号软件的一个功能,来电归属地。拨号的时候,会在拨号界面出现一个号码归属地的小框框。效果如下:而且这个小窗体还可以自定义风格,并且可以自由移动。这里大概讲下实现的过程。

这个小框框其实就是一个自定义的吐司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>   

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏everhad

[异常特工]android常见bug跟踪

前言 对app的线上bug的收集(友盟、云捕等)有时会得到这样的异常堆栈信息:没有一行代码是有关自身程序代码的。这使得对bug的解决无从下手,根据经验,内存不足...

2045
来自专栏developerHaoz 的安卓之旅

手把手教你从零开始做一个好看的 APP - Day four

本文为 手把手教你从零开始做一个好看的 APP - Day four ,如果想看该系列的其他文章,请点击以下连接

912
来自专栏肖蕾的博客

用这个,自定义日历控件各种效果都不是问题

1954
来自专栏Android知识点总结

Android自定义控件辅助利器之EventParser

932
来自专栏潇涧技术专栏

Head First Android SwipeRefreshLayout

本文内容和代码参考自Implementing Swipe to Refresh, an Android Material Design UI Pattern,原...

1022
来自专栏浅探ARKit

ARKit同时检测水平平面和竖直平面

ARKit1.5里,新增了检测竖直平面的功能。为此特意写一个demo。 下面是效果图: [IMG_3728.PNG] 和之前的水平平面相比 其实就是把多一个属性...

42010
来自专栏java初学

android入门 — ProgressDialog/DatePickerDialog/TimePickerDialog

29011
来自专栏林冠宏的技术文章

三行代码接入,社交软件打字时底下弹出的表情布局,自定义ViewPager+页面点标+各种功能的android小框架。

(转载请声明出处:https://cloud.tencent.com/developer/user/1148436/activities) 前言:       ...

3429
来自专栏向治洪

listview的工作原理

/**      * Unsorted views that can be used by the adapter as a convert vi...

21810
来自专栏郭霖

Android ListView工作原理完全解析,带你从源码的角度彻底理解

在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。ListVie...

36810

扫码关注云+社区

领取腾讯云代金券