##Day03##
#3.1设置向导第二个界面&样式抽取# ***
手机防盗第二个界面(2.手机卡绑定SetUp2Activity.java)完成之后,找到activity_setup2.xml布局文件重新审视代码,也有一个下一步按钮(activity_setup2.xml布局文件完全拷贝自activity_setup1.xml布局文件),这个按钮我们是没有改变的(即下一步的按钮是完全一样的),相同的操作在java中是可以向上抽取的,我们这个也是可以向上抽取的,不是抽取到自定义控件中,而是抽取到样式文件当中了
样式抽取步骤:
1.打开res -> values -> styles(模仿上边系统的写法,对比下一步按钮)形成样式抽取
系统写法:
<style name="AppBaseTheme" parent="AppBaseTheme">
<item name="android:windowNoTitle">true</item>
</style>
Button按钮:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下一步"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:drawableRight="@drawable/next"
android:background="@drawable/button"
android:padding="5dp"
android:onclick="next" >
形成的样式抽取:
<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"
/>
3.在2.手机卡绑定SetUp2Activity.java中实现上一步/下一步的点击事件
public void pre(View v){
//跳转到第一个界面(1.欢迎使用手机防盗)
Intent intent = new Intent(this,SetUpActivity1.class);
StartActivity(intent);
}
public void next(View v){
//跳转到第三个界面
}
注意:
一般id是不会抽取的,因为id是它在布局文件中的唯一标识,但是也可以抽取,因为id在不同的布局文件中是可以相同的,如果你想让比如button按钮标识就是activity_setup2.xml中的,那你定义的id和其他布局文件中是不一样的,这个时候就要单独在button中写出来
上一步的抽取和下一步一样,以上就是将布局文件中的一些属性抽取到样式文件中的操作,在开发中会经常遇到,而且这个操作用了,标志着至少拥有两年工作经验,一般干一年的人看不懂这个,他会问为什么这里面什么都没有,就一个样式文件就可以进行操作,老的程序员,一眼就可以看出向上抽取了。
ps:出去工作以后,会比一年工作多一点的人开发要快,但是写完了不能让老板觉得什么都不干,那就把能抽取的抽取一下,别人看不懂了就会来请教,老板会觉得你很牛,什么事会先想着你,纯属工作技巧。
#3.2设置向导第三个和第四个界面# **
注意:在控件中使用样式文件中的属性即可修改样式文件中相应的属性的值
即,在控件中使用与样式文件中相同的属性,就可以覆盖样式文件中相应属性的值
public void next(View v){
//跳转到第三个界面
Intent intent = new Intent(this,SetUpActivity3.class);
startActivity(intent);
}
1.跳转到第三个界面时,它的参数需要创建一个SetUp3Activity.java,继承自Activity
创建完SetUp3Activity.java之后,到清单文件中配置(每创建完一个activity,首先要想到到清单文件中配置);
2.紧接着在SetUp3Activity.java中重写onCreate方法,并在其中加载布局文件(setContentView(R.layout.activity_setup3))
加载布局文件时,需要创建一个activity_setup3.xml布局文件(可以拷贝activity_setup2.xml,修改即可)
此布局文件中选择联系人按钮需要添加一个状态选择器(正常的和按下的图片),复制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>
3.将状态选择器设置给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就会报空指针异常,这个不用在意,在模拟器上测试安装的时候会经常出现这个问题。
4.运行成功后,继续实现第三个界面的上一步和下一步点击事件,拷贝第二个界面的修改即可
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.java,继承自Activity,不在赘述。
在activity_setup4.xml中下一步按钮需要修改成“设置完成” ,下一步用的样式文件,在样式文件中我们设置了text文本是下一步,现在要改一下下一步的文本是设置完成了,如果在样式文件中改的话,前面第二个第三个界面都会变成设置完成,所以可以这样改,在第四个界面的button中,增加属性 android:text="设置完成"即可,即,在控件中使用与样式文件中相同的属性,就可以覆盖样式文件中相应属性的值
小图片也不需要了,那就添加属性 android:drawableRight="@null"即可
<Button style="@style/next"
android:text="设置完成"
android:drawableRight="@null"
/>
#3.3界面切换逻辑处理# *****
问题:
上面已经将四个界面都实现了,但运行时,回到第一个界面,在点击返回键,按理说,应该回到主界面HomeActivity.java,但是它会倒着走了一遍才会回到主界面
原因:找到setUp1Activity.java,原因在于,跳转到第二个界面的时候是通过startActivity(intent)跳转的,这相当于已经把这个activity存放到任务栈当中了,然后在setUp2Activity.java中又通过startActivity(intent)跳转了,所以才会出现这个问题。
解决:在每一个跳转处添加finish(); 即当跳转到下一个界面时,把上一个界面隐藏掉就可以了
但是这样又暴露了另一个问题,
1.创建一个基类setupbaseActivity
public abstract class SetUpBaseActivity extends Activity {
2.在基类中抽取上一步和下一步的点击事件,创建两个抽象类去让子类实现具体的操作
//是按钮的点击事件,我们抽取是按钮的点击事件,因为每个界面的按钮实现的操作不一样,所以才在点击事件中调用抽象方法来实现响应的操作
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
}
}
4.重写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);
}
#3.4界面切换动画# ***
1.res -> anim -> xxxx.xml,创建的时候选择translate
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0"
android:duration="500"
>
<!-- fromXDelta : 从哪个位置开始移动
toXDelta : 移动到哪个位置
duration : 持续时间
-->
</translate>
2.在代码中调用
//必须在startActivity或者finish方法之后执行
//enterAnim : 新界面进入的动画
//exitAnim : 旧界面退出的动画
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
#3.5手势识别器# ***
1.在基类中的oncreate方法中创建一个手势识别器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//手势识别器必须添加到界面的触摸事件中才会有效果
detector = new GestureDetector(this, new MyOnGestureListener());
}
2.创建一个监听
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.将手势是识别器添加到界面触摸事件中
//界面的触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);//将手势识别器添加到界面的触摸事件中
return super.onTouchEvent(event);
}
#3.6shape资源# ****
1.res -> drawable -> xx.xml, 选在shape
<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>
2.使用,给textview设置背景
android:background="@drawable/shape_drawable_button"
#3.7绑定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"/>
#3.8重启手机,发送报警短信# ***
1.创建一个广播接受者
public class BootCompletedReceiver extends BroadcastReceiver {
2.清单文件中配置
<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>
3.在onReceive方法中执行操作
System.out.println("手机重启了......");
SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
//判断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("5556", null, "da ge wo bei dao le,help me!!!", null, null);
}
}
5.添加权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
#3.9设置安全号码# **
1.在下一步点击事件中,获取输入的号码,并保存
//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();
2.回显操作
//回显操作
et_setup3_safenum.setText(sp.getString("safenum", ""));
#3.10获取联系人操作# ******
/**
* 获取系统联系人
*/
public static List<HashMap<String, String>> getAllContacts(Context context){
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;
}
#3.11选择联系人界面# **
1.创建activity,清单文件配置,创建布局文件
2.在oncreate方法中,调用获取联系人的操作
//获取联系人
list = ContactsEngine.getAllContacts(getApplicationContext());
3.使用listview显示数据
a.布局文件中
<ListView
android:id="@+id/lv_contact_contacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
></ListView>
b.代码中
lv_contact_contacts = (ListView) findViewById(R.id.lv_contact_contacts);
lv_contact_contacts.setAdapter(new Myadapter());
c.getveiw中
//条目的样式
@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;
}
#3.12设置安全号码界面数据传递# ***
1.使用startActivityForResult进行界面跳转
//在当前的activity退出的时候,会调用之前activity的onActivityResult方法
startActivityForResult(intent, 0);
2.重写setup3activity的中onActivityResult方法
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"));
//设置结果的方法,会将结果返回给调用它的activity
setResult(0, intent);
//移出界面
finish();
}
});
4.在setup3activity的中onActivityResult方法去获取传递的数据,同时对data进行为空判断,避免返回键返回时程序崩溃
if (data!=null) {
//获取ContactsActivity传递过来的数据,null.方法 参数为null
String num = data.getStringExtra("num");
//将获取的数据显示到输入框中
et_setup3_safenum.setText(num);
}