##Day06##
#6.1外拨电话显示号码归属地# (重点,复习代码注册广播接受者)
前边实现了来电显示归属地的操作,但是别忘了还有这样一个操作,即拨打电话时,显示号码归属地
思考,既然要在外拨电话显示归属地,那首先要去监听一下外拨电话的事件,监听外拨电话的事件,一般我们都是通过广播实现的,
1.清单文件注册
在清单文件中注册的广播接受者,在程序一安装,广播接受者就会生效,一直有效,程序员无法控制广播的开始和结束
2.代码注册广播接受者
注册广播接受者代码所在的程序运行,广播接受者才会生效,杀死进程,广播接受者就会失效,同时我们是可以去注销广播接受者的,程序员是可以去控制广播接受者的打开和关闭
代码注册广播接受者步骤
1.在addressservice中,创建一个广播接受者
/**
* 外拨电话的广播接受者
* @author Administrator
*
*/
public class MyOutGoingCall extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
}
}
2.在oncreate方法中new一个广播接受者,同时创建一个intentfilter,并注册广播接受者
//注册外拨电话的广播接受者
//需要哪些元素,需要权限:<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
//1.广播接受者
myOutGoingCall = new MyOutGoingCall();
//2.广播的过滤条件
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL");//设置广播接受者要接受的广播事件
//3.注册广播接受者
registerReceiver(myOutGoingCall, intentFilter);
3.在ondestory方法中注销广播接受者
//注销广播接受者
unregisterReceiver(myOutGoingCall);
4.在广播接受者的onreceive方法中执行相应的操作
@Override
public void onReceive(Context context, Intent intent) {
//获取外拨电话的号码,获取广播接受者执行操作的数据
String resultData = getResultData();
//查询号码归属地
String queryAddress = addressDao.queryAddress(resultData, context);
if (!TextUtils.isEmpty(queryAddress)) {
showToast(queryAddress);
}
}
#6.2归属地提示框风格# (重点关注setSingleChoiceItems方法)
1.复制Settingview的自定义控件,布局文件,并修改(将checkbox改成imagview),同时删除关于checkbox及自定义属性的相关操作,在settingclickview中的view.inflate中加载布局文件
2.在设置中心使用
<cn.itcast.mobilesafexian02.ui.SettingClickView
android:id="@+id/scv_setting_changedbg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
></cn.itcast.mobilesafexian02.ui.SettingClickView>
3.在代码中使用
/**
* 更改归属地提示框风格
*/
private void changedBg() {
final String[] items={"半透明","活力橙","卫士蓝","金属灰","苹果绿"};
//初始化自动控件中的控件的值
scv_setting_changedbg.setTitle("归属地提示框风格");
scv_setting_changedbg.setDes(items[sp.getInt("which", 0)]);//根据保存的选项的索引,去动态获取items相应的文本
scv_setting_changedbg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new Builder(SettingActivity.this);//必须告诉dialog应该显示到那个activity中
//设置图标
builder.setIcon(R.drawable.ic_launcher);
//设置标题
builder.setTitle("归属地提示框风格");
//设置单选按钮
//items : 单选按钮的文本的数组
//checkedItem : 当前选中的选项位置
//listener : onclickListener
builder.setSingleChoiceItems(items, sp.getInt("which", 0), new DialogInterface.OnClickListener() {
//which : 点击选中的选择项的索引
@Override
public void onClick(DialogInterface dialog, int which) {
//保存选中的选项
Editor edit = sp.edit();
edit.putInt("which", which);
edit.commit();
//设置自定义控件的描述信息
scv_setting_changedbg.setDes(items[which]);
//隐藏对话框
dialog.dismiss();
}
});
//设置取消按钮
builder.setNegativeButton("取消", null);//null其实就相当于不给按钮设置点击事件,在dialog中会默认执行dialog.dimiss();
//显示dialog
builder.show();
}
});
}
4.根据保存的选项索引,设置addressservice中的showToast方法view的背景
a.
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 };
b.
//设置view对象的背景,根据保存的选项索引从bgcolor图片数组中获取相应的图片
view.setBackgroundResource(bgcolor[sp.getInt("which", 0)]);
#6.3更改归属地提示框显示位置# (params.x的含义)
//设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 100;//x表示的不是坐标,而是距离边框的距离,根据Gravity来设置的,LEFT就是距离左边框的距离,ringht就是距离右边框的距离
params.y = 100;//y和x是相同的含义,根据top或者bottom来时设置距离顶部或者底部的距离
#6.4归属地提示框位置界面#
1.在设置中心中添加归属地提示框位置的条目
2.创建DragViewActivity,清单文件配置,创建界面
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/call_locate_orange"
android:gravity="center_vertical"
android:orientation="horizontal" >
<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:textColor="#ffffff"
android:textSize="18sp" />
</LinearLayout>
<!-- layout_alignParentBottom : 在父控件的下方 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按住提示框拖到任意位置\n按手机返回键立即生效"
android:layout_alignParentBottom="true"
android:textSize="18sp"
android:textColor="#000000"
android:gravity="center"
android:background="@drawable/call_locate_blue"
android:padding="10dp"
/>
</RelativeLayout>
#6.5随着手指移动而移动# (重点!!!)
1.给控件设置触摸监听事件
ll_dragview_toast.setOnTouchListener(new OnTouchListener() {
//v : 控件
//event : 当前执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//获取当前的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下的事件
System.out.println("按下了.....");
break;
case MotionEvent.ACTION_MOVE:
//移动的事件
System.out.println("移动了.....");
break;
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了.....");
break;
}
//True if the listener has consumed the event, false otherwise.
//true:消费了,执行了,false:不执行
//返回true表明我们down已经执行完了,可以执行移动和抬起的事件了
return true;
}
});
2.随着手指移动而移动,按照步骤来
//设置成成员变量是为了在移动的事件能够进行使用
private int startX;
private int startY;
//v : 控件
//event : 当前执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//获取当前的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下的事件
System.out.println("按下了.....");
//1.记录开始的x和y的坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动的事件
System.out.println("移动了.....");
//2.获取新的位置的x和y的坐标
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
//3.计算移动的偏移量
int dX = newX-startX;
int dY = newY-startY;
//4.让控件移动相应的偏移量,重新绘制控件
int l = ll_dragview_toast.getLeft();//获取控件距离屏幕左边框的距离,原控件(没移动前)的距离
int t = ll_dragview_toast.getTop();//获取控件距离屏幕顶部的距离
//移动相应的偏移量
l+=dX;
t+=dY;
//r:表示控件右边框距离屏幕左边框的距离
ll_dragview_toast.layout(l, t, l+ll_dragview_toast.getWidth(), t+ll_dragview_toast.getHeight());//重新绘制控件
//5.更新开始的坐标
startX = newX;
startY = newY;
break;
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了.....");
break;
}
//True if the listener has consumed the event, false otherwise.
//true:消费了,执行了,false:不执行
//返回true表明我们down已经执行完了,可以执行移动和抬起的事件了
return true;
}
#6.6动态设置号码归属地提示框位置#
1.在ontouch的抬起事件中保存控件的坐标
//获取坐标
int x = ll_dragview_toast.getLeft();//获取移动过的控件的距离屏幕左边框的距离
int y = ll_dragview_toast.getTop();
//保存控件的坐标,而不是手指的坐标
Editor edit = sp.edit();
edit.putInt("x", x);
edit.putInt("y", y);
edit.commit();
2.在addressService的显示归属地提示框的方法中根据保存的坐标设置控件的坐标
//设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边
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来时设置距离顶部或者底部的距离
#6.7回显位置# (重点)
android中所有控件,正常显示,分为三步骤
1.测量控件的宽高,oncreate执行完,测量控件才会完成
2.分配控件位置
3.绘制控件
回显操作
1.获取保存的x和y的坐标
int x = sp.getInt("x", 0);
int y = sp.getInt("y", 0);
System.out.println("x:"+x+" y:"+y);
2.获取控件的属性,并设置相应的属性
RelativeLayout.LayoutParams params = (LayoutParams) ll_dragview_toast.getLayoutParams();//获取控件的属性参数
//设置控件距离父控件左边框的距离
params.leftMargin = x;
//设置控件距离父控件顶部的距离
params.topMargin = y;
//重新设置控件的属性
ll_dragview_toast.setLayoutParams(params);
控件变形
原因:因为保存的控件的坐标是左上角的坐标,所以当控件移出屏幕在进行回显操作时,在重新绘制控件时发现现存的空间放不下控件,所以控件自作聪明对自己进行压缩处理,以便让用户能过完全看到自己
解决控件移出屏幕的问题
1.在移动的事件中,绘制控件之前
//判断控件是否移出屏幕
if (l < 0 || r > width || t < 0 || b > height - 25) {
break;
}
因为需要用到屏幕的宽高,所以
2.获取屏幕宽高
//获取屏幕宽度
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
//int width = windowManager.getDefaultDisplay().getWidth();
DisplayMetrics outMetrics = new DisplayMetrics();//白纸
windowManager.getDefaultDisplay().getMetrics(outMetrics);//给白纸指定宽高
width = outMetrics.widthPixels;
height = outMetrics.heightPixels;
#6.8多击事件#(重点,原理)
1.单击事件 : 一组事件的集合,比如按下+抬起表示一个事件
2.触摸事件 : 单个事件,比如按下就是按下事件,抬起就是抬起事件
单击事件和触摸事件一起使用的时候,触摸事件要返回return false,不能返回return true;因为先执行的触摸事件,在执行单击事件,触摸事件中返回fasle表示事件拦截,但是在单击事件中按下的事件其实是单击事件的前半部分,所以为了单击事件的成功,所以在单击事件中把按下的事件处理了
所有根据触摸控件有关的操作,都是先执行触摸事件,在执行其他事件,
其实所有的触摸控件的操作,内部实现都是通过触摸事件来实现的
具体实现
long[] mHits = new long[2];
/**
* 双击居中事件
*/
private void doubleClick() {
ll_dragview_toast.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//拷贝数组的操作
System.arraycopy(mHits, 1, mHits, 0, mHits.length-1);
mHits[mHits.length-1] = SystemClock.uptimeMillis();//获取距离开机时间的毫秒值(手机睡眠的时间)
if (mHits[0] >= (SystemClock.uptimeMillis()-500)) {//判断是否是第三次点击
//双击居中,因为点击控件进行操作的,所以控件已经有了,那就可以去执行重新绘制操作
int l = (width - ll_dragview_toast.getWidth())/2;
int t = (height - ll_dragview_toast.getHeight() -25)/2;
ll_dragview_toast.layout(l, t, l+ll_dragview_toast.getWidth(), t+ll_dragview_toast.getHeight());
//保存坐标,为回显做准备
Editor edit = sp.edit();
edit.putInt("x", l);
edit.putInt("y", t);
edit.commit();
}
}
});
}
#6.9细节的处理#
1.在布局文件中设置两个一模一样的textview,分别显示在父控件的上方和下方
2.在移动事件进行判断
//2.判断是否大于屏幕高度的一半
if (top > height/2) {
//下方隐藏,上方显示
tv_dragview_bottom.setVisibility(View.INVISIBLE);
tv_dragview_top.setVisibility(View.VISIBLE);
}else{
//上方隐藏,下方显示
tv_dragview_top.setVisibility(View.INVISIBLE);
tv_dragview_bottom.setVisibility(View.VISIBLE);
}
3.回显时也进行判断
//设置回显时textivew的显示和隐藏
if (y > height/2) {
//下方隐藏,上方显示
tv_dragview_bottom.setVisibility(View.INVISIBLE);
tv_dragview_top.setVisibility(View.VISIBLE);
}else{
//上方隐藏,下方显示
tv_dragview_top.setVisibility(View.INVISIBLE);
tv_dragview_bottom.setVisibility(View.VISIBLE);
}
#6.10电话界面控件随着手指移动而移动# (重点)
TYPE_PRIORITY_PHONE : 优先于电话的类型
必须添加一个权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
1.在addressService中给view增加触摸事件,修改移动事件中操作
params.x += dX;
params.y += dY;
// 实现坐标和控件实际对应,防止控件坐标移出屏幕,如果params.x移出屏幕,那就设置params.x极限值
if (params.x < 0) {
params.x = 0;
}
if (params.x > width - view.getWidth()) {
params.x = width - view.getWidth();
}
if (params.y < 0) {
params.y = 0;
}
if (params.y > height - view.getHeight() -25 ) {
params.y = height - view.getHeight() -25;
}
//更新控件
windowManager.updateViewLayout(view, params);
2.修改控件的params属性设置,将flags中的不可触摸注释,同时修改type为TYPE_PRIORITY_PHONE,同时增加权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
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天生没有触摸事件
#6.11小火箭#
1.触摸事件
iv_rocket.setOnTouchListener(new OnTouchListener() {
// 设置成成员变量是为了在移动的事件能够进行使用
private int startX;
private int startY;
// v : 控件
// event : 当前执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//开启动画
animationDrawable.start();//在低版本中不太兼容
// 获取当前的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下的事件
System.out.println("按下了.....");
// 1.记录开始的x和y的坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 移动的事件
System.out.println("移动了.....");
// 2.获取新的位置的x和y的坐标
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
// 3.计算移动的偏移量
int dX = newX - startX;
int dY = newY - startY;
// 4.让控件移动相应的偏移量,重新绘制控件
int l = iv_rocket.getLeft();// 获取控件距离父控件左边框的距离,原控件(没移动前)的距离
int t = iv_rocket.getTop();// 获取控件距离父控件顶部的距离
// 移动相应的偏移量
l += dX;
t += dY;
int r = l + iv_rocket.getWidth();
int b = t + iv_rocket.getHeight();
// r:表示控件右边框距离屏幕左边框的距离
iv_rocket.layout(l, t, r, b);// 重新绘制控件
// 5.更新开始的坐标,为了让每次都是新的位置
startX = newX;
startY = newY;
break;
case MotionEvent.ACTION_UP:
// 抬起的事件
//判断是否是底部中间的位置
if (iv_rocket.getTop() > 290 && iv_rocket.getLeft() > 100 && iv_rocket.getLeft() < 200) {
sendRocket();
iv_rocket_m.startAnimation(alphaAnimation);//开始执行动画
iv_rocket_t.startAnimation(alphaAnimation);
}
break;
}
// True if the listener has consumed the event, false otherwise.
// true:消费了,执行了,false:不执行
// 返回true表明我们down已经执行完了,可以执行移动和抬起的事件了
return true;
}
});
2.发送小火箭
/**
* 发射小火箭
*/
protected void sendRocket() {
// 执行操作的时候是在线程中当执行逐渐减少操作的
new Thread() {
public void run() {
for (int i = 0; i < 40; i++) {
SystemClock.sleep(10);
// 运行在主线程,内部封装了一个handler
runOnUiThread(new Runnable() {
@Override
public void run() {
int top = iv_rocket.getTop() - 15;// 是在不断的减小距离顶部的距离
// 重新绘制控件
iv_rocket.layout(iv_rocket.getLeft(), top,
iv_rocket.getLeft() + iv_rocket.getWidth(),
top + iv_rocket.getHeight());
}
});
}
runOnUiThread(new Runnable() {
@Override
public void run() {
iv_rocket_t.clearAnimation();//消除动画
iv_rocket_m.clearAnimation();
}
});
};
}.start();
}
3.帧动画和渐变动画
帧动画
1.res -> anim -> xxx.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"><!-- oneshot : 是否执行一次 true:执行一次 false:循环执行 -->
<item android:drawable="@drawable/desktop_rocket_launch_1" android:duration="200" />
<item android:drawable="@drawable/desktop_rocket_launch_2" android:duration="200" />
</animation-list>
2.控件使用
android:background="@anim/rocket"
3.代码中使用
animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
//开启动画
animationDrawable.start();//在低版本中不太兼容
渐变动画
1.创建一个渐变动画
alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(2000);
2.开启动画
iv_rocket_m.startAnimation(alphaAnimation);//开始执行动画
3.清除动画
iv_rocket_t.clearAnimation();//消除动画