版权声明:本文章原创于 RamboPan ,未经允许,请勿转载。
- 第一次:2019年01月06日
- 第二次:2019年03月08日
- 更换举例
- 变量、类名、方法 标记统一
- 代码块调整
说说 setOnClickListener
因为不是计算机专业,当时入门安卓的时候,看的 Mars 的视频学起来的,挺多知识很迷糊。
后面也是开始做了些项目才慢慢熟悉起来。
最近花了一些时间,把做的第一个 app 重构了下,以前的代码不能直视。
当然菜不要紧,要是菜还不勤快,那肯定就没法了。
说到初见安卓,肯定会用到一些交互控件,最常用的方式之一就是 Button 触发 OnClickListener。
当时因为 Java 懂的不太多,看到过几种写法,还有点被绕晕了。
现在看肯定是没啥问题,当时看感觉真是有点迷糊。
代码【1】:
//方式一
public MainActivity extends Activity implements OnClickListener{
……
@Override
protected void onCreate(Bundle savedInstanceState) {
……
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
//此时 MainActivity 作为 OnClickListener 作用
}
}
先来分析下这个,button.setOnClickListener() 需要传入的是 OnClickListener 类型的实例。
此时是把 MainActivity 作为 OnClickListener 类型传入,但是 MainActivity 是继承 Activity 的,不是 OnClickListener 类型,那么此时可以让 MainActivity 实现了 OnClickListener 接口,将 onClick 方法重写,那么此时就可以作为 OnClickListener 类型。
接口类型 与 接口的实现 可以这么理解,你定义了一个类型,那么这个类型的事物有些什么共同的特性,我们常把共同的特性抽离出来。而每个特性表现出来又可能不一样。
比如程序员这个职业,有个技能算敲代码,那么这个 接口类型 就是程序员,接口中实现 就算敲代码。你如果作为程序员,你就要满足会敲代码这个技能。当然反过来可以不成立。
//大致就这种意思
public interface 程序员{
public void 敲代码();
}
接下来可能是有许多类型的程序员,比如手机应用就是 Android 与 iOS,他们同样都是敲代码,但是敲的代码不一样,那么可以简化成这样。
//安卓程序员
public class 安卓程序员 implements 程序员{
public void 敲代码(){
//我敲的是 Java 与 Kotlin 代码。
}
}
//苹果程序员
public class 苹果程序员 implements 程序员{
public void 敲代码(){
//我敲的是 Object-C 与 Swift 代码。
}
}
所以当 MainActivity 作为 OnClickListener 时,就要实现 OnClickListener 中的方法,即 onClick() 方法。当然可以什么代码都不写。不管 MainActivity 内部逻辑有多复杂,button 触发 OnClickListener 时,就只需要考虑 MainActivity 中 onClick() 怎么执行的。
此处的 this 代表的是 MainActivity 的实例,如果调用方法外还有一个其他的类,那么就必须要使用 MainActivity.this 来指定是 MainActivity 这个实例,比如我们在一个新的线程当中使用。
代码【2】
public class MainActivity extends Activity implements OnClickListener{
……
@Override
protected void onCreate(Bundle savedInstanceState) {
……
final Button button = new Button(this);
new Thread(new Runnable() {
@Override
public void run() {
button.setOnClickListener(MainActivity.this);
// 思考题
//button.setOnClickListener(this);
}
}).start();
}
}
不过话说,如果在这个例子中是直接使用 this 的话,那么究竟是指的是哪个的实例呢 ?
当然是 new Thread。
代码【3】
//方式二
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
……
button.setOnClickListener(new MyOnClickListener());
}
class MyOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
}
}
}
再来说说这个,此处需要一个 OnClickListener 实例,我们去定义了一个新的类,然后实现了 OnClickListener 接口,然后直接 new 一个该类可以了。
此处区别与上一个写法,就是此处新建了一个 MyOnClickListener 类,实现 OnClickListener 接口,而默认类父类为 Object ,所以该类基本作用就是实现 onClick() 方法。
而 MainActivity 不光作为 OnClickListener 类型,也作为 Activity 类型,有许多复杂的逻辑 和 可以调用的方法。
代码【4】
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
……
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
这种是匿名类,就是你没法说出类的名字,你只知道它实现了 onClick() 方法。
那么可能有人要说了,这种明明感觉代码要少点嘛,为啥不用这种。?
那现在我再说,如果有个 button2 也需要设置一样的事件,你要怎么写。
代码【5】:
//按照第三种写法,匿名用两个 button 触发
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
……
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
//按照第二种写法,两个 button 用相同的触发
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
……
MyOnClickListener listener = new MyOnClickListener();
button.setOnClickListener(listener);
button2.setOnClickListener(listener);
}
class MyOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
}
}
}
哈哈,现在怎么样,是不是感觉第二种更简单了,当然前提是两个触发都用相同的逻辑。
其实不光是这种省代码,还有一个很重要的方面就是 注册 与 反注册。我们如果用电脑多,会经常听到 注册 与 反注册。注册的作用在于,可以和系统进行一定的关联,得到系统的通知。
比如我们现在需要打开一个 .zip 后缀的文件,但是没有对应的应用软件。那我们就去网上随便下载一个压缩应用软件,然后一般安装完成后,就可以打开了。(默认安装好后会自动修改默认启动应用)
这是因为你安装完了,你注册信息到系统,告诉系统哪些类型文件我可以打开了,以前打不开是因为系统也没有找到对应的软件,所以没法打开,现在我可以来处理这些类型文件,那下次碰到你就可以交给我来处理了。
需要反注册的一个目的,也同样类似,取消与系统的关联,我如果删了这个软件,就没法用这个软件了,你要告诉系统,下次再打开某某类型软件,就不用找我了,如果你非要找我,肯定会出现异常。当然系统没崩溃肯定是因为已经处理了找不到的情况。
如果有名字,你肯定可以反注册,因为你能找得到;如果是匿名,没名字,你说说怎么反注册,因为你没法按名字找。
当然此处的名字指的是针对对象的引用。
代码【6】:
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
……
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
button.setOnClickListener(null);
}
}
其实这种情况下, 可以通过 button.setOnClickListener(null) 来解决。不过如果反注册时,还需要在接口中处理一些其他的操作,那么这样简单的操作肯定不会生效。
比如广播,服务这种还是需要非匿名的,不然会出现错误。当然此处不讨论。所以一些复杂的情况,或者系统需要的情况下,都最好使用显示的接口。
随着对 Java 逐渐熟悉,知道这是类的转型。
转型主要分为 向上转型 和 向下转型 。
向上转型:
举个简单地例子:一个学生是人,因为人是大的范围,而学生算是人当中的一个小范围,如果按职业来分的话,就还有 老师、科学家、商人等。
比如哪天我们需要扫大街,需要一定的人来完成这个任务。
代码【7】:
//我们定义一个方法,需要传入一个 Man 类型。
public void cleanStreet(Man man){
……
}
//新建一个类为 Man
public class Man{
……
}
//新建一个类为 Student , Student 继承 Man
//那么 Man 类型就是父类(超类) Student 类型就是子类
public class Student extends Man{
……
}
//派出学生
cleanStreet(new Student());
//派出普通人
cleanStreet(new Man());
//
扫地这个一般人都会吧,如果人够的时候呢,就找普通的人就行了,如果人不够的时候,比如学生搞社会实践的时候,那么他就可以作为人这个类型去发挥作用。从逻辑上说,学生是人也是没有问题的。所以:
-
向上转型是安全的。
-
在传入参数的时候,默认不需要加任何符号,计算机都可以识别向上转型。
-
向上转型就是把一个小的类型转化一个大的类型。
(更具体的类型转化为更通用的类型)子类 —> 父类。
向下转型:
按照刚才的例子来说,把一个人变为学生,这个听起来不太 “安全” 。如果每个普通人都拿去变成学生,那么国家这教育水平,人均文化水平就提高了一个大的层次了,把美国老大的位置抢下来就指日可待了。
那怎样才能保证一个人能比较安全的转型为学生呢,那么有一种可能的情况: 就是他之前就是学生这个类型,被转型为普通人,然后你再把他转型为学生,那么肯定没什么问题。
还拿刚才那个例子,你扫地之前在写作业,就贼难的数学题那种,一般人解不出来,社会实践的时候,你去扫地了,然后地扫完了。数学题谁来解是吧?嗯,又把你转型为学生回去大战数学题 … (求心理阴影)
代码【8】:
//新定义一个方法,需要传入的类型为学生。
public void fightMaths(Student student){
……
}
//新建一个学生对象,先把他转型为普通人。
Man man = new Student();
//那么向下转型的时候,是需要加一些标记的。
//把 man 对象,加上 (需要转换的类型) ,此处为 (Student)
//就可以把 Man 类型强制转换为 Student 类型进行传入。
fightMaths((Student)man);
(Student) 就表示使用强制转型,() 内需要转型的类名。OK,看着好像就没什么问题了,
这样的话,其实有一种情况,就是如果你并不确定是不是一定能转换成功怎么办 ?或者故意是一个错误的类型怎么办?
//我们新建一个 Worker 类,也继承 Man 。
public class Worker extends Man{
……
}
//先把 Worker 转为 Man 类型。
Man man2 = new Worker();
//再去把 man2 强制转换为 Student
//因为先转换为 Man 类型,此时编译器已经没法区别他之前是不是 Student 类型。
fightMaths((Student)man2);
如果此时运行一下,肯定会弹出异常,类型转换错误。
Caused by: java.lang.ClassCastException:
xxx.MainActivity$Worker cannot be cast to xxx.MainActivity$Student`
这样的话,可以使用 try catch 包裹住,捕获可能转换存在的异常。
Man man2 = new Worker();
try{
doHomework((Student)man2);
}catch (ClassCastException e){
e.printStackTrace();
}
当然这种向下转型一般情况下是在自己的代码中,向上转型过的,然后自己又再转型回来。因为如果是别人的话,可能你不确定,当然如果确定了也行。举个 Handler 发送消息的例子。
代码【9】:
// String 虽然是常用字符串,但仍然是一个类,同属于 Object 的子类(当然中间还有几级关系)。
String text = "abcd";
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//使用 (String) 强制转型转换为 String 类型
String result = (String)msg.obj;
}
};
Message msg = Message.obtain();
//msg.obj 为 Object 类型,直接转型不需要加其他标志
msg.obj = text;
handler.sendMessage(msg);
当然怎么处理都是开发者自己的事,如果你确定逻辑没有问题,自己能够有把握,那么不捕获异常应该也行。