Snackbar新版Toast 從源碼角度完全解析

我們將會從一下幾個角度瞭解Snackbar。

1.什麼是Snackbar

2.如何使用Snackbar

3.有哪些常用的API

4.從源碼角度分析其實現

5.總結

1.什麼是Snackbar

Snackbar是一個輕量級的用戶操作反饋工具,類似於Toast,但是比Toast更美觀和實用,當你操作它的時候,Snackbar會置頂顯示一個一定時間的簡要的信息在屏幕的底部,並且它還可以添加事件。

2.如何使用Snackbar

    Snackbar
          .make(parentLayout, R.string.snackbar_text,  
                                        Snackbar.LENGTH_LONG)
           .setAction(R.string.snackbar_action, myOnClickListener)
                  .show(); // Don’t forget to show!

這是官方給的一句話,是不是很簡單。當然 你需要使用 Android Support Library 22.2以上的版本。

3.有哪些常用的API

由於它很簡單,官方也只暴露出了幾個常用的方法。我們來看一下
dismiss() 不解釋
getDuration() 不解釋
getView() 返回Snackbar的佈局
make(View view, int resId, int duration) 這裏的view代 、
表 Snackbar的父view,因爲它要找一個依賴
make(View view, CharSequence text, int duration) 不解釋
setAction(int resId, View.OnClickListener listener) 設置動作
setAction(CharSequence text, View.OnClickListener listener)
setActionTextColor(ColorStateList colors) 設置文本的顏色
setActionTextColor(int color) 設置動作文本的顏色
setDuration(int duration) 設置時間段
setText(int resId)
setText(CharSequence message)
show()
就這幾個方法

4.從源碼角度分析其實現

首先我們看Snakebar類的相關方法


static {
        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                switch (message.what) {
                    case MSG_SHOW:
                        ((Snackbar) message.obj).showView();
                        return true;
                    case MSG_DISMISS:
                        ((Snackbar) message.obj).hideView();
                        return true;
                }
                return false;
            }
        });
    }
//首先設置了一個handler

Snackbar(ViewGroup parent) {
        mParent = parent;
        mContext = parent.getContext();

        LayoutInflater inflater = LayoutInflater.from(mContext);
        mView = (SnackbarLayout) inflater.inflate(R.layout.layout_snackbar, mParent, false);
    }
    //在構造方法中獲得了父控件,獲得了context,初始化自己的view,由於它的構造函數是protected的,大家懂的。

//我們來看看最主要的方法make,在看make方法之前我們來看看findSuitableParent這個make方法裏主要的功能。
@Nullable
    private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) { //判斷是不是CoordinatorLayout,如果是直接用,CoordinatorLayout也是22.2包下的新的layout
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) { //如果是FrameLayout,則進入else if
                if (view.getId() == android.R.id.content) { //如果id == android.R.id.content 那麼直接拿來用android.R.id.content代表根視圖,至於根視圖爲什麼是FrameLayout,我就不說了
        return (ViewGroup) view;
                } else {                            //是FrameLayout但不是根視圖的話,先添加進callback再說
                    fallback = (ViewGroup) view;
                }
            }
            if (view != null) {
                final ViewParent parent = view.getParent();         //獲得view的父控件,並循環執行,直到找到FrameLayout,或者找到CoordinatorLayout
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);
        return fallback;
    }
//排版有點亂,大家將就看,其實就是循環找到FrameLayout或者CoordinatorLayout才罷休

//好了,我們再看make方法。
      public static Snackbar make(View view, CharSequence text, @Duration int duration) {
        Snackbar snackbar = new Snackbar(findSuitableParent(view)); //初始化一個Snackbar,並傳入父控件
        snackbar.setText(text);  //做一些初始化操作
        snackbar.setDuration(duration);
        return snackbar; //返回過去
    }

