由淺入深理解Android中的回調機制

什麼是接口回調?來先看看接口回調的定義吧:可以把使用某一接口的類創建的對象的引用賦給該接口聲明的接口變量,那麼該接口變量就可以調用被類實現的接口的方法。什麼意思?反正我是沒看懂,哈哈(#黑線),這麼官方的話還是不要看的好,不如看一個具體的例子。
話說有一天,奧巴馬下達命令,讓美國空軍派轟炸機前去轟炸恐怖分子基地(奧黑啥時候能變得這麼有正義感!),並要求空軍完成任務後通報轟炸詳情。下達完命令後呢,奧巴馬不會坐那乾等空軍電報吧,再說奧黑忙着呢,整天想着怎麼陰俄羅斯和中國呢…於是下達完命令奧巴馬就去幹其他事情了。兩個小時後空軍發來電報:恐怖分子基地已摧毀。
嗯,就醬紫!上邊的例子就是一個典型的接口回調!(奧巴馬命令空軍去空炸基地,空軍轟炸完後向奧巴馬報告即接口回調)接下來用java代碼來實現上邊的描述。
首先定義一個回調接口,該接口中有一個report()的回調方法,空軍完成任務後會通過調用此方法發送電報。

/**
 * Created by zhpan on 2016/7/27.
 */
public interface ReportCallback {	//回調接口
	public void report(String result); //回調方法
}

定義一個奧巴馬類,讓他實現ReportCallback接口,並重寫report()方法,注意,Obama類中持有空軍Plane的引用,在此類中可通過調用plane.boom(this)命令空軍去執行任務,boom()方法的參數爲this,相當於new Obama()作爲參數,即將自身傳了進去。

/**
 * Created by zhpan on 2016/7/27.
 */
public class Obama implements ReportCallback {

	private Plane plane=new Plane();
	
	public Obama() {
		super();
	}

	public Obama(Plane plane) {
		super();
		this.plane = plane;
	}
	//命令轟炸機轟炸恐怖分子基地的方法
	public void command(){
		plane.boom(this);	
	}

	public void report(String result) {
		System.out.println("任務詳情:"+result);
	}
}

下邊是空軍類,Obama類中調用此處的boom()方法(空軍接到任務)。此類中持有了一個ReportCallback的引用cb,cb其實是由Obama向上轉型而來,因爲在Obama類中調用boom()方法時參數爲this,用cb調用report()實際上是通過接口調用到了接口ReportCallback的子類Obama類中的report()方法。

/**
 * Created by zhpan on 2016/7/27.
 */
public class Plane {
	public void boom(ReportCallback cb){
		//空軍執行了五次轟炸任務
		for(int i=0;i<5;i++){
			//一個耗時操作
			try {
				System.out.println("執行轟炸任務");
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//轟炸結束後通過report()方法發送了電報給奧黑
		cb.report("基地已摧毀");
	}
}

測試類,Obama調用command方法。

public class Test {
	public static void main(String[] args) {
		
		Obama obama=new Obama();
		//	奧巴馬下達命令
		obama.command();
	}
}

測試結果如下:
這裏寫圖片描述
經過上邊的例子,我們應該對接口回調有了一個初步的認識。那麼在Android中都哪些地方用到了回調機制呢?其實,接口回調在Android中用的相當廣泛,Android中的一些事件處理方法,比如:onKeyDown、onKeyLongPress、onTouchEvent等還有View的監聽事件都是通過回調實現的。
下面我們以Button爲例來分析Android中的回調機制。當用戶點擊Button時,會觸發Button的onClick()方法,onClick()就是一個回調方法。結合View的源碼來看:

在View中定義了一個OnClickListener 的接口,接口中有一個onClick的抽象方法,可類比上邊例子中的ReportCallback 接口。

 /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

同時View中還持有一個OnClickListener 接口的引用mOnClickListener,mOnClickListener對應了上邊例子Plane 類中boom(ReportCallback cb)方法的參數cb,另外View中還定義了setOnClickListener()方法,該方法的參數是OnClickListener接口,方法體中將該參數賦值給了mOnClickListener。

       /**
         * Listener used to dispatch click events.
         * This field should be made private,
         *  so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;

   /**
     * Register a callback to be invoked when this view is 
     * clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

最後在performClick()方法中通過li.mOnClickListener.onClick(this);調用了onClick()方法,可類比Plane 類中cb調用report()方法。


    /**
     * Call this view's OnClickListener, if it is defined. 
     * Performs all normalactions associated with 
     * clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener 
     * that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

這是Android中回調機制的一個典型應用場景,通過上邊對Android源碼的分析相信大家對回調機制又有了更深一步的瞭解。在此基礎上接下來我們不妨自己動手寫一個回調。Come on!

我們以一個簡易的購物車添加商品爲例來實現一個自己的接口回調!先來看效果圖吧
這裏寫圖片描述
上圖中商品列表其實是一個ListView,由於只是演示接口回調的應用,所以只在ListView中填充了一條數據。而最下邊的結算一欄是在ListView的外部,那麼當商品數量改變時結算總價必然會跟隨變化,也就是下邊的結算會隨着點擊加號或者減號而變化。
做過item的子控件的點擊事件的小夥伴應該清楚,item中子控件的點擊事件是在Adapter中完成的,那麼在ListView中點擊子控件控件如何將得到的結果顯示到ListView外部呢?即商品數量改變了,如何將總價格更新到ListView外部的結算出?其實就可以用接口回調來完成。接下來看實現思路。
1.首先我們可以定義一個接口OnNumChangeListener,接口裏邊包含兩個抽象方法,分別負責增加和減少商品數量。代碼如下:

public interface OnNumChangeListener {
    //  增加商品數量的回調方法
    void onAddNumListener(int price,ViewHolder holder);
    //減少商品數量的回調方法
    void onSubNumListener(int price,ViewHolder holder);
}

2.在MyAdapter中添加setOnNumChangeListener方法,並在“+”、“-”按鈕點擊的時候分別調用OnNumChangeListener接口中的兩個方法。代碼如下:

/**
 * Created by zhpan on 2016/7/28.
 * item中的數據並沒有在getView()方法中添加,而是是在item佈局文件中添加的。
 */
public class MyAdapter extends BaseAdapter {
    Context mContext;
    OnNumChangeListener mOnNumChangeListener ;

	public void setOnNumChangeListener(OnNumChangeListener onNumChangeListener ){
		this.mOnNumChangeListener = onNumChangeListener 
	}

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView=View.inflate(mContext,R.layout.item,null);
        final ViewHolder holder=new ViewHolder();
        holder.add = (TextView) convertView.findViewById(R.id.tv_item_add);
        holder.sub = (TextView) convertView.findViewById(R.id.tv_item_sub);
        holder.num = (TextView) convertView.findViewById(R.id.tv_item_num);
        //  “+”的監聽事件
        holder.add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //  回調方法
                mOnNumChangeListener .onAddNumListener(10, holder);
            }
        });
        //  “-”的監聽事件
        holder.sub.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //  回調方法
                mOnNumChangeListener .onSubNumListener(10,holder);
            }
        });
        return convertView;
    }

	... //  省略MyAdapter中無關代碼

}

