版權聲明:本文章原創於 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);
當然怎麼處理都是開發者自己的事,如果你確定邏輯沒有問題,自己能夠有把握,那麼不捕獲異常應該也行。