//我們再來看看setAction方法,沒啥可說的。這裏採用的是鏈式編程。
    public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
        final TextView tv = mView.getActionView();

        if (TextUtils.isEmpty(text) || listener == null) {
            tv.setVisibility(View.GONE);
            tv.setOnClickListener(null);
        } else {
            tv.setVisibility(View.VISIBLE);
            tv.setText(text);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listener.onClick(view);
                    dismiss();
                }
            });
        }
        return this;
    }
    //Snackbar內部自定義了一個繼承自LinearLayout的SnackbarLayout用於顯示自己的view,代碼也不是很複雜,這想看的可以看看,畢竟自定義控件還是很重要的一項技能,這裏就不說了。

//再來看看show方法
     public void show() { //調用了SnackbarManager來顯示了,那我們就去SnackbarManager看看
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }

//首先看看SnackbarManager的類結構圖
![image](http://img.blog.csdn.net/20150608221707832)
//我們通過看SnackbarManager類結構可以看出,SnackbarManager主要的工作是顯示和隱藏Snackbar,我們主要看show和dismiss方法

 public void show(int duration, Callback callback) {
    synchronized (mLock) { //同步線程鎖,一次只能有一個對象進來。
         if (isCurrentSnackbar(callback)) {
         //判斷是不是當前的callback,如果是那麼說明這個cakllback已經在隊列裏了,那麼只需要
         mCurrentSnackbar.duration = duration;
         mHandler.removeCallbacksAndMesssage(mCurrentSnackbar); 
             //如果正在顯示,則調用handler重新顯示一遍removeCallbacksAndMessages的參數如果爲null則表示移除所有的Callbacks和Messages,在activityondestory執行能有效的避免內存泄露
         scheduleTimeoutLocked(mCurrentSnackbar);
         return;
       } else if (isNextSnackbar(callback)) { 
       //如果是下一個SnackbarRecord則直接設置時間,handler會自動調用, 這裏就用到了handler looper  mq的概念
         mNextSnackbar.duration = duration;
       } else {//如果都不是則重新創建一個SnackbarRecord,    SnackbarRecord  我們等會再說
        mNextSnackbar = new SnackbarRecord(duration, callback);
     }

    if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar)) { 
    //如果mCurrentSnackbar不爲空,試圖去取消它。
    return;
    } else {
                mCurrentSnackbar = null; //清空mCurrentSnackbar
                showNextSnackbarLocked(); //然後直接顯示,showNextSnackbarLocked該方法會先把mNextSnackbar賦值給mCurrentSnackbar,然後讓mCurrentSnackbar顯示
            }
  } 


    //總結一下這個show方法,首先會判斷mCurrentSnackbar是不是空,如果不是,並且它的callback和之前的不一致,則直接顯示(會先把之前的隱藏再顯示現在的); 如果是空或者說並且它的callback和之前的不一致,則判斷NextSnackbar是不是空,如果不是空,並且它的callback和之前一致,則直接設置至,等待handler的取值;如果mNextSnackbar是空,或者說並且它的callback和之前的不一致則直接先幹掉mCurrentSnackbar,然後給它重新賦值,邏輯還是有點繞的。   

//再來看看 dismiss
    public void dismiss(Callback callback) {   
     //很容易就看懂了。不解釋了
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                cancelSnackbarLocked(mCurrentSnackbar);
            }
            if (isNextSnackbar(callback)) {
                cancelSnackbarLocked(mNextSnackbar);
            }
        }
    }

    //而SnackbarRecord 類很簡單,內部維持了一個若引用。
     private static class SnackbarRecord {
        private final WeakReference<Callback> callback;
        private int duration;

        SnackbarRecord(int duration, Callback callback) {
            this.callback = new WeakReference<>(callback);
            this.duration = duration;
        }

        boolean isSnackbar(Callback callback) {
            return callback != null && this.callback.get() == callback;
        }
    }


5.總結

好了,Snackbar整體就差不多了,總結一下,所有Snackbar的初始化工作都是在Snackbar類裏執行的,而最後的show和dismiss都在snackbarManager裏執行的,
而snackbarManager裏維持了一個SnackbarRecord,SnackbarRecord內部擁有mCurrentSnackbar, mNextSnackbar兩個回調,用於維持Snackbar的顯示和隱藏

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