1. 程式人生 > >由淺入深理解Android中的回撥機制

由淺入深理解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();
                }
            }

        });

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

原始碼下載