Day03##
Day03 02.设置向导第二个界面&样式抽取# ***
手机防盗第二个界面的activity_setup2.xml布局文件也有下一步按钮
相同操作可向上抽取,不是抽取到自定义控件中,而是抽取到样式文件当中
1.样式抽取步骤:
1)打开res -> values -> styles(模仿上边系统的写法,对比下一步按钮)形成样式抽取
<style name="next">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:text">下一步</item>
<item name="android:layout_alignParentRight">true</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:drawableRight">@drawable/next</item>
<item name="android:background">@drawable/button</item>
<item name="android:padding">5dp</item>
<item name="android:onClick">next</item>
</style>
2)布局文件控件中使用(找到每一个被样式抽取的button按钮,将属性全部删除后添加如下)
<Button
style="@style/next"
/>
2.在SetUp2Activity中实现上一步/下一步点击事件
public void pre(View v){
//跳转到第一个界面(1.欢迎使用手机防盗)
Intent intent = new Intent(this,SetUpActivity1.class);
StartActivity(intent);
}
public void next(View v){
//跳转到第三个界面
}
一般id不会抽取,id是在布局文件中的唯一标识,但也可以抽取,因为id在不同布局文件中可以相同
上一步的抽取和下一步一样,以上就是将布局文件中的一些属性抽取到样式文件中的操作
Day03 03.设置向导第三个和第四个界面# **
在控件中使用与样式文件中相同的属性,即可覆盖样式文件中相应属性值
1.跳转到第三个界面SetUp3Activity,布局文件选择联系人按钮需添加状态选择器(正常的和按下的图片)
复制button.xml,名称改为selector_contact_button.xml
其中的两张图片修改成需要的即可,修改后如下:
selector_contact_button.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/btn_green_pressed" /> <!-- pressed 按下-->
<item android:drawable="@drawable/btn_green_normal" /> <!-- default 默认图片-->
</selector>
2.将状态选择器设置给activity_setup3.xml中的button按钮
如下添加属性:android:background="@drawable/selector_contact_button"
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择联系人"
android:background="@drawable/selector_contact_button"
android:onClick="selectContact"
/>
.9图片是防止图片被拉伸的,eclipse布局预览界面对.9图片是不支持的
运行时出现AndroidRuntime java.lang.RuntimeException:Unable to instantiate application android.app.Application:java.lang.NullPointerException(空指针异常)
这是因为通过Run As或点击斜三角运行的时候,相当于将App卸载掉,重新将最新的安装上去
前面已经打开的(后来被卸载)那个app就会报空指针异常,这个不用在意,在模拟器上测试安装的时候会经常出现这个问题
3.运行成功后,继续实现第三个界面的上一步和下一步点击事件,拷贝第二个界面修改即可
public void pre(View v){
//跳转到第二个界面(1.欢迎使用手机防盗)
Intent intent = new Intent(this,SetUpActivity2.class);
StartActivity(intent);
}
public void next(View v){
//跳转到第四个界面
Intent intent = new Intent(this,SetUpActivity4.class);
startActivity(intent);
}
同理,跳转到第四个界面SetUp4Activity
在activity_setup4.xml中下一步按钮需要修改成“设置完成”
下一步用的样式文件,在样式文件中设置了text文本是下一步
现在要改一下下一步的文本是设置完成了,如果在样式文件中改的话
前面第二个第三个界面都会变成设置完成,所以可以这样改
在第四个界面的button中,增加属性 android:text="设置完成"即可
即在控件中使用与样式文件中相同的属性,就可以覆盖样式文件中相应属性的值
小图片也不需要了,那就添加属性 android:drawableRight="@null"即可
<Button style="@style/next"
android:text="设置完成"
android:drawableRight="@null"
/>
Day03 04.界面切换逻辑处理# *
问题1:
上面已经将四个界面都实现了,运行时回到第一个界面,再点击返回键应该回到主界面,但它倒着走了一遍才回到主界面
原因:
setUp1Activity跳转到第二个界面时是通过startActivity(intent)跳转
相当于把这个activity存放到任务栈当中了
在setUp2Activity中又通过startActivity(intent)跳转了,所以才会出现这个问题
解决:
在4个activity,每一个跳转处添加finish()
即当跳转到下一个界面时,把上一个界面隐藏掉就可以了
引出问题2:
第一个界面跳转到第二个,第二个点击物理返回键应回到第一个,但直接回到主界面了
解决:
将每个界面的上一步下一步功能向上抽取到基类
1)创建基类SetUpBaseActivity
2)将4个activity中每个的上一步下一步操作
public void pre(View v){
//跳转到第一个界面
Intent intent = new Intent(this,SetUp1Activity.class);
StartActivity(intent);
finish();
}
public void next(View v){
//跳转到第三个界面
Intent intent = new Intent(this,SetUp3Activity.class);
StartActivity(intent);
finish();
}
抽取到基类SetUpBaseActivity中,并创建两个抽象类让子类实现具体操作
//每个按钮实现的操作不一样,才在点击事件中调用抽象方法来实现响应操作
public void pre(View v){
pre_activity();
}
public void next(View v){
next_activity();
}
//父类不知道下一步和上一步具体代码,所以写一个抽象类让子类实现后根据自己的特性实现各自的操作
//下一步
public abstract void next_activity();
//上一步
public abstract void pre_activity();
3)让SetUp1Activity继承SetUpBaseActivity实现相应操作:
public class SetUp1Activity extends SetUpBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setup1);
}
@Override
public void next_activity() {
//跳转到第二个界面
Intent intent = new Intent(this,SetUp2Activity.class);
startActivity(intent);
finish();
}
@Override
public void pre_activity() {
// TODO Auto-generated method stub
}
}
同理,让SetUp2Activity,SetUp3Activity,SetUp4Activity都继承SetUpBaseActivity并实现相应的操作
子类点击下一步按钮后执行的是基类的next方法,执行next时又是执行的它里边的抽象类next_activity(),所以会跳到SetUp2Activity中的next_activity去执行相应操作,这就是抽取的逻辑
每个界面都有物理返回键,返回键执行的都是上一步操作
可在基类中对物理返回键做处理,onKeyDown,用onBackPress也可以,一般用的都是onKeyDown
在基类SetUpBaseActivity中重写onkeydown解决返回键返回主界面问题
/**
-
监听物理按钮的点击事件
-
keyCode : 点击的物理按钮的标示
-
event : 点击事件
-
KEYCODE_BACK : 返回键
-
KEYCODE_HOME : home键
**/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK) { //true:拦截事件,即点击返回键不管用 false:表示执行 //return true; pre_activity(); } return super.onKeyDown(keyCode, event); } keyCode恒等于KeyEvent中的KEYCODE_0...KEYCODE9这些物理按键做智能家居时用 KEYCODE_HOME就是物理Home键,在高版本中不能再禁止了,为了防流氓软件 return super.onKeyDown(keyCode, event) 是执行的意思,super.onKeyDown调用父类操作,父类中默认就是执行 点击返回键执行onKeyDown时执行上一步操作pre_activity 运行程序,从第2个界面按返回键,回到第1个,再从第1个按返回键,回到主界面,问题解决了
Day03 05.界面切换动画# ***
手机防盗4个界面逻辑处理完了,接下来增加界面切换动画效果
1.res -> anim -> xxxx.xml,创建的时候选择平移动画translate
setup_enter_next.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0"
android:duration="500">
<!-- fromXDelta : 从哪个位置开始移动
toXDelta : 移动到哪个位置
duration : 持续时间-->
</translate>
setup_exit_next.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="-100%"
android:duration="500">
<!-- fromXDelta : 从哪个位置开始移动
toXDelta : 移动到哪个位置
duration : 持续时间 -->
</translate>
2.在代码中调用(即在4个Activity中使用)
//必须在startActivity或finish之后执行
//enterAnim : 新界面进入的动画
//exitAnim : 旧界面退出的动画
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
比如在SetUp2Activity中:
@Override
public void next_activity() {
//判断是否绑定SIM卡
if (sv_setup2_sim.isChecked()) {
// 跳转到第三个界面
Intent intent = new Intent(this, SetUp3Activity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
}else{
Toast.makeText(getApplicationContext(), "请绑定SIM卡", 0).show();
}
}
@Override
public void pre_activity() {
// 跳转到第一个界面
Intent intent = new Intent(this, SetUp1Activity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.setup_enter_pre, R.anim.setup_exit_pre);
}
Day03 06.手势识别器# ***
上边实现了这4个界面的切换动画效果后,还想实现一个效果
效果:靠手指滑动来切换这4个界面
思路:添加手势识别器
每个界面中都要添加手势识别器,在基类SetUpBaseActivity中添加手势识别器就可以代替所有子类中添加手势识别器
1.基类SetUpBaseActivity中的OnCreate中创建手势识别器,并创建监听MyOnGestureListener
//手势识别器
private GestureDetector detector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//手势识别器必须添加到界面的触摸事件中才会有效果
detector = new GestureDetector(this, new MyOnGestureListener());
}
private class MyOnGestureListener extends SimpleOnGestureListener{
//e1 : 按下的事件,存储有按下的坐标
//e2 : 抬起的事件,存储有抬起的坐标
//velocityX : 关于x轴的滑动速率
//velocityY : 关于y轴的滑动速率
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
//获取按下的坐标
float startX = e1.getRawX();
//获取抬起的坐标
float endX = e2.getRawX();
//获取按下和抬起y坐标
float startY = e1.getRawY();
float endY = e2.getRawY();
if (Math.abs(startY-endY) > 50) {
Toast.makeText(getApplicationContext(), "你小子又乱滑了,不要闹了!!!", 0).show();
return true;
}
//下一步
if ((startX-endX) >100) {
next_activity();
}
//上一步
if ((endX-startX) > 100) {
pre_activity();
}
//true if the event is consumed, else false
//true : 事件执行 false:事件不执行
return true;
}
}
3.将手势识别器添加到界面触摸事件中,即在基类SetUpBaseActivity中重写onTouchEvent方法
//界面的触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);//将手势识别器添加到界面的触摸事件中
return super.onTouchEvent(event);
}
这就是整个手势识别器的实现
Day03 07.手机防盗界面 ##
接下来实现第4个界面的“设置完成”按钮
1.到SetUp4Activity的next_activity,添加intent跳转到手机防盗界面LostFindActivity
但是点击第4个界面的“设置完成”却跳转到了第一个界面
因为LostFindActivity的onCreate中判断了用户是否第一次进入手机防盗模块
第4个界面完成跳转时已不是第一次进入,所以要在SetUp4Activity中的next_activity中改一下
修改是否第一次进入手机防盗模块需要一个sp
在基类SetUpBaseActivity中写一个sp,即:
//protected 只能本类和子类使用
protected SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sp = getSharedPreferences("config", MODE_PRIVATE);
}
在SetUp4Activity中的next_activity中使用
@Override
public void next_activity() {
//修改是否是第一次进入手机防盗模块
Editor edit = sp.edit();
edit.putBoolean("first", false);
edit.commit();
//跳转到手机防盗界面
Intent intent = new Intent(this,LostFindActivity.class);
startActivity(intent);
finish();
}
运行程序,点击第4个界面的设置完成就跳转到了“手机防盗”界面
“手机防盗”界面显示的是设置信息,有“安全号码”,“防盗保护是否开启”,“重新进入设置向导”,“功能简介”
接下来实现“手机防盗”界面LostFindActivity,代码和布局省略
Day03 08.shape资源 #
上边实现了“手机防盗”页面,但“重新进入设置向导”还有一个圆角白色矩形图片没有实现
圆角矩形2种实现方式
第一种:
美工给你用.9图做,但像这种圆角纯色图片美工不会给
第二种:用代码画圆角矩形图片
用代码画圆角矩形图片步骤
在res -> drawable目录下创建shape_drawable.xml, 选择shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
<!-- shape:图片的形状 rectangle : 矩形 oval : 椭圆 line:线 ring:环形-->
<!-- corners : 圆角的角度 -->
<corners android:radius="10dp"/>
<!-- solid : 颜色-->
<solid android:color="#ff0000"/>
<!-- stroke : 边框 dashWidth : 点的宽度 dashGap : 点与点之间的距离
<stroke android:width="3dp" android:color="#00ff00" android:dashWidth="3dp" android:dashGap="5dp"/>
-->
<!-- gradient : 渐变 angle :旋转的角度-->
<gradient android:startColor="#00ff00" android:centerColor="#ff0000" android:endColor="#0000ff"/>
</shape>
这就是用代码画图片的实现
shape既然是一张图片,只要是图片都能使用状态选择器
给shape图片增加状态选择器
1.在drawable下创建一个shape_drawable_press.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
<!-- shape:图片的形状 rectangle : 矩形 oval : 椭圆 line:线 ring:环形-->
<!-- corners : 圆角的角度 -->
<corners android:radius="10dp"/>
<!-- solid : 颜色-->
<solid android:color="#ff0000"/>
<!-- stroke : 边框 dashWidth : 点的宽度 dashGap : 点与点之间的距离
<stroke android:width="3dp" android:color="#00ff00" android:dashWidth="3dp" android:dashGap="5dp"/>
-->
<!-- gradient : 渐变 angle :旋转的角度-->
<gradient android:startColor="#ff0000" android:centerColor="#0000ff" android:endColor="#00ff00"/>
</shape>
开始颜色换成红色,右边颜色换成绿色
2.在drawable下创建一个shape_drawable_button.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/shape_drawable_pressed" />
<item android:drawable="@drawable/shape_drawable" /> <!-- default 默认图片-->
</selector>
3.给activity_lostfind.xml的“重新进入设置向导”的textview设置背景
即可将shape图片的状态选择器设置上去
运行程序发现TextView不能点击,TextView天生没有点击事件
所以需要给TextView添加Clickable属性,并设置onClick点击事件为setup
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="重新进入设置向导"
android:textSize="18sp"
android:background="@drawable/shape_drawable_button"
android:clickable="true"
android:onClick="setup"
/>
4.到LostFindActivity中添加setup点击方法,即:
public void setup(View v){
//跳转到设置向导第一个界面
Intent intent = new Intent(this,SetUp1Activity.class);
startActivity(intent);
finish();
}
Day03 09.绑定SIM卡# ***
4个界面做完了后实现每个界面中的功能
第一个界面:1.欢迎使用手机防盗,这里边就是一些功能简介
第二个界面:2.手机卡绑定,这里边有一些自定义控件,有点击绑定SIM卡
实现绑定SIM卡操作:
1.在SetUp2Activity里边初始化自定义组合控件SettingView
2.给自定义组合控件添加点击事件
private SettingView sv_setup2_sim;
@Override
protected void onCreate(Bundle savedInstanceState) {
sv_setup2_sim = (SettingView) findViewById(R.id.sv_setup2_sim);
//设置回显操作
//判断保存的SIM卡是否为空,如果是表示没有绑定,不是表示绑定
if (TextUtils.isEmpty(sp.getString("sim", ""))) {
sv_setup2_sim.setChecked(false);
}else{
sv_setup2_sim.setChecked(true);
}
sv_setup2_sim.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Editor edit = sp.edit();
//根据checkbox状态,设置描述信息状态
if (sv_setup2_sim.isChecked()) {
//解绑
edit.putString("sim", "");
sv_setup2_sim.setChecked(false);
}else{
//绑定SIM卡
//1.获取电话的管理者
TelephonyManager tel = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
//tel.getLine1Number();//获取SIM卡绑定的电话,Line1:多卡多待,但是在中国不太适用,中国的运营商一般不会将SIM卡和手机号绑定,获取不到电话
String sim = tel.getSimSerialNumber();//获取SIM卡序列号,唯一标示
//2.保存SIM卡
edit.putString("sim", sim);
//3.改变checkbox的状态
sv_setup2_sim.setChecked(true);
}
edit.commit();
}
});
}
这里需要添加获取电话状态的权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Day03 10.重启手机,发送报警短信# ***
上边绑定SIM卡操作已实现
界面“2.手机卡绑定”页面还有一句“下次重启手机如果发现SIM卡发生变化就会发送报警短信”
这里边涉及到3个操作:
1.重启手机
2.发现SIM卡发生变化
3.发送报警短信
重启手机时要发现SIM卡是否发生变化,需要用广播接收者监听系统重启手机的广播事件
1.创建一个广播接收者BootCompletedReceiver并在清单文件中配置
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("手机重启了......");
}
}
清单文件中配置
<receiver android:name="cn.itcast.mobilesafexian02.receiver.BootCompletedReceiver">
<!-- priority : 值越大优先级越高,优先级越高越先接收到广播事件 -->
<intent-filter
android:priority="1000"
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
广播接收者存放的包名是:cn/itcast/mobilesafexian02/receiver
不是根目录: cn/itcast/mobilesafexian02
所以不能用点来表示,要用全类名来表示
优先级priority设置完后,接下来要设置一个广播接收者、广播事件了
手机重启的事件叫做BOOT_COMPLETED
到这广播接收者就注册成功,接下来在onReceive中System输出测试
运行程序查看效果前,需先把模拟器中原先那个删除掉,然后重启下模拟器
这里监听了手机重启的广播事件
<action android:name="android.intent.action.BOOT_COMPLETED"/>
监听手机重启的广播事件,也是侵犯用户隐私的,所以也需要添加权限,即:
android permission RECEIVE_BOOT_COMPLETED
2.在onReceive方法中执行操作
第1步做完后,重启手机已可以获取到了,接下来去观察SIM是否发生变化并发送报警短信
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("手机重启了......");
SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
//根据保存的防盗保护是否开启状态,来判断是否发送报警短信
if (sp.getBoolean("protected", false)) {
//判断SIM卡是否发生变化
//1.获取保存的SIM卡
String sp_sim = sp.getString("sim", "");
//2.获取当前的SIM卡
TelephonyManager tel = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String sim = tel.getSimSerialNumber();//获取SIM卡序列号,唯一标示
//3.判断SIM卡是否为空
if (!TextUtils.isEmpty(sp_sim) && !TextUtils.isEmpty(sim)) {
//4.判断SIM卡是否一致
if (!sp_sim.equals(sim)) {//避免空指针异常
//发送短信
SmsManager smsManager = SmsManager.getDefault();//短信的管理者
//destinationAddress : 收件人
//scAddress : 服务中心的地址 一般null
//text : 短信内容
//sentIntent : 是否发送成功
//deliveryIntent : 协议一般null
smsManager.sendTextMessage(sp.getString("safenum", "5556"), null, "da ge wo bei dao le,help me!!!", null, null);
}
}
}
}
}
3.添加权限:
第4步发送短信的操作也需要权限,即:
<uses-permission android:name="android.permission.SEND_SMS"/>
重启手机,发送报警短信总共需要的权限如下:
android permission.READ_PHONE_STATE
android permission.RECHIVE_BOOT_COMPLETED
android permission.SEND_SMS
启动模拟器5556,并重启模拟器5554,运行程序,模拟器5556上,直接就收到来自模拟器5554的报警短信“da ge wo bei dao le,help me!!!”,刚才短信上边还有个坐标之类的刚才删了,那个明天讲
注意这条短信号码:1555-521-5554,我们是5554模拟器给5556发的短信,前边的前缀1555-521,是模拟器上经常会出现的问题,不用在意
Day03 11.设置安全号码# **
第二个界面“手机卡绑定”操作已做完,接下来执行第3个界面“3.设置安全号码”
在此可以在第二个界面点击下一步这里去做判断了
如isChecked等于true,表示绑定了SIM卡,跳转到第三个界面
如是false,表示没有绑定,提醒用户“请绑定SIM卡”
@Override
public void next_activity() {
//判断是否绑定SIM卡
if (sv_setup2_sim.isChecked()) {
// 跳转到第三个界面
Intent intent = new Intent(this, SetUp3Activity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
}else{
Toast.makeText(getApplicationContext(), "请绑定SIM卡", 0).show();
}
}
上边“判断是否绑定SIM卡”愿意做就做,不愿做就别做,每个公司对用户体验的理解不一样
跳转到第三个界面“3.设置安全号码”,第三个界面就是保存一下在号码输入框这里输入的号码
到SetUp3Activity的点击下一步处保存输入的安全号码
1.在下一步点击事件中,获取输入的号码,并保存
@Override
public void next_activity() {
//1.获取输入的内容
String num = et_setup3_safenum.getText().toString().trim();
//2.判断输入的内容是否为空
if (TextUtils.isEmpty(num)) {
Toast.makeText(getApplicationContext(), "请输入安全号码", 0).show();
return;
}
//3.保存输入的安全号码
Editor edit = sp.edit();
edit.putString("safenum", num);
edit.commit();
// 跳转到第四个界面
Intent intent = new Intent(this, SetUp4Activity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
}
到这,保存安全号码操作完成,保存完之后还要进行回显操作
2.在Setup3Activity中的onCreate方法中进行回显操作
//回显操作
et_setup3_safenum.setText(sp.getString("safenum", ""));
这个就是将保存的安全号码设置给了EditText
运行程序,在“3.设置安全号码”处,输入5556,然后点击下一步到第四个界面,然后再点击上一步回到第三个界面,看到EditText中还是会显示5556,这就是回显的业务逻辑
Day03 12.获取联系人操作 # **
第三个界面还有一个选择联系人按钮,现在手机号码都是11位,用户不可能记住,所以点击“选择联系人”按钮会进入选择联系人界面,列举出所有联系人供用户选择,点击一个联系人,就把联系人号码显示到EditText输入框,点击下一步时保存一下,这样用户体验比较好
DDMS下data/data/com.android.providers.contacts/databases里有contacts2.db数据库
所有联系人都保存在这个数据库当中的,导出来用可视化工具SQlite看下
contacts2.db数据库中有个raw_contacts,里边就是所有联系人姓名
contacts2.db的data里边是联系人的所有信息,每个信息都是以一条信息的形式展示的
data表前边的raw_contant_id就是data表和raw_contacts表进行关联的
通过view_data表和raw_contacts表就可以把所有系统联系人查询出来
要想获取联系人就要去查询数据库,但是这个数据库的权限“--rw--rw----”是不可读不可写,
这个contacts2.db,只能用户使用
安卓中一个应用程序相当于一个用户组,要从用户组获取数据就要用内容提供者了,其实用最多的是内容解析者
根据内容提供者查询数据,获取联系人操作是业务操作,创建一个业务类ContactsEngine.java
public class ContactsEngine {
/**
-
获取系统联系人
*/
public static List<HashMap<String, String>> getAllContacts(Context context){
SystemClock.sleep(3000);
List<HashMap<String, String>> list = new ArrayList<HashMap<String,String>>();
//1.获取内容解析者
ContentResolver resolver = context.getContentResolver();
//2.内容提供者的地址 com.android.contacts 比如www.baidu.com/jdk raw_contacts表的地址:raw_contacts view_data表的地址:data
//3.生成查询地址
Uri raw_uri = Uri.parse(“content://com.android.contacts/raw_contacts”);//http://www.baidu.com/jdk
Uri data_uri = Uri.parse(“content://com.android.contacts/data”);
//4.查询数据,先查询raw_contacts的contact_id
Cursor cursor = resolver.query(raw_uri, new String[]{“contact_id”}, null, null, null);
//5.解析cursor
while(cursor.moveToNext()){
String contact_id = cursor.getString(0);//获取contact_id数据
// cursor.getString(cursor.getColumnIndex(“contact_id”));//getColumnIndex : 查询contact_id在cursor中的索引,查询字段比较多的情况
if (contact_id != null) {
//6.根据contact_id查询view_data表中的数据,null.方法 参数为null
Cursor c = resolver.query(data_uri, new String[]{“data1”,“mimetype”}, “raw_contact_id=?”, new String[]{contact_id}, null);
HashMap<String, String> map = new HashMap<String, String>();
//7.解析c
while(c.moveToNext()){
String data1 = c.getString(0);
String mimetype = c.getString(1);
//8.根据mimetype判断data1的类型
if (mimetype.equals(“vnd.android.cursor.item/phone_v2”)) {
//电话
//9.添加数据
map.put(“phone”, data1);
}else if(mimetype.equals(“vnd.android.cursor.item/name”)){
//姓名
map.put(“name”, data1);
}
}
//10.将数据添加到集合中
list.add(map);
//11.关闭cursor
c.close();
}
}
//11.关闭cursor
cursor.close();
return list;
}
}获取系统联系人操作属于侵犯隐私,需在清单文件添加权限:
android permission.READ_CONTACTS
到这,获取联系人操作就做完了,接下来单元测试
凡是关于业务操作都要单元测试保证运用业务类之前逻辑正确,避免用时出现错误却不知哪里错了 一般业务操作或数据库操作都需要进行单元测试 基础班时直接在清单文件配置一下,然后在工程里边写单元测试类去单元测试 不建议这么去做,这个工程写完要签名打包上线时还得把单元测试全部删掉 所以说一般会new一个Project,选择Android,它里边有Android Test Project就是单元测试工程 给单元测试工程起名称TestMobilesafexian02 Select Test Target,选择mobilesafexian02 SDK选择16的版本 打开TestMobilesafexian02工程的清单文件看到已经配置好了 直接创建单元测试类TestContacts继承的是AndroidTestCase TestContacts.java public class TestContacts extends AndroidTestCase{ public void testContacts(){ List<HashMap<String,String>> list = ContactsEngine.getAllContacts(getContext()); for(HashMap<String,String> hashMap : list){ System.out.println("姓名:"+hashMap.get("name")+" 电话:"+hashMap.get("phone")); } } } 在这个测试方法中,就可以调用mobilesafexian02项目中的业务类ContactsEngine了 接下来把这个list集合遍历出来并输出一下 选中testContacts,右键Run As,Android JUnit Test 显示绿色进度条,表示测试成功,看到log也打印出来了,说明逻辑没问题了 现在演示一个问题,打开联系人,删除联系人zhangfei,然后把log清空掉 运行testContacts,出错了,有一个地方为空了,双击定位到出错的地方为: ContactsEngine.java中的31行,即: Cursor c = resolver.query(data_uri, new String[]{"data1","mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null); 为空原因一般是null.方法,或参数为null了 既然它是执行到31行才为空的,说明上边执行通过,说明resolver不为空,那只有一种可能,这行有一个参数contact_id为空了 重新把contacts2.db导出来,zhangfei还在但它的contact_id为null了,android包括ios中所有删除数据操作其实没有真正删除,都是把标识置为null表示已经删除 原因找到了,在这判断一下就可以了,即: if(contact_id != null){ }
把其他操作放进去就可以了
再次运行程序就不会报空了,说明获取联系人操作没问题了
Day03 13.选择联系人界面 # **
获取到联系人后要去用
点击“重新进入设置向导”,“下一步”,“3.设置安全号码”,“选择联系人”,“选择联系人”界面
在这个界面中去“获取系统联系人”
到activity_setup3.xml,给“选择联系人”button设置点击事件属性:
android:onClick="selectContact"
到SetUp3Activity实现这个方法:
/**
-
选择联系人按钮的点击事件
*/
public void selectContact(View v){
跳转到选择联系人界面
//Intent intent = new Intent(this,ContactsActivity.class);
在当前的activity退出时,会调用之前activity的onActivityResult方法
//startActivityForResult(intent, 0);Intent intent = new Intent(); intent.setAction("android.intent.action.PICK"); intent.addCategory("android.intent.category.DEFAULT"); intent.setType("vnd.android.cursor.dir/phone_v2"); startActivityForResult(intent, 1); } 1.创建ContactsActivity,在onCreate中调用获取联系人操作 private List<HashMap<String,String>> list; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_contact); //获取联系人 list = ContactsEngine.getAllContacts(getApplicationContext()); } 2.使用listview显示数据 a.布局文件添加ListView控件: <ListView android:id="@+id/lv_contact_contacts" android:layout_width="match_parent" android:layout_height="match_parent"
b.代码中初始化ListView,getveiw加载条目布局item_contact.xml初始化条目控件 private List<HashMap<String,String>> list; private ListView lv_contact_contacts; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_contact); //获取联系人 list = ContactsEngine.getAllContacts(getApplicationContext()); //展示查询出来数据 lv_contact_contacts = (ListView) findViewById(R.id.lv_contact_contacts); lv_contact_contacts.setAdapter(new Myadapter()); } private class Myadapter extends BaseAdapter{ //条目的个数 @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } //条目的样式 @Override public View getView(int position, View convertView, ViewGroup parent) { //将布局文件转化成view对象 View view = View.inflate(getApplicationContext(), R.layout.item_contact, null); //初始化控件 TextView tv_itemcontact_name = (TextView) view.findViewById(R.id.tv_itemcontact_name);//view.findViewById表示去item_contact初始化控件 TextView tv_itemcontact_phone = (TextView) view.findViewById(R.id.tv_itemcontact_phone); //设置相应的数据 tv_itemcontact_name.setText(list.get(position).get(“name”)); tv_itemcontact_phone.setText(list.get(position).get(“phone”)); return view; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return 0; } }
Day03 14.设置安全号码界面数据传递 # ***
上边实现了选择联系人界面,接下来实现
点击联系人界面ContactsActivity中联系人之后
将号码回显到SetUp3Activity(“3.设置安全号”界面)的安全号码输入框EditText里
分析:
点击SetUp3Activity(设置安全号码界面)中的“选择联系人”按钮,
跳转到ContactsActivity(选择联系人界面),
点击联系人条目
把ContactsActivity销毁掉,同时把号码传递到setUp3Activity
把号码回显到安全号码输入框中
涉及两个activity之间传递数据的操作:
两个activity传递数据就不能在SetUp3Activity中用StartActivity,必须用StartActivityforResult();
1.SetUp3Activity中用startActivityForResult跳转
//在当前的SetUp3Activity退出时,会调用之前activity的onActivityResult方法
startActivityForResult(intent, 0);
2.重写setup3activity的中onActivityResult方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if (data!=null) {
// //获取ContactsActivity传递过来的数据,null.方法 参数为null
// String num = data.getStringExtra("num");
// //将获取的数据显示到输入框中
// et_setup3_safenum.setText(num);
// }
if(data !=null){
// String num = data.getStringExtra("num");
Uri uri = data.getData();
String num = null;
// 创建内容解析者
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(uri,
null, null, null, null);
while(cursor.moveToNext()){
num = cursor.getString(cursor.getColumnIndex("data1"));
}
cursor.close();
num = num.replaceAll("-", "");// 555-6 -> 5556
et_setup3_safenum.setText(num);
}
}
SetUp3Activity要获取ContactsActivity的数据,SetUp3Activity肯定要传递一个数据给ContactsActivity
ContactsActivity中点击ListView条目后,把数据回传给SetUp3Activity,给ContactsActivity增加条目点击事件
3.在ContactsActivity中,给listview增加条目点击事件,并进行数据设置传递
//给listview增加条目点击事件
lv_contact_contacts.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//回传数据
Intent intent = new Intent();
intent.putExtra("num", list.get(position).get("phone"));
//设置结果的方法,会将结果返回给调用它的SetUp3Activity
setResult(0, intent);
//移出界面
finish();
}
});
在点击事件中回传数据,SetUp3Activity的onActivityResult要接收的参数是一个Intent
所以在ContactsActivity点击事件回传数据时,要new一个Intent出来,putExtra,传的是电话号码num
点击事件参数中有position,是点击条目的位置,可根据条目位置position得到点击条目所对应的数据phone,即:
putExtra("num", list.get(position).get("phone"));
用setResult,resultcode对应设置成0,把intent传递进去,即:
setResult(0,intent);
4.在setup3activity中的onActivityResult获取数据,并对data为空判断,避免返回键返回时程序崩溃
if (data!=null) {
//获取ContactsActivity传递过来的数据,null.方法 参数为null
String num = data.getStringExtra("num");
//将获取的数据显示到输入框中
et_setup3_safenum.setText(num);
}
onActivityResult获取数据后需为空判断,避免物理返回键程序崩溃
如用户没有点击联系人,而是点击了物理返回键,直接就奔溃了,log日志报result出现错误,是一个空指针异常,在68行双击可直接定位到出错地方,即SetUp3Activity的onActivityResult中的:String num = data.getStringExtra("num");
空指针异常的两种情况
null.方法或参数为null
String num = data.getStringExtra("num");
这行报错明显就是第一种错误,null.方法为空了,因为这里没有参数num
data为空是因为在ContactsActivity的条目点击事件中点击时,new了一个Intent去储存数据,但在点击物理返回键时,没有在物理返回键中去new一个intent