【0065】【项目实战】-【手机安全卫士】-【5】高级工具模块
1. 高级工具之归属地查询功能分析

【说明】
【1】在按钮选择之后会存在阴影效果;
【2】在输入不同的号码之后会直接显示不同的归属地;




【如果是空号查询】如果是空号查询则在输入电话号框的前部会出现左右抖动的效果;

2.主页面跳转到归属地查询页面

【新建类】


【清单文件的配置】

【布局文件的加载】



【源码】

【效果】

3. 归属地查询界面



【布局】/mobilesafe74/res/layout/activity_query_address.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 <TextView 7 style="@style/TitleStyle" 8 android:text="归属地查询"/> 9 <EditText 10 android:id="@+id/et_phone" 11 android:hint="请输入电话号码" 12 android:inputType="phone" 13 android:layout_width="match_parent" 14 android:layout_height="wrap_content"/> 15 <Button 16 android:id="@+id/bt_query" 17 android:text="查询" 18 android:layout_width="wrap_content" 19 android:layout_height="wrap_content"> 20 </Button> 21 <TextView 22 android:id="@+id/tv_query_result" 23 android:layout_width="wrap_content" 24 android:layout_height="wrap_content"/> 25 </LinearLayout>
【效果测试】

4. 数据库的操作
4.1查询界面的数据库结构分析
【说明】两张表的其实功能是一样的,但是address.db是对telocation的优化;

【表1】数据比较大;


【表2】经过优化关联


4.2 数据库拷贝过程



【源码】
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 //去除掉当前activity头title 5 // requestWindowFeature(Window.FEATURE_NO_TITLE); 6 setContentView(R.layout.activity_splash); 7 8 //初始化UI 9 initUI(); 10 //初始化数据 11 initData(); 12 //初始化动画 13 initAnimation(); 14 //初始化数据库 15 initDB(); 16 } 17 18 private void initDB() { 19 //1,归属地数据拷贝过程 20 initAddressDB("address.db"); 21 } 22 23 /** 24 * 拷贝数据库值files文件夹下 25 * @param dbName 数据库名称 26 */ 27 private void initAddressDB(String dbName) { 28 //1,在files文件夹下创建同名dbName数据库文件过程 29 File files = getFilesDir(); 30 File file = new File(files, dbName); 31 if(file.exists()){ 32 return; 33 } 34 InputStream stream = null; 35 FileOutputStream fos = null; 36 //2,输入流读取第三方资产目录下的文件 37 try { 38 stream = getAssets().open(dbName); 39 //3,将读取的内容写入到指定文件夹的文件中去 40 fos = new FileOutputStream(file); 41 //4,每次的读取内容大小 42 byte[] bs = new byte[1024]; 43 int temp = -1; 44 while( (temp = stream.read(bs))!=-1){ 45 fos.write(bs, 0, temp); 46 } 47 } catch (Exception e) { 48 e.printStackTrace(); 49 }finally{ 50 if(stream!=null && fos!=null){ 51 try { 52 stream.close(); 53 fos.close(); 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } 57 } 58 } 59 60 }
【效果】在splash界面执行结束之后会将数据库文件拷贝到当前项目的files文件夹下;

4.3 address数据库data1查询过程

【说明】在第一张表data1中查询外键;

【源码】查询第一张表data的外键;

【效果】外键的查询

4.4 关联查询data2表获取归属地

【测试代码】


4.5 查询代码的优化
【1】对数据的位数的判断


【问题的解决】
需要考虑的问题:可能出现的号码为:七个1,七个字母等等;



