说说 setOnClickListener 的几种方式 与 向上向下转型

版权声明:本文章原创于 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 敲代码();
	}

接下来可能是有许多类型的程序员,比如手机应用就是 AndroidiOS,他们同样都是敲代码,但是敲的代码不一样,那么可以简化成这样。

	//安卓程序员
	public class 安卓程序员 implements 程序员{
		public void 敲代码(){
			//我敲的是 Java 与 Kotlin 代码。
		}
	}
	
	//苹果程序员
	public class 苹果程序员 implements 程序员{
		public void 敲代码(){
			//我敲的是 Object-C 与 Swift 代码。
		}
	}

所以当 MainActivity 作为 OnClickListener 时,就要实现 OnClickListener 中的方法,即 onClick() 方法。当然可以什么代码都不写。不管 MainActivity 内部逻辑有多复杂,button 触发 OnClickListener 时,就只需要考虑 MainActivityonClick() 怎么执行的。

此处的 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);

当然怎么处理都是开发者自己的事,如果你确定逻辑没有问题,自己能够有把握,那么不捕获异常应该也行。

技术有限,如果疏漏之处、错误、或疑问,欢迎留言指出。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章