MobileSafe Day03

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
  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值