【源码】需要返回值String
1 package com.itheima.mobilesafe74.engine; 2 3 import android.database.Cursor; 4 import android.database.sqlite.SQLiteDatabase; 5 import android.util.Log; 6 7 public class AddressDao { 8 private static final String tag = "AddressDao"; 9 //1,指定访问数据库的路径 10 public static String path = "data/data/com.itheima.mobilesafe74/files/address.db"; 11 private static String mAddress = "未知号码"; 12 /**传递一个电话号码,开启数据库连接,进行访问,返回一个归属地 13 * @param phone 查询电话号码 14 */ 15 public static String getAddress(String phone){ //需要返回值String 16 mAddress = "未知号码"; 17 //正则表达式,匹配手机号码 18 //手机号码的正则表达式 19 String regularExpression = "^1[3-8]\\d{9}"; 20 //2,开启数据库连接(只读的形式打开) 21 SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY); 22 if(phone.matches(regularExpression)){ 23 phone = phone.substring(0,7); 24 //3,数据库查询 25 Cursor cursor = db.query("data1", new String[]{"outkey"}, "id = ?", new String[]{phone}, null, null, null); 26 //4,查到即可 27 if(cursor.moveToNext()){ 28 String outkey = cursor.getString(0); 29 Log.i(tag, "outkey = "+outkey); 30 //5,通过data1查询到的结果,作为外键查询data2 31 Cursor indexCursor = db.query("data2", new String[]{"location"}, "id = ?", new String[]{outkey}, null, null, null); 32 if(indexCursor.moveToNext()){ 33 //6,获取查询到的电话归属地 34 mAddress = indexCursor.getString(0); 35 Log.i(tag, "address = "+mAddress); 36 } 37 }else{ 38 mAddress = "未知号码"; 39 } 40 }else{ 41 int length = phone.length(); 42 switch (length) { 43 case 3://119 110 120 114 44 mAddress = "报警电话"; 45 break; 46 case 4://119 110 120 114 47 mAddress = "模拟器"; 48 break; 49 case 5://10086 99555 50 mAddress = "服务电话"; 51 break; 52 case 7: 53 mAddress = "本地电话"; 54 break; 55 case 8: 56 mAddress = "本地电话"; 57 break; 58 case 11: 59 //(3+8) 区号+座机号码(外地),查询data2 60 String area = phone.substring(1, 3); 61 Cursor cursor = db.query("data2", new String[]{"location"}, "area = ?", new String[]{area}, null, null, null); 62 if(cursor.moveToNext()){ 63 mAddress = cursor.getString(0); 64 }else{ 65 mAddress = "未知号码"; 66 } 67 break; 68 case 12: 69 //(4+8) 区号(0791(江西南昌))+座机号码(外地),查询data2 70 String area1 = phone.substring(1, 4); 71 Cursor cursor1 = db.query("data2", new String[]{"location"}, "area = ?", new String[]{area1}, null, null, null); 72 if(cursor1.moveToNext()){ 73 mAddress = cursor1.getString(0); 74 }else{ 75 mAddress = "未知号码"; 76 } 77 break; 78 } 79 } 80 return mAddress; 81 } 82 }
4.7 点击查询号码归属地
【说明】
【1】查询属于耗时操作,需要在子线程中进行查询;
【2】子线程的数据想要发送给主线程,需要使用Handler消息机制;
1 package com.itheima.mobilesafe74.activity; 2 3 import com.itheima.mobilesafe74.R; 4 import com.itheima.mobilesafe74.engine.AddressDao; 5 6 import android.app.Activity; 7 import android.os.Bundle; 8 import android.os.Handler; 9 import android.text.Editable; 10 import android.text.TextUtils; 11 import android.text.TextWatcher; 12 import android.view.View; 13 import android.view.View.OnClickListener; 14 import android.view.animation.Animation; 15 import android.view.animation.AnimationUtils; 16 import android.view.animation.Interpolator; 17 import android.widget.Button; 18 import android.widget.EditText; 19 import android.widget.TextView; 20 21 public class QueryAddressActivity extends Activity { 22 private EditText et_phone; 23 private Button bt_query; 24 private TextView tv_query_result; 25 private String mAddress; 26 private Handler mHandler = new Handler(){ 27 public void handleMessage(android.os.Message msg) { 28 //4,控件使用查询结果 29 tv_query_result.setText(mAddress); 30 }; 31 }; 32 33 @Override 34 protected void onCreate(Bundle savedInstanceState) { 35 super.onCreate(savedInstanceState); 36 setContentView(R.layout.activity_query_address); 37 38 initUI(); 39 } 40 41 private void initUI() { 42 et_phone = (EditText) findViewById(R.id.et_phone); 43 bt_query = (Button) findViewById(R.id.bt_query); 44 tv_query_result = (TextView) findViewById(R.id.tv_query_result); 45 46 //1,点查询功能,注册按钮的点击事件 47 bt_query.setOnClickListener(new OnClickListener() { 48 @Override 49 public void onClick(View v) { 50 String phone = et_phone.getText().toString(); 52 //2,查询是耗时操作,开启子线程 53 query(phone); 73 } 74 75 } 76 }); 77 99 /** 100 * 耗时操作 101 * 获取电话号码归属地 102 * @param phone 查询电话号码 103 */ 104 protected void query(final String phone) { 105 new Thread(){ 106 public void run() { 107 mAddress = AddressDao.getAddress(phone); 108 //3,消息机制,告知主线程查询结束,可以去使用查询结果 109 mHandler.sendEmptyMessage(0); 110 }; 111 }.start(); 112 } 113 }
【测试效果】

5 实时查询归属地
【源码】输入数字之后实时查询归属地;
1 //5,实时查询(监听输入框文本变化) 2 et_phone.addTextChangedListener(new TextWatcher() { 3 @Override 4 public void onTextChanged(CharSequence s, int start, int before, int count) { 5 6 } 7 8 @Override 9 public void beforeTextChanged(CharSequence s, int start, int count, 10 int after) { 11 12 } 13 14 @Override 15 public void afterTextChanged(Editable s) { 16 String phone = et_phone.getText().toString(); 17 query(phone); 18 } 19 });


【BUG】

【原因】原因是在上一次查询的结果会保留下来,删除也不会更改;

【修改】在每次查询之前都设置为“未知号码”

6 apiDemo抖动效果使用
【说明】在内容为空的时候进行查询,在输入框的头部会出现抖动的效果;



【抖动的效果】

【原理】使用api的抖动的原理
【搜索关键字】在源码包中所有关键字







【项目中使用抖动效果的源码】

【报错】缺少动画的布局文件



【效果】

7 插补器概念

【interpolator】是按照正弦曲线进行抖动的;

【根据自己绘制的效果进行抖动】


8. 手机的震动效果


【有规律的震动】

8.归属地悬浮窗
8.1 效果
【具有以下的5大功能】



【位置的确定】



8.2 服务中电话状态的监听
8.2.1 布局条目的增加
【说明】在悬浮窗的弹出需要:设置是否弹出+显示风格+弹出的位置的设定;

【布局】

【效果】

【选择的代码逻辑】

8.2.2 电话监听的逻辑

【服务的创建】



【服务的开启和关闭】

【服务的框架】

【服务显示时刻】


【监控的逻辑】/mobilesafe74/src/com/itheima/mobilesafe74/service/AddressService.java
1 @Override 2 public void onCreate() { 3 //第一次开启服务以后,就需要去管理吐司的显示 4 //电话状态的监听(服务开启的时候,需要去做监听,关闭的时候电话状态就不需要监听) 5 //1,电话管理者对象 6 mTM = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 7 //2,监听电话状态 8 mPhoneStateListener = new MyPhoneStateListener(); 9 mTM.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 10 11 super.onCreate(); 12 } 13 14 class MyPhoneStateListener extends PhoneStateListener{ 15 //3,手动重写,电话状态发生改变会触发的方法 16 @Override 17 public void onCallStateChanged(int state, String incomingNumber) { 18 switch (state) { 19 case TelephonyManager.CALL_STATE_IDLE: 20 //空闲状态,没有任何活动(移除吐司) 21 Log.i(tag, "挂断电话,空闲了......................."); 22 break; 23 case TelephonyManager.CALL_STATE_OFFHOOK: 24 //摘机状态,至少有个电话活动。该活动或是拨打(dialing)或是通话 25 break; 26 case TelephonyManager.CALL_STATE_RINGING: 27 //响铃(展示吐司) 28 Log.i(tag, "响铃了......................."); 29 break; 30 } 31 super.onCallStateChanged(state, incomingNumber); 32 } 33 }
【监控逻辑的测试】

【状态的监听的顺序】
【说明】在点击开启归属地的设置之后,立刻会对电话的状态进行监听;因为服务的onCreate方法已经执行;

【代码执行的流程】

【响铃的状态】拨打电话->响铃的状态->挂断电话->空闲状态;

8.2.4 服务关闭-监听的关闭的修正
【问题】即使关闭了服务,监听动作仍然存在;
【正确的状态】服务按钮开启,服务开启,监听开启;
服务关闭,监听关闭;


【源码修改】

【测试效果】确实不再监听,不会出现监听的打印信息;

8.3 响铃时土司的弹出

【测试效果】


8.4 服务和归属地显示状态的绑定
【问题】之前保存的设置的状态没有进行回显
【问题分析】不能使用sp保存,因为存在在内存紧张的时候,系统会杀死后台的进程,因此监听服务可能会被杀死,但是在sp中读取的状态是设置好的;
因此:出现了设置回显的状态和服务的状态不一致的情况;
app会出现BUG;
【正确的方式】将服务和设置的值进行绑定;

【源码的实现】创建类,专门用于判断服务是否还存在后台运行;


【注意】包名需要是完整的包名;



【效果】开启之后可以再次回显,关闭之后进入之后可以再次回显;绑定了服务;


8.5 查看源码定义吐司样式布局
【源码的查看】


【代码的移植】


【土司的布局文件】






【源码】在代码移植过来之后进行了修改;

【效果】

【BUG】土司在停止来电的时候仍然存在;


【效果】 在开启弹出土司设置后,土司弹出;挂掉电话之后土司消失;

8.6 来电归属地查询


【效果】



9.选择吐司样式自定义组合控件
9.1【效果】
【1】出现单选框;
【2】选择的样式与上面的样式不同;没有checkBox;选择之后不必要切换;

9.2 样式的源码的修改
9.2.1 样式的修改
【说明】直接复制SettingItemView,名称改为SettingClickView;然后修改内容即可;

【源码】/mobilesafe74/src/com/itheima/mobilesafe74/view/SettingClickView.java
1 package com.itheima.mobilesafe74.view; 2 3 import com.itheima.mobilesafe74.R; 4 import android.content.Context; 5 import android.util.AttributeSet; 6 import android.util.Log; 7 import android.view.View; 8 import android.widget.CheckBox; 9 import android.widget.RelativeLayout; 10 import android.widget.TextView; 11 12 public class SettingClickView extends RelativeLayout { 13 private TextView tv_des; 14 private TextView tv_title; 15 16 public SettingClickView(Context context) { 17 this(context,null); 18 } 19 20 public SettingClickView(Context context, AttributeSet attrs) { 21 this(context, attrs,0); 22 } 23 24 public SettingClickView(Context context, AttributeSet attrs, int defStyle) { 25 super(context, attrs, defStyle); 26 //xml--->view 将设置界面的一个条目转换成view对象,直接添加到了当前SettingItemView对应的view中 27 View.inflate(context, R.layout.setting_click_view, this); 28 29 //自定义组合控件中的标题描述 30 tv_title = (TextView) findViewById(R.id.tv_title); 31 tv_des = (TextView) findViewById(R.id.tv_des); 32 } 33 34 /** 35 * @param title 设置标题内容 36 */ 37 public void setTitle(String title){ 38 tv_title.setText(title); 39 } 40 41 /** 42 * @param des 设置描述内容 43 */ 44 public void setDes(String des){ 45 tv_des.setText(des); 46 } 47 48 }

【源码】/mobilesafe74/res/layout/setting_click_view.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" > 5 <RelativeLayout 6 android:padding="5dp" 7 android:layout_width="match_parent" 8 android:layout_height="wrap_content"> 9 <TextView 10 android:id="@+id/tv_title" 11 android:textColor="#000" 12 android:textSize="18sp" 13 android:layout_width="wrap_content" 14 android:layout_height="wrap_content"/> 15 <TextView 16 android:id="@+id/tv_des" 17 android:layout_below="@id/tv_title" 18 android:textColor="#000" 19 android:textSize="18sp" 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content"/> 22 <ImageView 23 android:id="@+id/iv_image" 24 android:background="@drawable/jiantou1_pressed" 25 android:layout_alignParentRight="true" 26 android:layout_centerVertical="true" 27 android:layout_width="wrap_content" 28 android:layout_height="wrap_content"/> 29 <View 30 android:background="#000" 31 android:layout_below="@id/tv_des" 32 android:layout_width="match_parent" 33 android:layout_height="1dp"/> 34 </RelativeLayout> 35 </RelativeLayout>

【效果】

9.2.2 内容的修改
【源码】/mobilesafe74/src/com/itheima/mobilesafe74/utils/SpUtil.java
【说明】增加putInt()方法和getInt()方法进行整数的sp存储;
1 /** 2 * 写入boolean变量至sp中 3 * @param ctx 上下文环境 4 * @param key 存储节点名称 5 * @param value 存储节点的值string 6 */ 7 public static void putInt(Context ctx,String key,int value){ 8 //(存储节点文件名称,读写方式) 9 if(sp == null){ 10 sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE); 11 } 12 sp.edit().putInt(key, value).commit(); 13 } 14 /** 15 * 读取boolean标示从sp中 16 * @param ctx 上下文环境 17 * @param key 存储节点名称 18 * @param defValue 没有此节点默认值 19 * @return 默认值或者此节点读取到的结果 20 */ 21 public static int getInt(Context ctx,String key,int defValue){ 22 //(存储节点文件名称,读写方式) 23 if(sp == null){ 24 sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE); 25 } 26 return sp.getInt(key, defValue); 27 }


【效果】

9.2.3 吐司样式单选框设置
1 private void initToastStyle() { 2 scv_toast_style = (SettingClickView) findViewById(R.id.scv_toast_style); 3 //话述(产品) 4 scv_toast_style.setTitle("设置归属地显示风格"); 5 //1,创建描述文字所在的string类型数组 6 mToastStyleDes = new String[]{"透明","橙色","蓝色","灰色","绿色"}; 7 //2,SP获取吐司显示样式的索引值(int),用于获取描述文字 8 9 mToastStyle = SpUtil.getInt(this, ConstantValue.TOAST_STYLE, 0); 10 11 //3,通过索引,获取字符串数组中的文字,显示给描述内容控件 12 scv_toast_style.setDes(mToastStyleDes[mToastStyle]); 13 //4,监听点击事件,弹出对话框 14 scv_toast_style.setOnClickListener(new OnClickListener() { 15 @Override 16 public void onClick(View v) { 17 //5,显示吐司样式的对话框 18 showToastStyleDialog(); 19 } 20 }); 21 } 22 23 /** 24 * 创建选中显示样式的对话框 25 */ 26 protected void showToastStyleDialog() { 27 Builder builder = new AlertDialog.Builder(this); 28 builder.setIcon(R.drawable.ic_launcher); 29 builder.setTitle("请选择归属地样式"); 30 //选择单个条目事件监听 31 /* 32 * 1:string类型的数组描述颜色文字数组 33 * 2:弹出对画框的时候的选中条目索引值 34 * 3:点击某一个条目后触发的点击事件 35 * */ 36 builder.setSingleChoiceItems(mToastStyleDes, mToastStyle, new DialogInterface.OnClickListener() { 37 @Override 38 public void onClick(DialogInterface dialog, int which) {//which选中的索引值 39 //(1,记录选中的索引值,2,关闭对话框,3,显示选中色值文字) 点击之后要发生的事件 40 SpUtil.putInt(getApplicationContext(), ConstantValue.TOAST_STYLE, which); 41 dialog.dismiss(); 42 scv_toast_style.setDes(mToastStyleDes[which]); 43 } 44 }); 45 //消极按钮 46 builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { 47 @Override 48 public void onClick(DialogInterface dialog, int which) { 49 dialog.dismiss(); 50 } 51 }); 52 builder.show(); //对话框必须显示 53 }
【效果】

9.2.4 吐司显示样式修改
【原理】将选择框的选择之后的int值读取来,然后根据值选择对应的图片背景作为TextView的背景显示;
服务中进行图片的匹配;




【效果】

10 吐司显示样式修改
10.1 效果
【归属地的提示框设置】可以复用SettingclickView自定义控件;

【弹出一个Activity】透明样式的Activity,可以看到底部的文字;



【需要完成的功能】

10.2 自定义布局的控件


【新建类】定义土司所在位置的Activity;



【效果的原理】
【1】显示的效果如下,是一个半透明的Activity;
【2】中间的可移动的区域块,在移动超过半边界的时候,上/下的条目有一个会显示,一个会隐藏,其实两个都存在;所谓的障眼法;



【布局的选择】因为要重叠,因此使用相对布局或者帧布局;

【布局源码】
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#accc"> 6 <!-- imageView在相对布局中,所以其所在位置的规则需要由相对布局提供 --> 7 <ImageView 8 android:id="@+id/iv_drag" 9 android:background="@drawable/drag" 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content"/> 12 <Button 13 android:id="@+id/bt_top" 14 android:layout_width="match_parent" 15 android:text="按中提示框拖拽到任意位置" 16 android:gravity="center" 17 android:visibility="invisible" 18 android:background="@drawable/call_locate_blue" 19 android:layout_height="wrap_content"/> 20 <Button 21 android:layout_alignParentBottom="true" 22 android:id="@+id/bt_bottom" 23 android:layout_width="match_parent" 24 android:text="按中提示框拖拽到任意位置" 25 android:gravity="center" 26 android:visibility="visible" 27 android:background="@drawable/call_locate_blue" 28 android:layout_height="wrap_content"/> 29 </RelativeLayout>

【设置布局的透明效果】


【修改为灰色的效果】


【修改为灰色半透明的状态】


10.3 拖拽过程中坐标的移动计算规则
【计算移动之后的位置】维护好x和y坐标,就可以计算出移动的距离;
10.3.1【拖拽功能的完成】





【源码】
1 package com.itheima.mobilesafe74.activity; 2 3 import com.itheima.mobilesafe74.R; 4 import com.itheima.mobilesafe74.utils.ConstantValue; 5 import com.itheima.mobilesafe74.utils.SpUtil; 6 7 import android.app.Activity; 8 import android.os.Bundle; 9 import android.view.MotionEvent; 10 import android.view.View; 11 import android.view.View.OnTouchListener; 12 import android.view.WindowManager; 13 import android.widget.Button; 14 import android.widget.ImageView; 15 import android.widget.RelativeLayout; 16 import android.widget.RelativeLayout.LayoutParams; 17 18 public class ToastLocationActivity extends Activity { 19 private ImageView iv_drag; 20 private Button bt_top,bt_bottom; 21 private WindowManager mWM; 22 private int mScreenHeight; 23 private int mScreenWidth; 24 25 26 @Override 27 protected void onCreate(Bundle savedInstanceState) { 28 super.onCreate(savedInstanceState); 29 setContentView(R.layout.activity_toast_location); 30 31 initUI(); 32 } 33 34 private void initUI() { 35 //可拖拽双击居中的图片控件 36 iv_drag = (ImageView) findViewById(R.id.iv_drag); 37 bt_top = (Button) findViewById(R.id.bt_top); 38 bt_bottom = (Button) findViewById(R.id.bt_bottom); 39 40 41 int locationX = SpUtil.getInt(getApplicationContext(), ConstantValue.LOCATION_X, 0); 42 int locationY = SpUtil.getInt(getApplicationContext(), ConstantValue.LOCATION_Y, 0); 43 44 45 //监听某一个控件的拖拽过程(按下(1),移动(多次),抬起(1)) 46 iv_drag.setOnTouchListener(new OnTouchListener() { 47 private int startX; 48 private int startY; 49 //对不同的事件做不同的逻辑处理 50 @Override 51 public boolean onTouch(View v, MotionEvent event) { 52 switch (event.getAction()) { 53 case MotionEvent.ACTION_DOWN: 54 startX = (int) event.getRawX(); 55 startY = (int) event.getRawY(); 56 break; 57 case MotionEvent.ACTION_MOVE: 58 int moveX = (int) event.getRawX(); 59 int moveY = (int) event.getRawY(); 60 61 int disX = moveX-startX; 62 int disY = moveY-startY; 63 64 //1,当前控件所在屏幕的(左,上)角的位置 65 int left = iv_drag.getLeft()+disX;//左侧坐标 66 int top = iv_drag.getTop()+disY;//顶端坐标 67 int right = iv_drag.getRight()+disX;//右侧坐标 68 int bottom = iv_drag.getBottom()+disY;//底部坐标 69 70 71 //2,告知移动的控件,按计算出来的坐标去做展示 72 iv_drag.layout(left, top, right, bottom); 73 74 //3,重置一次其实坐标 75 startX = (int) event.getRawX(); 76 startY = (int) event.getRawY(); 77 78 break; 79 case MotionEvent.ACTION_UP: 80 81 } 82 //在当前的情况下返回false不响应事件,返回true才会响应事件 83 return true; 84 } 85 }); 86 } 87 }
10.3.2【记录坐标的位置】
在每次放置后,再次进入之后需要读取上次放置的位置;
【问题】重新打开后位置恢复到了原来的位置;只需要记录左上角的位置即可;




【效果】

10.3.3 容错的处理
坐标位置的修正


【源码】
1 case MotionEvent.ACTION_MOVE: 2 int moveX = (int) event.getRawX(); 3 int moveY = (int) event.getRawY(); 4 5 int disX = moveX-startX; 6 int disY = moveY-startY; 7 8 //1,当前控件所在屏幕的(左,上)角的位置 9 int left = iv_drag.getLeft()+disX;//左侧坐标 10 int top = iv_drag.getTop()+disY;//顶端坐标 11 int right = iv_drag.getRight()+disX;//右侧坐标 12 int bottom = iv_drag.getBottom()+disY;//底部坐标 13 14 //容错处理(iv_drag不能拖拽出手机屏幕) 15 //左边缘不能超出屏幕 16 if(left<0){ 17 return true; 18 } 19 20 //右边边缘不能超出屏幕 21 if(right>mScreenWidth){ 22 return true; 23 } 24 25 //上边缘不能超出屏幕可现实区域 26 if(top<0){ 27 return true; 28 } 29 30 //下边缘(屏幕的高度-22 = 底边缘显示最大值) 31 if(bottom>mScreenHeight - 22){ 32 return true; 33 } 34
【效果】不会超出边缘

10.4 描述文字的位置修改
【源码】

【效果】

【判断条件不够严格】需要将状态栏也考虑进来,drag的二分之一与(屏幕减去状态栏的高度的结果的二分之一进行比较)

【bug】开始进行应用的时候也需要进行位置的判断考虑;不然再次出现的位置即使过了下半部分也会出现在下半部分;


【效果】
10.5 双击事件demo

【效果】双击之后弹吐司;

10.6 多击事件demo




【源码移植】

【三击事件判断的原理】数组中的值依次向前移,然后依次判断最后一位的时间值减去第一位的时间值是否满足最小的时间间隔;


【效果】

11 双击事件在项目中的应用



【说明】注意在既响应拖拽事件又响应点击事件,需要将拖拽事件的返回值改为false;

【效果】双击之后居中,并且可以响应点击事件;

【设置返回false的原因】


12. 服务中处理吐司的拖拽事件
12.1 双击居中之后存储位置

【效果】

12.2 位置设置值的读取
【源码】


12.3 来电显示的拖拽位置的同步


【位置的范围的限定】

【效果展示】


浙公网安备 33010602011771号