3.接下來在MainActivity中爲Adapter設置OnNumChangeListener接口 並重寫接口中的兩個方法。代碼如下:

adapter.setOnNumChangeListener(new MyAdapter.OnNumChangeListener(){
			/**
		     * 在此實現增加商品數量,並更新總價格
		     */
		    @Override
		    public void onAddNumListener(int price,ViewHolder holder) {
		        String numStr = holder.num.getText().toString();
		        int num=Integer.parseInt(numStr);
		        num++;
		        holder.num.setText(num+"");
		        int totalPrice=price*num;
		        mTextView.setText("結算:¥"+totalPrice);
		
		    }
		    /**
		     * 在此實現減少商品數量,並更新總價格
		     */
		    @Override
		    public void onSubNumListener(int price,ViewHolder holder) {
		        String numStr = holder.num.getText().toString();
		        int num=Integer.parseInt(numStr);
		        if(num>1){
		            num--;
		            holder.num.setText(num+"");
		            int totalPrice=price*num;
		            mTextView.setText("結算:¥"+totalPrice);
		        }else{
		            Toast.makeText(MainActivity.this, "不能再減少了", Toast.LENGTH_SHORT).show();
		        }
		    }

		});

這樣一個簡單的接口回調就完成了,相信經過這本篇文章的學習,大家一定能夠掌握接口回調的使用,如果沒有明白也沒關係,可以參考下面源碼。

源碼下載

好庫推薦

給大家推薦一下BannerViewPager。這是一個基於ViewPager實現的具有強大功能的無限輪播庫。通過BannerViewPager可以實現騰訊視頻、QQ音樂、酷狗音樂、支付寶、天貓、淘寶、優酷視頻、喜馬拉雅、網易雲音樂、嗶哩嗶哩等APP的Banner樣式以及指示器樣式。

歡迎大家到github關注BannerViewPager

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章