說說 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);

當然怎麼處理都是開發者自己的事,如果你確定邏輯沒有問題,自己能夠有把握,那麼不捕獲異常應該也行。

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