Day02##
Day02 01 复习 #
Day02 02 TextView滚动效果 # ***
跑马灯效果的两种实现方式:
1.布局文件中给TextView增加属性来实现滚动效果
在activity_home.xml布局文件的GridView上方添加一个TextView
即:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="手机卫士,真64核杀毒引擎,超神速度,打开7次可以召唤神龙,辅助杀毒!!!"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:focusableInTouchMode="true"
android:focusable="true"
/>
<!--
singleLine : 单行显示 (效果是后边有省略号,隐藏了后边的内容,和ellipsize设置为end时的效果一样)
ellipsize : marquee 滚动
ellipsize的五个值:
none : 效果是后边没有省略号,省略了后面的内容
start : 效果是前面有省略号,隐藏了前面的内容
middle : 效果是中间有省略号,隐藏了中间的内容
end : 效果是后边有省略号,隐藏了后面的内容
marquee : 效果是滚动,获取了焦点之后才能滚动
android:marqueeRepeatLimit="marquee_forever" : 永远滚动 相当于 marquee_forever = -1
在android中,默认滚动次数是3次
设置marqueeRepeatLimit或 marquee_forever属性,让它永远滚动
focusableInTouchMode : 触摸获取焦点
focusable :是否拥有焦点功能 true:可以获取 false: 不可以
-->
<!--
GridView : 跟listview相似
numColumns : 设置每行显示的个数,其实就是设置显示列数
verticalSpacing : 设置行与行之间的距离
layout_marginTop : 距离控件顶部的距离
-->
<GridView
android:id="@+id/gv_home_gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:numColumns="3"
android:verticalSpacing="10dp" >
</GridView>
</LinearLayout>
Button和TextView的关系:
button继承自TextView,所以TextView中拥有的属性button也会拥有,但button拥有的属性,TextView不一定拥有
textview本身没有焦点功能,所以要在textView中添加focusableInTouchMode(触摸获取焦点)之后,还要添加focusable(是否拥有焦点功能)属性
而button本身拥有焦点功能,所以不需要再添加focusable
2.自定义TextiView实现自动获取焦点功能(自定义控件) **
textview自身没有焦点功能,所以自定义一个textView控件,让它去主动获取焦点,来实现textView的滚动效果
2.1.创建自定义控件Home_textView.java让它继承自textView
public class Home_TextView extends TextView {
//有三个构造方法
//1.有一个参数的构造方法 (代码中使用时调用)
public Home_TextView(Context context) {
super(context);
//new一个textview时,需要一个context,所以调用的是这个一个参数的构造方法
// TextView textView = new TextView(context);
}
//2.有两个参数的构造方法 (布局文件使用时调用,反射转化成相应代码,代码中new,AttributeSet参数:会保存控件所有属性)
public Home_TextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
//3.有三个参数的构造方法 (布局文件使用时调用,比两个参数的多了样式文件(defStyle),一般不用)
public Home_TextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
//是否获取焦点(主动获取焦点,实现textView的滚动效果)
@Override
public boolean isFocused() {
//true:获取焦点 false:不获取焦点
return true;
}
}
注意:
1.布局文件中的所有控件,都可以用代码来实现,也就是在安卓开发中完全可以不写布局文件,直接用代码写
用代码写布局文件:可防止别人反编译看到你的布局文件,坏处是很繁琐,时间成本很高
2:布局文件是辅助程序运行的,运行时通过反射转化成相应代码,即布局文件中都是转化成代码运行的
3:在values下的styles.xml文件中定义一下,就可以使用这个自定义控件中第2个方法的defStyle样式文件了,这个一般很少有人用,一般两个参数的方法,就可以满足需求
2.2.在布局文件中使用自定义控件
选中Home_TextView-->右键Copy Qualified Name-->在activity_home.xml文件中
用拷贝的全类名替换掉目标textview名称
即:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!--
singleLine : 单行显示 (效果是后边有省略号,隐藏了后边的内容,和ellipsize设置为end时的效果一样)
ellipsize : marquee 滚动
ellipsize的五个值:
none : 效果是后边没有省略号,省略了后面的内容
start : 效果是前面有省略号,隐藏了前面的内容
middle : 效果是中间有省略号,隐藏了中间的内容
end : 效果是后边有省略号,隐藏了后面的内容
marquee : 效果是滚动,获取了焦点之后才能滚动
android:marqueeRepeatLimit="marquee_forever" : 永远滚动 相当于 marquee_forever = -1
在android中,默认滚动次数是3次
设置marqueeRepeatLimit或 marquee_forever属性,让它永远滚动
focusableInTouchMode : 触摸获取焦点
focusable :是否拥有焦点功能 true:可以获取 false: 不可以
-->
<cn.itcast.mobilesafexian02.ui.Home_TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:text="手机卫士,真64核杀毒引擎,超神速度,打开7次可以召唤神龙,辅助杀毒!!!" />
<!--
GridView : 跟listview相似
numColumns : 设置每行显示的个数,其实就是设置显示列数
verticalSpacing : 设置行与行之间的距离
layout_marginTop : 距离控件顶部的距离
-->
<GridView
android:id="@+id/gv_home_gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:numColumns="3"
android:verticalSpacing="10dp" >
</GridView>
</LinearLayout>
Day02 02 GridView条目点击事件 # ***
实现每个模块的功能之前,先要给GridView增加条目点击事件
给GridView增加条目点击事件
HomeActivity中增加如下代码:
gv_home_gridview.setOnItemClickListener(new OnItemClickListener() {
//parent :其实就是GridView
//view : 条目的view对象 其实就是一个Linearlayout
//position :条目的位置
//id : 条目的id
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
}
});
怎样知道一个方法或参数是什么意思:
在onItemClick方法中,加一条打印语句,即:
System.out.println("..................");
给这条语句设置断点,点debug模式运行,进入debug模式后,高亮显示的部分,表示正在执行的代码
执行到打了断点的这行代码时会停止,将鼠标移到打印语句所在的onItemClick参数上会显示出函数的含义
第一个参数parent:看到是一个GridView
第二个参数view:看到是一个LinearLayout
这个LinearLayout从哪来的:
往下看,找到getView方法,双击布局文件item_home.xml,看到这个布局文件最外边一层就是LinearLayout,getView方法的View.inflate,就是将布局文件转化成View对象
View对象就是item_home.xml布局文件最外边的LinearLayout
第三个参数position:看到position等于8,代表的是条目位置
第四个参数id:代表的是条目id 看到id=0,原因在于下边有一个方法叫做getItemId,这个getItemId方法就是获取条目id,一般它的参数写一个position就可以了,即一般都是返回一个position就可以
通过debug模式打断点的方式,知道了onItemClick方法的4个参数意思
GridView条目点击事件onItemClick中,通过switch判断,根据positio判断当前条目的position
点击设置中心,设置中心的position是8,直接在case处写个8表示是设置中心
在case8中,实现跳转到设置中心SettingActivity的操作
即:
gv_home_gridview.setOnItemClickListener(new OnItemClickListener() {
//parent : gridview
//view : 代表就是条目的view对象
//position :条目的位置
//id : 条目的id
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//判断当前点击的条目的位置
switch (position) {
case 8://设置中心
Intent intent = new Intent(HomeActivity.this,SettingActivity.class);
startActivity(intent);
break;
}
}
});
创建一个Activity的步骤:
1.创建SettingActiivity
2.到清单文件中配置SettingActivity
3.重写onCreate方法并setContentView加载布局
4.创建布局文件activity_setting.xml
运行程序,点击设置中心,直接就跳转到设置中心界面了
Day02 04自定义组合控件# ***
在SettingActivity中实现“提示更新”条目的布局:
前边每次打开app时,都弹出“提示更新”对话框,现在应用都在设置中心添加“是否提示更新”功能
如果用户打开提示更新功能,下次打开app时如果有最新版本,就会弹出“提示更新”的对话框
如果用户关闭提示更新功能,下次打开app时就算有最新版本,也不会弹出“提示更新”的对话框
主动权在用户身上,这个功能的用户体验比较好
“提示更新”条目原型图如下:
上边标题,下边描述信息,右边checkbox,这种样式使用相对布局非常简单
设置中心不只有“提示更新”条目,后期会有很多条目,还有样式一样的条目时,不停复制“提示更新”条目,改改文字的方式,会导致布局文件很大
android系统渲染界面就是在渲染布局文件,布局文件越大,渲染速度会越慢,java和android中重复代码都可向上抽取
把它抽取到一个自定义控件中去使用,叫做自定义组合控件
将提示更新条目和其他条目相同的地方向上抽取,形成自定义组合控件
自定义组合控件实现“提示更新”条目布局的步骤:
1.创建settingview.xml实现“提示更新”条目布局
2.创建自定义组合控件SettingView.java
自定义组合控件是组合了多个自定义控件,不能像自定义控件只继承一个控件(例如上边只继承textView)
应该继承自settingView.xml中的根布局RelativeLayout
3.实现三个构造方法,右键选Source,Generate Constructors from Superclass生成
4.创建init()方法添加布局文件settingview.xml
在每个构造方法中都去调用下init()方法,这样不管哪种方式使用自定义组合控件,都能调用init方法
添加布局第一种方式:用View.inflate将布局文件转化成View对象,然后addView进去
5.到activity_setting.xml中使用自定义组合控件SettingView
拷贝SettingView全类名到activity_setting.xml,将原来代表“提示更新”条目的RelativeLayout控件删除掉,
以全类名为名称,新建一对标签<cn.itcast.mobilesafexian02.ui.SettingView标签
运行看下,提示更新条目可以显示出来了,这是第一种方式,先把它注释掉
第二种添加布局的方式:
原先View.inflate里边有一个root,一般都写null,但是我们写成this,然后等号前边的View view不要了,即:
View.inflate(getContext(), R.layout.settingview, this);
运行程序,发现效果一样
即:
public class SettingView extends RelativeLayout {
//在代码中使用的时候调用
public SettingView(Context context) {
super(context);
init();//不管采用哪种方式使用自定义组合控件,都能调用init()方法来添加布局文件
}
//在布局文件使用的时候调用 ,多了样式文件
public SettingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
//在布局文件使用的时候调用
public SettingView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
-
添加布局文件
*/
private void init(){
//添加布局文件
//第一种方式
//View view = View.inflate(getContext(), R.layout.settingview, null);
//this.addView(view);//layoutParams : 用代码给控件设置属性,表示要设置控件(子控件)在自定义控件(父控件)中的属性 //this.addView(child, params); //第二种方式 View.inflate(getContext(), R.layout.settingview, this); } }
添加布局的两种方式:
第一种方式:用View.inflate将布局文件转化成View对象,然后addView进去 View view = View.inflate(getContext(), R.layout.settingview, null); this.addView(view); 第二种方式:View.inflate(getContext(), R.layout.settingview, this); 参数root:给view对象找一个父控件,相当于把view对象设置给了父控件,那这个写个this,this就是自定义控件 第二种是一个比较简单的写法,等号左边的View view都可以省略 this.addView(view)也可以省略掉,而且不设置属性params的话,父控件会使用原控件的属性 添加布局两种方式区别: 第一种方式:爹有了,直接找孩子,这个明显是亲生的 不在代码中设置属性params的话,控件使用父控件的属性 第二种方式:孩子有了,直接找爹,这种模式是喜当爹模式 不在代码中设置属性params的话,父控件会使用原控件的属性 第一种方式中,addView还有一些重载的方法,即addView(child, params) 参数params:是用代码给控件设置属性,使用这个方法就表示要设置控件在自定义控件中的属性 因为有时要根据控件不同去设置适应父控件 一般使用第二种方式,一行代码解决,但是有一些情况,还是要使用第一种方式
实现一条线的效果,可在布局文件中添加view标签:
<view android:layout_width = "match_parent" android:layout_height = "0.5dp" //线的粗细 android:background="#770000" //线的颜色 android:layout_below="@id/tv_setting_des" //指定线在描述信息的下方 android:layout_marginTop="5dp" //指定线条距离描述信息的外上边距 />
Day02 05自定义控件的点击事件# **
上边实现了条目布局,接下来实现“提示更新”功能:“打开/关闭提示更新”
打开提示更新功能,下次打开app时如有最新版本,弹出“提示更新”对话框
关闭提示更新功能,下次打开app时就算有最新版本,也不弹出“提示更新”对话框
SettingActivity中只能初始化SettingView,没法初始化SettingView中的textview和checkbox
SettingActivity中要想改变textView,checkbox值,必须要SettingView提供相应方法
自定义组合控件点击事件实现:
1.SettingView中初始化控件并提供相应方法,且在相应方法中给对应控件赋值
1)init方法中初始化控件
2)添加方法setTitle,用来设置标题,要设置标题,需传过来一个标题,即参数写成String title,
添加方法setDes,用来设置描述信息,参数需传过来一个des
添加方法setChecked,用来设置checkbox状态,参数需传过来一个状态isChecked
添加方法isChecked,用来获取checkbox状态,返回值写为boolean
条目描述信息“关闭/打开提示更新”随checkbox状态改变
checkbox被选中,描述信息变为“打开提示更新”
checkbox未选中,描述信息变为“关闭提示更新”
所以需要isChecked方法来获取状态
setChecked用以设置状态,isChecked方法用以获取状态
3)在哪调用这些方法,就从哪传递过来这些title,des,isChecked数据
此处在SettingActivity中初始化自定义组合控件中控件值处调用,所以数据是从那传递过来
private TextView tv_setting_title; //标题
private TextView tv_setting_des; //描述信息
private CheckBox cb_setting_isupdate; //checkbox
private void init(){
//添加布局文件
View view = View.inflate(getContext(), R.layout.settingview, this);
tv_setting_title = (TextView) view.findViewById(R.id.tv_setting_title);
tv_setting_des = (TextView) view.findViewById(R.id.tv_setting_des);
cb_setting_isupdate = (CheckBox) view.findViewById(R.id.cb_setting_isupdate);
}
public void setTitle(String title){
//给标题设置内容,内容为传递过来的title
tv_setting_title.setText(title);
}
public void setDes(String des){
//给描述信息设置内容,内容为传递过来的des
tv_setting_des.setText(des);
}
public void setChecked(boolean isChecked){
//给checkbox设置状态,状态为传递过来的状态
cb_setting_isupdate.setChecked(isChecked);
}
public boolean isChecked(){
//给控件cb_setting_isupdate设置的这个isChecked()是android提供的,不是我们自己写的isChecked,名称一样
return cb_setting_isupdate.isChecked();
}
2.在SettingActivity中初始化SettingView,并初始化SettingView中控件的值
1)在onCreate方法中初始化SettingView
2)调用SettingView提供的方法来初始化以及改变控件值
可在onCreate方法中初始化控件值,但是后期条目越多,onCreate方法中代码会越多
为达解耦性,专门写一个提示更新方法update,在onCreate方法中调用下即可
private SettingView sv_setting_update;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setting);
//初始化自定义组合控件
sv_setting_update = (SettingView) findViewById(R.id.sv_setting_update);
update();
}
/**
-
提示更新
*/
private void update() {
//初始化自定义组合控件中控件的值
sv_setting_update.setTitle(“提示更新”);
sv_setting_update.setDes(“打开提示更新”);
sv_setting_update.setChecked(true);
}运行程序,点设置中心,原描述信息:“关闭提示更新”变为了“打开提示更新” 原checkbox状态:未选中变为了选中 3.给SettingView设置点击事件,并根据CheckBox状态展示不同描述信息 /**
-
提示更新
*/
private void update() {
//初始化自定义组合控件中控件的值
sv_setting_update.setTitle(“提示更新”);
sv_setting_update.setDes(“打开提示更新”);
sv_setting_update.setChecked(true);
//给自定义组合控件设置点击事件
sv_setting_update.setOnClickListener(new onClickListener(){@Override public void onClick(View v){ if (sv_setting_update.isChecked()) { // 关闭提示更新 sv_setting_update.setDes("关闭提示更新"); sv_setting_update.setChecked(false); } else { // 打开提示更新 sv_setting_update.setDes("打开提示更新"); sv_setting_update.setChecked(true); } } }); } 运行程序,在设置中心,点击“提示更新”条目 描述信息就变成了“关闭提示更新”,checkbox状态变为未选状态 再点击,描述信息就变成了“打开提示更新”,checkbox状态变为选中状态 4.CheckBox拦截整个自定义组合控件“获取焦点和点击事件”的解决 复现:不点击条目,点击checkbox框框,看到checkbox选中,但描述信息没改变 原因: checkbox本身具有获取焦点和点击事件功能 点击checkbox时,将把整个自定义组合控件的焦点和点击事件都拦截 导致创建的对checkbox状态进行点击监听的方法没执行 解决: 在settingview.xml中的checkbox控件中添加两个属性: android:clickable="false" android:focusable="false" clickable : 设置是否可以点击 true可以,false不可以 focusable :设置是否可以获取焦点 true可以,false不可以 全设置false,就可将获取焦点和点击事件给自定义组合控件 运行程序,再点击checkbox没问题了
Day02 06屏蔽自动更新# **
刚实现了SettingView的点击事件,这里还有几个bug
bug1:关闭提示更新,返回主界面,再次进入设置中心,提示更新又变成打开的解决
bug2:关闭提示更新,退出应用,再次打开应用,更新版本对话框还会弹出的解决
设置中心的“提示更新”条目应全局有效,并控制“更新版本的对话框”是否弹出
解决:
1.使用Sharedpreferences保存状态,回显状态
2.根据提醒更新的状态决定更新版本对话框是否弹出
1.使用Sharedpreferences保存状态,回显状态
1)创建sp
在SettingActivity的onCreate方法获取sp,即:
private SharedPreferences sp;
private SettingView sv_setting_update;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setting);
sp = getSharedPreferences("config", MODE_PRIVATE);
//初始化自定义组合控件
sv_setting_update = (SettingView) findViewById(R.id.sv_setting_update);
update();
}
getSharedPreferences(name, mode)
参数name:保存信息的xml文件名 写成"config"
参数mode:权限,写MODE_PRIVATE
在高版本中对sp保存数据进行了安全升级,不管是设置读或写操作,最终都是私有权限
2)sp保存状态
将“关闭提示更新”状态,“打开提示更新”状态分别保存到sp中
在SettingActivity的update方法中的onClick方法的if,else判断中进行保存
sv_setting_update.setOnClickListener(new onClickListener(){
@Override
public void onClick(View v){
if (sv_setting_update.isChecked()) {
// 关闭提示更新
sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
Editor edit = sp.edit();
edit.putBoolean("update",false);
edit.commit();
//edit.apply();
} else {
// 打开提示更新
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
Editor edit = sp.edit();
edit.putBoolean("update",true);
edit.commit();
}
}
});
edit.commit()和edit.apply()区别:
这两都可以保存到文件中
edit.apply()仅限于9版本之上才会保存到文件中,9版本之下是保存到内存中,关闭应用后,保存的东西会消失
清单文件中的minSdkVersion=8,所以edit.apply()在这里不太适用,所以还是用这个edit.commit()
保存“关闭/打开提示更新”状态,都要创建edit,提取出来放在if外边,也可把commit提取出来
3)sp回显状态(动态设置初始化状态)
05课“5.在SettingActivity中初始化自定义组合控件中控件的值”处,当时没进行状态判断,初始化控件的值为固定值
将“关闭提示更新”状态,“打开提示更新”状态分别从sp中动态回显到初始化状态
这样就解决了每次进入设置中心时,初始化的状态都是写死的问题
在SettingActivity的update方法中的onClick方法上边,即:
/**
-
提示更新
*/
private void update() {// 初始化自定义组合控件中的控件的值 sv_setting_update.setTitle("提示更新"); // 根据sp保存的状态设置初始化的状态 if (sp.getBoolean("update", true)) { sv_setting_update.setDes("打开提示更新"); sv_setting_update.setChecked(true); } else { sv_setting_update.setDes("关闭提示更新"); sv_setting_update.setChecked(false); } sv_setting_update.setOnClickListener(new onClickListener(){ @Override public void onClick(View v){ Editor edit = sp.edit(); if (sv_setting_update.isChecked()) { // 关闭提示更新 sv_setting_update.setDes("关闭提示更新"); sv_setting_update.setChecked(false); edit.putBoolean("update",false); //edit.apply(); } else { // 打开提示更新 sv_setting_update.setDes("打开提示更新"); sv_setting_update.setChecked(true); edit.putBoolean("update",true); } edit.commit(); } }); } 运行程序,点设置中心,把“更新状态”调到“关闭提示更新” 退出后再进去,看到还是退出时的“关闭提示更新”状态
2.根据提醒更新的状态决定更新版本对话框是否弹出:
1)创建一个sp SplashActivity的update中连接了服务器查看是否有最新版本,有,弹出更新版本对话框 如果关闭了设置中心“提示更新”,就没必要再去连接服务器查看是否有最新版本了 SplashActivity中的onCreate中创建sp,从sp中取出数据来,即: private SharedPreferences sp; @Override protected void onCreate(Bundle savedInstanceState) { sp = getSharedPreferences("config", MODE_PRIVATE); } config是保存信息的xml文件的文件名,不是里边数据的名称,是不会被覆盖掉的 2)使用sp回显状态,根据回显状态判断是否弹出版本更新对话框 从sp中拿出“提示更新”状态,如果key不为update,缺省值就为true,为true就调用更新版本的update()方法弹出对话框 否则跳转到主界面 private SharedPreferences sp; @Override protected void onCreate(Bundle savedInstanceState) { sp = getSharedPreferences("config", MODE_PRIVATE); if (sp.getBoolean("update", true)) { update(); } else { enterHome(); } } 运行程序,把设置中心的“提示更新”设置为“关闭提示更新”,退出程序再进入,是不是没有欢迎界面了 看下SplashActivity的update()中是怎么干的,看到在弹出对话框之前睡了2秒钟 在跳转到主界面时,也可让它睡上2秒 private SharedPreferences sp; @Override protected void onCreate(Bundle savedInstanceState) { sp = getSharedPreferences("config", MODE_PRIVATE); if (sp.getBoolean("update", true)) { update(); } else { System.out.sleep(2000); enterHome(); } } 再运行程序,显示的是一个空白页面,2秒之后跳转到了主界面,这个空白页面就是SplashActivity,出现空白页面是因为onCreate方法在主线程,这里是在主线程睡了 主线程中不能睡原因: 主线程做渲染界面操作,布局文件中控件都是通过主线程渲染出来的,让主线程睡2秒就没办法渲染界面了,SplashActivity还是打开的,activity默认没有布局时就是空白界面 解决:new一个子线程,把睡2秒的操作放进去,即: private SharedPreferences sp; @Override protected void onCreate(Bundle savedInstanceState) { sp = getSharedPreferences("config", MODE_PRIVATE); if (sp.getBoolean("update", true)) { update(); } else { new Thread() { public void run() { SystemClock.sleep(2000); enterHome(); }; }.start(); } 运行程序没有问题了 在这里开一个子线程表示子线程和主线程是并行运行的,只不过另开了一个子线程去睡2秒钟,这个不影响渲染 到这,屏蔽自动更新的操作做完了
Day02 07自定义属性 # **
settingActivity的提示更新update()中的“根据保存的状态设置初始化状态”,是通过自定义组合控件提供的方法来更改自定义组合控件中控件的值
// 初始化自定义组合控件中的控件的值
sv_setting_update.setTitle("提示更新");
// 根据保存的状态设置初始化的状态
if (sp.getBoolean("update", true)) {
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
} else {
sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
}
settingActivity中采用上边方式设置(在代码中设置)自定义组合控件中控件的值,很不方便
可以在布局文件activity_setting.xml中找到自定义组合控件SettingView,直接设置属性itcast:title="提示更新"
查看系统是如何定义属性的:
查看系统属性文件:sdk\platforms\android-16\data\res\values\attrs.xml
使用自定义属性初始化自定义组合控件中控件的值步骤:
1.在values目录下建一个attrs.xml文件(固定写法)
模仿系统操作定义我们的属性
attrs.xml
<resources>
//名称为自定义组合控件的全类名,只用SettingView也可以
<declare-styleable name="cn.itcast.mobilesafexian02.ui.SettingView">
<!-- title:代表标题 format : 格式 类型-->
<attr name="title" format="string" />
<!-- des_on : 代表打开提示更新-->
<attr name="des_on" format="string" />
<!-- des_off : 代表关闭提示更新-->
<attr name="des_off" format="string" />
</declare-styleable>
</resources>
title,des_on,des_off是我们自定义的各种属性名称
自定义属性定义成功后可以到R文件中查看,此时会看到已经自动创建出了attr来,里边是我们自定义的各种属性名称的唯一标识
2.到布局文件activity_setting.xml中使用自定义属性
android中有title这个属性,但是没有des_on,des_off属性(自己定义的)
android为系统属性定义了命名空间,使用自定义属性也必须加自己的命名空间
怎样写自己的命名空间:
拷贝系统的命名空间,将xmlns:android改为xmlns:itcast
将尾部的android包名改为清单文件中的包名cn.itcast.mobilesafexian02
xmlns:itcast="http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02"
意思是要找自定义属性时,是到项目的attrs.xml中去找,不是去android下的attrs.xml中找
将自己的命名空间也加到activity_setting.xml的根布局处
activity_setting.xml
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:itcast="http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<cn.itcast.mobilesafexian02.ui.SettingView
android:id="@+id/sv_setting_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- 此便为自定义属性-->
itcast:title="提示更新"
itcast:des_on="打开提示更新"
itcast:des_off="关闭提示更新"
</cn.itcast.mobilesafexian02.ui.SettingView> 自定义属性在activity_setting.xml布局的SettingView控件处也使用了,但是没有意义 系统属性android:layout_height="wrap_content"等,其实是在代码中使用了才有意义了 要让自定义属性itcast:title="提示更新"有意义,必须到自定义组合控件SettingView中使用 3.在settingView代码中获取指定属性值,设置给相应控件 1)获取布局文件中自定义属性的值 两个参数的构造函数是在布局文件使用时调用,所以使用两个参数的构造函数 参数AttributeSet保存有控件的所有属性,可过attrs获取控件的所有属性 方式1:通过getAttributeCount()可以获取所有属性值 使用attrs.getAttributeCount(),获取控件属性个数,可以输出看下 通过遍历属性个数来获取相应属性值 运行程序打印出来属性个数为6,activity_setting.xml中的SettingView控件属性个数刚好是6个 打印出来属性值为@2131230722,是6个属性中的id,是控件在内存中的唯一标识 -1是width属性的值match_parent,-2是height属性的值wrap_content 后边打印出来相应属性值,分别为title的值“提示更新”,des_on的值“打开提示更新”,des_off的值“关闭提示更新” 这里是获取出所有属性的值,但是不能获取指定属性的值,所以这种方式在此处不适用 方式2:通过getAttributeValue(namespace,name)可以获取特定属性的值 参数namespace : 命名空间 参数name : 属性的名称 也就是通过命名空间和属性的名称可以获取指定属性的值 2)将获取到的自定义属性的值设置给对应控件 获取到title,des_on,des_off属性值后,分别设置给对应控件 设置checkbox选中(des_on),不选中(des_off)时,要根据SettingView中写的isChecked()来判断 private String des_on; private String des_off; //在布局文件使用的时候调用 public SettingView(Context context, AttributeSet attrs) { super(context, attrs); init(); // 方式1 //int count = attrs.getAttributeCount();//获取控件属性的个数 //System.out.println(“属性个数:”+count); //for (int i = 0; i < count; i++) { 获取相应的属性的值 //System.out.println(attrs.getAttributeValue(i)); //} //方式2: String title = attrs.getAttributeValue(“http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02”, “title”); des_on = attrs.getAttributeValue(“http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02”, “des_on”); des_off = attrs.getAttributeValue(“http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02”, “des_off”); //使用获取到的属性的值 //设置标题控件的值 tv_setting_title.setText(title); //设置描述信息的值 //获取checkbox的状态 if (isChecked()) { tv_setting_des.setText(des_on); }else{ tv_setting_des.setText(des_off); } } 3)在setChecked()方法中封装“设置描述信息的值” 在SettingView中写的setChecked()中,增加设置描述信息的值,即将上边的if判断拷贝一份到setChecked方法中 /**
-
设置checkbox的状态
-
@param isChecked
*/
public void setChecked(boolean isChecked){cb_setting_isupdate.setChecked(isChecked); //设置描述信息的值 相当于将sv_setting_update.setDes("打开提示更新")封装到了这个方法中 if (isChecked()) { tv_setting_des.setText(des_on); }else{ tv_setting_des.setText(des_off); } } 4.在SettingActivity的unpdate()中注释掉相应“初始化自定义组合控件中的控件的值”的代码 在“3.自定义组合控件settingView中获取指定属性的值,设置给相应的控件” 处,已经设置了属性的值 在SettingActivity中初始化自定义组合控件中的控件值时就没有必要再去设置了 /**
-
提示更新
*/
private void update() {
// 初始化自定义组合控件中的控件的值
// sv_setting_update.setTitle(“提示更新”);
// 根据保存的状态设置初始化的状态
// defValue : 没有update的时候使用的值
if (sp.getBoolean(“update”, true)) {
// sv_setting_update.setDes(“打开提示更新”);
sv_setting_update.setChecked(true);
} else {
// sv_setting_update.setDes(“关闭提示更新”);
sv_setting_update.setChecked(false);
}sv_setting_update.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Editor edit = sp.edit(); if (sv_setting_update.isChecked()) { // 关闭提示更新 // sv_setting_update.setDes("关闭提示更新"); sv_setting_update.setChecked(false); edit.putBoolean("update", false); // edit.apply();//可以保存到文件中,但是仅限于9版本之上,9版本之下是保存到内存中的 } else { // 打开提示更新 // sv_setting_update.setDes("打开提示更新"); sv_setting_update.setChecked(true); edit.putBoolean("update", true); } edit.commit(); } }); } 但是setChecked的值,得留着,当上边setChecked返回true时 SettingView中的setChecked中的参数isChecked就代表true,接着给checkbox设置状态为isChecked,也就是true 然后获取checkebox的状态isChecked,判断isChecked状态,这个isChecked状态前边设置过,所以它现在是true,所以也会相应的把描述信息的值改为打开提示更新,否则,把描述信息的值改为关闭提示更新 上边这段话相当于将SettingActivity中update()方法中的sv_setting_update.setDes("打开提示更新") 封装到了SettingView中的setChecked方法中 这样就可以把SettingActivity中的那个操作注释掉, 同样把SettingActivity中的sv_setting_update.setDes("关闭提示更新")也注释掉 /**
-
设置checkbox的状态
-
@param isChecked
*/
public void setChecked(boolean isChecked){
cb_setting_isupdate.setChecked(isChecked);
//相当于将sv_setting_update.setDes(“打开提示更新”);封装到了这个方法中
if (isChecked()) {
tv_setting_des.setText(des_on);
}else{
tv_setting_des.setText(des_off);
}
}运行程序,点击“设置中心”,点击checkebox为选中状态,描述信息也变为了“打开提示更新”,再点击checkedbox为未选中状态,描述信息变为了“关闭提示更新”了 和以前的操作一模一样,但是写法变简单了 自定义属性好处: 1.写法变简单了 2.设置中心还要添加相同格式条目时,将布局文件activity_setting.xml中的自定义控件一复制 将title,des_on,des_off的值一改,然后到设置界面SettingActivity中”像第4步“一样一设置(像“提示更新”条目一样一设置) 就可以实现相应条目的事件了
Day02 08.设置密码对话框# ***
实现了设置中心“提示更新”功能,接下来开发手机防盗模块
创建“设置密码对话框”
1.在HomeActivity的GridView的点击事件onItemClick的switch判断中,添加手机防盗条目
并调用一个设置密码对话框的方法showSetPassWordDialog()
switch (position) {
case 0://手机防盗
showSetPassWordDialog();
break;
}
2.HomeActivity中将showSetPassWordDialog()创建出来
new一个Builder出来,返回一个AlertDialog.Builder
给它设置setCancelable(false),设置对话框不能消失
Dialog中没法设置输入框EditText,但它有setView(view)方法,参数view需要一个view对象
那用View.inflate()添加布局文件后返回一个View对象
将布局文件setView进去后需要show
把控件全部初始化出来,“输入密码的输入框”和“再次输入密码的输入框”,还有“确定”和“取消”按钮
protected void showSetPassWordDialog(){
AlertDialog.Builder builder = new Builder(this);
View view = View.inflate(getApplicationContext(), R.layout.dialog_setpassword, null);
//初始化控件
final EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password);
final EditText et_setpassword_confrim = (EditText) view.findViewById(R.id.et_setpassword_confrim);
Button btn_ok = (Button) view.findViewById(R.id.btn_ok);
Button btn_cancel = (Button) view.findViewById(R.id.btn_cancel);
builder.setView(view);
builder.show();
}
dialog_setpassword.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffffff">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置密码"
android:textSize="25sp"
android:gravity="center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#8866ff00"
android:textColor="#000000"
/>
<EditText
android:id="@+id/et_setpassword_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberPassword"
android:textColor="#000000"
android:hint="请输入密码">
</EditText>
<EditText
android:id="@+id/et_setpassword_confrim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberPassword"
android:textColor="#000000"
android:hint="请再次输入密码"/>
<!-- LinearLayout : 默认水平排列 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/btn_ok"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="确定"
android:textColor="#000000"/>
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消"
android:textColor="#000000"/>
</LinearLayout>
</LinearLayout>
EditText设置提示文字:android:hint="请输入密码"
运行程序,点击手机防盗,弹出了“设置密码”对话框,字体、颜色都和效果图一样后
“设置密码”对话框就2个功能,“确定”,“取消”
5.实现取消按钮功能,给取消按钮实现点击事件,并隐藏对话框
这里给“密码对话框”的“取消”按钮设置点击事件时,用的是View.OnClickListener
SplashActivity的"版本升级对话框"处,使用的是DialogInterface.OnClickListener
因为在“版本升级对话框”那里是通过builder.setPositiveButton去设置按钮,是在dialog中创建了一个按钮
“密码对话框”这里是通过btn_cancel.setOnClickListener设置取消按钮,取消按钮在dialog_setpassword.xml中,是在View对象中创建了一个按钮
这两个按钮所在的位置不同,注定使用的是不同的点击事件
隐藏对话框一般用的是:dialog.dismiss();
但这里没有dialog,在SplashActivity中的“提醒用户升级”处,用的是DialogInterface下的OnClickListener
所以它onClick方法里边的参数中就有一个dialog,而这里使用的是View下边的点击事件,它onClick方法里边的参数中没有dialog
前边学的显示对话框的两种方式:
builder.show();
builder.create().show();
那这个builder.create(),它会返回一个AlertDialog,那不用builder.show()了
用builder.create(),得到一个AlertDialog,然后再show,即:
AlertDialog dialog = builder.create();
dialog.show();
然后在onClick方法中,就可以使用这个dialog去dismiss了,即:
dialog.dismiss();
第5步整体代码如下:
private AlertDialog dialog;
protected void showSetPassWordDialog(){
AlertDialog.Builder builder = new Builder(this);
View view = View.inflate(getApplicationContext(), R.layout.dialog_setpassword, null);
//初始化控件
final EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password);
final EditText et_setpassword_confrim = (EditText) view.findViewById(R.id.et_setpassword_confrim);
Button btn_ok = (Button) view.findViewById(R.id.btn_ok);
Button btn_cancel = (Button) view.findViewById(R.id.btn_cancel);
btn_cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏对话框
dialog.dismiss();
}
});
dialog = builder.create();
builder.setView(view);
dialog.show();
}
这里的dismiss方法,不会报空指针异常
因为btn_cancel按钮属于view对象,view对象又是通过setView添加到builder里边,builder通过show显示
只有你先builder.create得到这个dialog对象,然后再显示出来,才能看到按钮,才能点击按钮隐藏对话框
你为空的判断在:
AlertDialog dialog = builder.create();
dialog.show();
这里已完全保证不会发生,在这里边创建一个dialog是绝对不会为空的
运行程序,点击手机防盗,点击"取消"按钮,对话框直接消失了
6.实现"确定按钮"操作
1)获取密码输入框输入的内容
需要给et_setpassword_password去final一下,在内部类中调用外部类操作,这是java中的知识
final EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password);
2)判断输入的密码是否为空,用的是TextUtils.isEmpty,这个方法可以判断2种情况:
第一种: null 没有内存
第二种:空字符串 "" 没有内存但是有内容 即空字符串也是一个字符串
如输入的密码是空,提醒用户“请输入密码”,同时return,不让执行下一个操作
3)如输入的密码不为空,去获取再次输入的密码
同理,et_setpassword_confrim控件也需要final一下
final EditText et_setpassword_confrim = (EditText) view.findViewById(R.id.et_setpassword_confrim);
4)判断两次密码输入是否一致,如果两次密码输入一致的话,保存密码,同时隐藏对话框,提醒用户"密码设置成功",如果不一致的话,提醒用户"两次密码输入不一致"
保存密码,其实就是保存到SharedPrefrences中
第6部整体代码如下:
btn_ok.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//1.获取密码输入输入的内容
String password = et_setpassword_password.getText().toString().trim();
//2.判断输入的密码是否为空
if (TextUtils.isEmpty(password)) {
Toast.makeText(getApplicationContext(), "请输入密码", 0).show();
return;
}
//3.获取再次输入的密码
String confrim_password = et_setpassword_confrim.getText().toString().trim();
//4.判断两次密码输入是否一致
if (password.equals(confrim_password)) {
//保存设置的密码
Editor edit = sp.edit();
edit.putString("password", MD5Utils.digestPassword(password));
edit.commit();
//隐藏对话框
dialog.dismiss();
//提醒用户
Toast.makeText(getApplicationContext(), "密码设置成功", 0).show();
}else{
Toast.makeText(getApplicationContext(), "两次密码输入不一致", 0).show();
}
}
});
运行后看到效果实现了
Day02 09输入密码对话框# **
设置密码对话框完成,设置了成功的密码后,再次点击手机防盗时需弹出输入密码对话框来输入密码
在主界面HomeActivity的“//判断当前点击的条目的位置” 中case 0 处
判断是否已设置密码,没,弹出设置密码对话框,有,弹出输入密码对话框
case 0://手机防盗
//判断是否已经设置过密码,没有,弹出设置密码对话框,有,弹出输入密码对话框
if (TextUtils.isEmpty(sp.getString("password", ""))) {
showSetPassWordDialog();
}else{
showEnterPassWordDialog();
}
break;
在HomeActivity中创建,选中showEnterPassWordDialog(),点击Create method 'showEnterPassWordDialog()' in type 'HomeActivity',
int count = 0;
/**
-
输入密码对话框
*/
protected void showEnterPassWordDialog() {AlertDialog.Builder builder = new Builder(this); builder.setCancelable(false);//设置对话框不能消息 //将布局文件转化成了view对象 View view = View.inflate(getApplicationContext(), R.layout.dialog_enterpassword, null); final EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password); Button btn_ok = (Button) view.findViewById(R.id.btn_ok); Button btn_cancel = (Button) view.findViewById(R.id.btn_cancel); ImageView iv_enterpassword_hide = (ImageView) view.findViewById(R.id.iv_enterpassword_hide); iv_enterpassword_hide.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //隐藏显示密码操作 if (count%2 == 0) { //显示密码 et_setpassword_password.setInputType(1); }else{ //隐藏密码 et_setpassword_password.setInputType(129); } count++; } }); btn_ok.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //1.获取输入的内容 String password = et_setpassword_password.getText().toString().trim(); //2.判断输入的内容是否为空 if (TextUtils.isEmpty(password)) { Toast.makeText(getApplicationContext(), "请输入密码", 0).show(); return; } //3.获取保存的密码 String sp_password = sp.getString("password", ""); //4.判断两个密码是否一致 if (MD5Utils.digestPassword(password).equals(sp_password)) { //跳转到手机防盗界面 Intent intent = new Intent(HomeActivity.this,LostFindActivity.class); startActivity(intent); //隐藏对话框 dialog.dismiss(); //提醒用户 Toast.makeText(getApplicationContext(), "密码正确", 0).show(); }else{ Toast.makeText(getApplicationContext(), "密码输入错误", 0).show(); } } }); btn_cancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); } }); builder.setView(view); dialog = builder.create(); dialog.show(); }
“输入密码对话框”和设置密码对话框非常相似,可以粘贴复制,把设置密码对话框的布局文件dialog_setpassword.xml复制改为dialog_enterpassword.xml,将重新输入密码的控件删除,并将title改为“输入密码”,
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="输入密码" android:textSize="25sp" android:gravity="center_horizontal" android:paddingTop="10dp" android:paddingBottom="10dp" android:background="#8866ff00" android:textColor="#000000" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" <!-- layout_weight : 不仅有显示比例操作,还可决定渲染的级别,值越大级别越低 textCursorDrawable : 设置光标颜色 @null : 根据输入文字的颜色一致 --> <EditText android:id="@+id/et_setpassword_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:ems="10" android:inputType="textPassword" android:textColor="#000000" android:hint="请输入密码" android:textCursorDrawable="@null" </EditText> <ImageView android:id="@+id/iv_enterpassword_hide" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/btn_circle_selected" android:layout_gravity="center_vertical" /> </LinearLayout> <!-- LinearLayout : 默认水平排列 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/btn_ok" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="确定" android:textColor="#000000" android:background="@drawable/button" /> <Button android:id="@+id/btn_cancel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="取消" android:textColor="#000000" android:background="@drawable/button" /> </LinearLayout> </LinearLayout> “输入密码对话框”布局和“设置密码对话框”布局的id全部相同是可以的,android允许不同的xml里边有相同id,但不允许同一个布局中有相同id 到HomeActivity中在输入密码对话框showEnterPassWordDialog方法中将布局替换成dialog_enterpassword.xml
Day02 10兼容低版本UI # *** 4.1.2 2.3.3
问题:
低版本中UI处理方式和高版本中UI处理方式不一样,androidUI的走向是版本越高,UI风格越扁平化
高版本中输入框是一条线,低版本中输入框有一个矩形背影
高版本中界面背景是白色,低版本中是黑色的,且在低版本中对话框上部是黑色的凸起
解决:
1.将高版本的界面背景刻意改成白色,这样在低版本时界面也就是白色了
(在dialog_setpassword.xml的Linearlayout中添加android:background="#ffffff")
2.解决低版本中对话框上部黑色的凸起
需要到代码中修改,在HomeActivity中找到设置密码对话框showSetPassWordDialog方法
将builder.setView(view)改成dialog.setView();
虽然效果一样,但dialog.setView()有个重载的方法
dialog.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom)
viewSpacingLeft:距离对话框内左边框的距离
viewSpacingTop: 距离对话框内上边框的距离
viewSpacingRight: 距离对话框内右边框的距离
viewSpacingBottom: 距离对话框内底边框的距离
都改成0即可,即:
dialog.setView(view, 0, 0, 0, 0);
Day02 11.MD5加密 #
设置密码和输入密码对话框实现后,还要用MD5加密实现防盗作用
问题:
密码是保存在本地的sharedpreference中的,可以在cmd.exe中
用adb shell-->cd data-->ls-->cd data-->ls-->找到应用程序包名cn.itcast.mobilesafexian02
继续 cd cn.itcast.mobilesafexian02-->ls-->里面有shared_prefs,再cd shared_prefs-->ls-->cat config.xml-->
发现里面有
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="update" value="false"/>
<string name="password">123<string>
</map>
即可知道密码为123
解决:
sp中保存密码时不能保存成明文,用MD5加密保存成密文
android抓包现象严重,涉及到密码等应该加密
1.md5: 特性 不可逆,明文转密文后,密文不能转成明文
2.MD5加盐:
以前安全的标准MD5现在不在安全,网上出现MD5解析工具数据库中存着密文+明文映射,拿MD5密文查询密文就可得到对应明文
‘MD5加盐不规则操作’ 用MD5解析工具是解析不出来的,即使解析成明文也不是原密码,因为+1(即加盐),是不规则加密
3.银行加密(安全性甚至超过MD5加盐操作):6位数字
随机算法给出10-30之间的一个数字 如计算出21会进行21次MD5加密(将加密后的密文不断复制加密21次)中间还会有加盐等操作,变得更复杂
拿这个加密后的密文再去MD5解析器上解析时就是付费的操作,这是MD5解析工具的盈利模式,将难破译,重复次数多或比较复杂密码设成付费操作
步骤:
1.MD5Utils.java文件
public class MD5Utils {
/**
-
md5加密
-
@param password
-
@return
*/
public static String digestPassword(String password){
StringBuilder sb = new StringBuilder();
try {
//1.获取数据摘要器
//参数:加密的方式
MessageDigest messageDigest = MessageDigest.getInstance(“MD5”);
//2.将一个byte数组进行加密,返回一个加密的byte数组
byte[] digest = messageDigest.digest(password.getBytes());
//3.遍历byte数组
for (int i = 0; i < digest.length; i++) {
//4.那byte元素去和int类型的255与运算,得到int类型的正整数
//byte: -128 - 127
int result = digest[i] & 0xff;
//5.因为int类型取值范围比较大,转化成十六进制的字符串
// String hexString = Integer.toHexString(result)+1;//不规则加密,加盐
String hexString = Integer.toHexString(result);
if (hexString.length() < 2) {
// System.out.print(“0”);
sb.append(“0”);
}
// System.out.println(hexString);
sb.append(hexString);
//e10adc3949ba59abbe56e057f20f883e
}
// System.out.println(sb.toString());
return sb.toString();
} catch (NoSuchAlgorithmException e) {
//找不到加密方式的异常
e.printStackTrace();
return “”;
}
}
}2.对保存的密码和输入的密码进行MD5加密 Homeactivity保存设置的密码showSetPassWordDialog方法中找到4.判断两次密码输入是否一致中,将: edit.putString("password",password); 改成edit.putString("password",MD5Utils.digestPassword(password)); 输入密码后,获取保存的密码验证是否一致时,因为保存的密码已经用MD5设置成了密文了,所以输入的密码也需要MD5加密一 次,两个密文去比较是否相同 在输入密码showEnterPassWordDialog方法中 //判断两个密码是否一致处,需要将: if(password.equal(sp_password)) 改成if(MD5Utils.digestPassword(password).equals(sp_password)) 初次将输入密码改为MD5加密时,以前保存的密码没经过MD5加密,会造成错误 解决:打开fileExplorer将data/data/shared_prefs中的config.xml删除即可(在删文件时一定要选中左边目标模拟器,否则删不掉) 运行程序正常,接下来通过cmd.exe看是否能盗取到密码 <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <boolean name="update" value="false"/> <string name="password">202cb962ac59075b964b07152d234b70<string> </map> 看到密码变成了"202cb962ac59075b964b07152d234b70",再想盗取密码已不可能
Day02 12.13.14.隐藏显示密码1、2,3# **
市面上的软件在输入密码框右边有图片按钮,点击按钮可显示输入的密码,再次点击后可隐藏输入的密码
步骤:
1.给输入密码框右边添加一个图片
到dialog_enterpassword.xml添加一个linearlayout,把输入密码框放进去,密码输入框右边添加一个imageview
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
<!-- layout_weight : 不仅有显示比例操作,还可决定渲染的级别,值越大级别越低 imageview没有设置说明是0,0比1小,渲染级别比1大,所以渲染的时候先渲染imageview,剩下的部分再去渲染edittext,所以imageview能显示出来-->
<EditText
android:id="@+id/et_setpassword_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!--添加权重属性,设为1 -->
android:layout_weight="1"
android:ems="10"
android:inputType="textPassword"
android:textColor="#000000"
android:hint="请输入密码"
</EditText>
<ImageView
android:id="@+id/iv_enterpassword_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/btn_circle_selected"
android:layout_gravity="center_vertical"
/>
</LinearLayout>
2.HomeActivity中输入密码对话框showEnterPassWordDialog方法中添加如下代码:
//找到图片按钮
ImageView iv_enterpassword_hide = (ImageView) view.findViewById(R.id.iv_enterpassword_hide);
//定义计数器
int count =0;
//给图片按钮设置点击事件
iv_enterpassword_hide.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏/显示密码操作
if (count%2 == 0) {
//如果count对2求余恒等于0,显示密码 (对2求余是否等于0只有两种可能,正好对应显示隐藏两种状态,还可用true/flase等好几种方式)
//InputType属性实现了隐藏/显示密码 numberpassword是隐藏密码,只能输入数字,textpassword是隐藏密码 输入文字,数字,字母都可以,text是显示密码
et_setpassword_password.setInputType(1);
}else{
//否则隐藏密码
et_setpassword_password.setInputType(129);
}
//计数器累加一下
count++;
}
});
查看布局文件中属性值的功能:
打开sdk/platforms/android-16/data/res/values/attrs.xml
ctrl+alt+f 搜索inputtype 双击inputtype
发现属性none的值为十六进制的 0
text的值为十六进制的 1
textpassword的值为十六进制的81,用计算器转化成十进制后是 129
显示/隐藏密码原理:
实际上是对setInputType属性的值进行动态修改
Day02 15.界面跳转逻辑 # **
手机防盗模块功能及界面跳转逻辑总结:
1.在设置密码对话框的方法showSetPassWordDialog中
1.1点击取消按钮:隐藏对话框
1.2点击确定按钮:
1)获取密码输入框输入的内容
2)判断输入的密码是否为空
为空,提示"请输入密码"
不为空
3)获取再次输入的密码
4)判断两次密码输入是否一致
一致,保存密码
不一致,提示"两次密码输入不一致"
2.HomeActivity条目点击事件的case0条目处,判断是否已设置过密码,即:
没有,弹出设置密码对话框,保存设置的密码
有,弹出输入密码对话框
2.1点击取消按钮时:隐藏对话框
2.2点击确定按钮时:
1)获取输入的密码
2) 判断输入的密码是否为空
为空,提示"请输入密码"
不为空,获取保存的密码
3)判断输入的密码和保存的密码是否一致
不一致,提示“密码输入错误”
一致,跳转到手机防盗界面LostFindActivity
3.手机防盗界面中判断用户是否是第一次进入
是,跳转手机防盗设置向导界面1,即SetUp1Activity进行手机防盗功能设置
不是,显示设置的手机防盗功能
1)加载activity_lostfind.xml
2)根据保存的安全号给控件设置安全号码
3)根据保存的防盗保护是否开启状态,设置相应图片
是,设置图片为lock,即:tv_lostfind_islock.setImageResource(R.drawable.lock);
否,设置图片为unlock,即:tv_lostfind_islock.setImageResource(R.drawable.unlock);
4.在手机防盗设置向导界面1点击next按钮,跳转到手机防盗设置向导界面2,即SetUp2Activity
1)在SettingView控件的点击事件onClick中,根据checkbox状态isChecked,设置描述信息状态
isChecked为true,改变checkbox为false,并解绑SIM卡,即edit.putString("sim", "")
isChecked为false,改变checkbox为true,并绑定SIM卡,即edit.putString("sim", sim);
2)在onCreate中根据保存的sim卡是否为空设置回显操作
为空,表示没有绑定,将checkbox状态改为false
不为空,表示绑定,将checkbox状态改为true
3)点击pre按钮,跳转回防盗设置向导界面1,即SetUp1Activity
4)点击next按钮时判断是否绑定SIM卡
否,提示"请绑定SIM卡"
是,跳转到手机防盗设置向导界面3,即SetUp3Activity
5.在手机防盗设置向导界面3,即SetUp3Activity
1)在选择联系人按钮的点击事件中
通过隐式意图跳转到选择联系人界面,onActivityResult()方法中获取ContactsActivity返回的联系人号码
将联系人号码显示到输入框中
2)点击pre按钮,跳转回手机防盗设置向导界面2,即SetUp2Activity
3)点击next按钮,判断输入的安全号码是否为空
是,提示"请输入安全号码"
不是,保存输入的安全号码
并跳转到手机防盗设置向导界面4,即SetUp4Activity
4)将保存的安全号码在onCreate方法中回显
6.在手机防盗设置向导界面4,即SetUp4Activity
1)在checkbox的点击事件onCheckedChanged方法中判断checkbox状态isChecked
isChecked为true,开启防盗保护,即:
设置checkbox文本为"您已经开启了防盗保护"
设置checkbox为true
保存防盗保护状态为true,也就是保存为开启防盗保护
设置checkbox文本为"您还没有开启了防盗保护"
设置checkbox为false
保存防盗保护状态为false,也就是保存为关闭防盗保护
2)根据保存的防盗保护状态进行回显操作,即:if (sp.getBoolean("protected", false))
如果保存的防盗保护状态为true,
设置checkbox文本为"您已经开启了防盗保护"
设置checkbox为true
如果保存的防盗保护状态为false,
设置checkbox文本为"您还没有开启了防盗保护"
设置checkbox为false
3)点击pre按钮,跳转回手机防盗设置向导界面3,即SetUp3Activity
4)点击next按钮,修改“是否是第一次进入手机防盗模块”为false
并跳转到手机防盗界面LostFindActivity
------------------------------------------------------------
设置密码和输入密码功能实现后,在输入密码界面点击确定按钮且密码正确时,跳转到手机防盗页面LostFindActivity
手机防盗模块包含两个功能:
1.让用户分别设置手机防盗功能的界面(SetUp1Activity,SetUp2Activity,SetUp3Activity,SetUp4Activity)
2.显示用户设置的手机防盗功能的界面(LostFindActivity)
LostFindActivity,SetUp1Activity,SetUp2Activity,SetUp3Activity,SetUp4Activity SetUpBaseActivity
步骤:
1.到HomeActivity输入密码对话框showEnterPassWordDialog中判断密码一致就跳到手机防盗界面LostFindActivity
//4.判断两个密码是否一致
if (MD5Utils.digestPassword(password).equals(sp_password)) {
//跳转到手机防盗界面
Intent intent = new Intent(HomeActivity.this,LostFindActivity.class);
startActivity(intent);
//隐藏对话框
dialog.dismiss();
//提醒用户
Toast.makeText(getApplicationContext(), "密码正确", 0).show();
}else{
Toast.makeText(getApplicationContext(), "密码输入错误", 0).show();
}
2.创建LostFindActivity
跳转到手机防盗界面时,要判断有没有进行过手机防盗设置
设置了就显示,没设置就跳转到1欢迎使用手机防盗界面
public class LostFindActivity extends Activity {
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sp = getSharedPreferences("config", MODE_PRIVATE);
//判断用户是否是第一次进入手机防盗模块,如果是的跳转手机防盗设置向导界面进行手机防盗功能设置,如果不是跳转到手机防盗界面显示设置的手机防盗功能
if (sp.getBoolean("first", true)) {
//跳转到功能设置向导界面
Intent intent = new Intent(this,SetUp1Activity.class);
startActivity(intent);
finish();
}else{
setContentView(R.layout.activity_lostfind);
//根据保存的安全号码和防盗保护状态设置相应显示操作
TextView tv_lostfind_safenum = (TextView) findViewById(R.id.tv_lostfind_safenum);
//根据保存的安全号设置安全号码
tv_lostfind_safenum.setText(sp.getString("safenum", ""));
ImageView tv_lostfind_islock = (ImageView) findViewById(R.id.tv_lostfind_islock);
//1.获取保存的防盗保护是否开启状态
boolean b = sp.getBoolean("protected", false);
//2.根据获取的状态设置相应的图片
if (b) {
tv_lostfind_islock.setImageResource(R.drawable.lock);
}else{
tv_lostfind_islock.setImageResource(R.drawable.unlock);
}
}
}
public void setup(View v){
//跳转到设置向导第一个界面
Intent intent = new Intent(this,SetUp1Activity.class);
startActivity(intent);
finish();
}
}
3.新建设置向导界面SetUp1Activity
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();
//必须在startActivity或者finish方法之后执行
//enterAnim : 新界面进入的动画
//exitAnim : 旧界面退出的动画
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
}
@Override
public void pre_activity() {
// TODO Auto-generated method stub
}
}
注意:
1.运行程序,进入手机防盗功能后,直接进入了设置向导第一个界面
因为在sp中还没保存first,没有保存,说明是第一次进入手机防盗,所以跳转到手机防盗设置向导界面,进行手机防盗功能设置
2.进入设置向导第一个界面后,点击返回键时,回到了一个空白界面,然后在点击返回键时,回到了主页面
因为在主界面homeactivity中跳转到的是手机防盗界面LostFindActivity
(Intent intent = new Intent(HomeActivity.this,LostFindActivity.class);)
而在手机防盗界面LostFindActivity中
//跳转到功能设置向导界面(Intent intent = new Intent(this,SetUp1Activity.class);
即又一次跳转,跳转到了设置向导界面setup1Activity中
跳转了两次,相当于打开了两个activity,从设置向导界面setup1Activity退出时是回到了手机防盗界面LostFindActivity
而这个手机防盗界面LostFindActivity还没有加载布局文件,所以会显示空白界面
解决:
当跳转到设置向导界面SetUp1Activity之后
用方法finish()把手机防盗界面LostFindActivity消除掉
即可在点击返回键时,直接跳转到主界面HomeActivity
Day02 16.设置向导第一个界面&状态选择器# *
设置向导第一个界面setup1Activity中对按钮增加状态选择器
1.创建布局文件Activity_setup1.xml
android系统中有些图片可以用,有些图片不能用
使用系统图片的好处:可以节省工程的体积,所以应该尽量使用系统图片
2.状态选择器:特殊的图片,根据不同状态显示不同图片,按下显示蓝色,抬起显示透明色
查看sdk-->docs 这里边都是api的镜像网站,也可以翻墙到谷歌官网去看,那里是最新的api,这里只是镜像网站,不过也足够使用,选择index.html用火狐打开,火狐有脱机工作可减少加载时间
bitmap:(.png,.jpg,.gif):一般用在显示网络图片,比如网络图片比较大,就用bitmap对它进行一些缩放,改变帧率分辨率
nine-patch:俗称.9图片,安卓中一种特有格式,作用:防止图片拉伸,要会制作
state List:状态选择器
用法:在res下 -> 新建drawable ->新建 button.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/function_greenbutton_pressed" /> <!-- pressed 按下时显示的图片-->
<item android:drawable="@drawable/function_greenbutton_normal" /> <!-- default 默认的图片-->
</selector>
3.找到布局文件的button按钮,将状态选择器button.xml设置成背景图片即可
android:background="@drawable/button"
Day02 17.总结 # *
TextView滚动效果
自定义组合控件
自定义控件的点击事件
屏蔽自动更新
自定义属性
设置密码对话框
输入密码对话框
MD5加密
界面跳转逻辑
设置向导第一个界面&状态选择器