SnackBar 源码解析

SnackBar 是 android 中一种 用来做消息提示的控件,它不继承自View, 而是内部 管理 SnackbarLayout 隐藏与显示; SnackbarLayout 是SnackBar的内部类, 继承于LinearLayout;

你可以在 android.support:design 包下找到SnackBar;
使用方式就不多说了,进入正题;

源码解析:

有三个核心类:SnackBar 、SnackbarManager、SnackbarRecord ;
那它们是如何工作的呢?

SnackBar 管理SnackbarLayout 隐藏与显示;
SnackbarManager 通知 SnackBar 中SnackbarLayout 隐藏与显示;
SnackbarRecord 中保存了Snackbar 的Callback 和SnackBar的显示时长,SnackbarManager 定义了两个SnackbarRecord :mCurrentSnackbar 和 mNextSnackbar;

1.先从第一次调用 snackbar 的show 说起:

  /**
     *SnackBar 中的show
     * Show the {@link Snackbar}.
     */
    public void show() {
  //调用 SnackbarManager 中的show
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }
//SnackbarManager 中的show (请理解注释)
  public void show(int duration, Callback callback) {
        synchronized (mLock) {

            if (isCurrentSnackbarLocked(callback)) {
                //①
                // Means that the callback is already in the queue. We'll just update the duration(当前的回调早已在队列中。我们只需要更新时长)

                mCurrentSnackbar.duration = duration;

                // If this is the Snackbar currently being shown, call re-schedule it's
                // timeout
                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
                scheduleTimeoutLocked(mCurrentSnackbar);
                return;
            } else if (isNextSnackbarLocked(callback)) {
                //②
                // We'll just update the duration(同样的,下一个想要显示snackbar的回调早已在队列中。我们只需要更新时长)
                mNextSnackbar.duration = duration;
            } else {
                //③
                // Else, we need to create a new record and queue it(代表这是下一个需要show的 snackbar )
                mNextSnackbar = new SnackbarRecord(duration, callback);
            }


            if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,
                    Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
                //④
                // If we currently have a Snackbar, try and cancel it and wait in line(如果当前有显示的Snackbar,尝试去取消显示)
                return;
            } else {
                //⑤
                // Clear out the current snackbar
                mCurrentSnackbar = null;
                // Otherwise, just show it now(如果当前没有要显示的就显示下一个Snackbar)
                showNextSnackbarLocked();
            }
        }
    }



调用snackbar 的show 后,会继续调用 snackbarManager的 show ,然后在 snackbarManager的 show 中按顺序执行③⑤代码块;

⑤中执行的showNextSnackbarLocked函数如下:

// SnackbarManager 中的函数
    private void showNextSnackbarLocked() {
        if (mNextSnackbar != null) {
        //把下一个想要显示的赋值给mCurrentSnackbar ;
            mCurrentSnackbar = mNextSnackbar;
            mNextSnackbar = null;

            final Callback callback = mCurrentSnackbar.callback.get();
            if (callback != null) {
            //Callback 中有show 和 dismiss
                callback.show();//实际调用的是如下的mManagerCallback 的show
            } else {
                // The callback doesn't exist any more, clear out the Snackbar
                mCurrentSnackbar = null;
            }
        }
    }

//showNextSnackbarLocked() 中的变量final Callback callback 是 mManagerCallback,它是定义在snackbar 中的成员变量, 早在 SnackbarManager 中的show (int duration, Callback callback) 传入;
   private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
        @Override
        public void show() {
        //发送一条消息让这个Snackbar 显示
            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
        }

        @Override
        public void dismiss(int event) {
       //发送一条消息让这个Snackbar取消显示
            sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));
        }
    };

调用mManagerCallback 的show后 会发一条消息,然后由主线程中的Looper 循环处理消息:

//Snackbar 中的代码块
 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();//至此第一个snackbar 就完全显示出来啦!
                        return true;
                    case MSG_DISMISS:
                        ((Snackbar) message.obj).hideView(message.arg1);
                        return true;
                }
                return false;
            }
        });
    }
// Snackbar 中的函数,最终显示一个Snackbar ,显示有两种方式:1.直接显示,2.通过播放动画显示;具体是通过shouldAnimate()控制的,内部只有一句代码:“mAccessibilityManager.isEnabled()” , 
AccessibilityManager 是一种系统服务(无障碍服务,http://www.xuebuyuan.com/2061597.html),大家在逢年过节可能用过微信红包助手之类的软件,就是利用这个服务实现的,有兴趣的大家可以阅读这篇文章:http://blog.csdn.net/h183288132/article/details/50973112
  final void showView() {
      //......具体是如何添加到窗口上的 ,由于篇幅原因我就不叙述了(so easy)

       if (ViewCompat.isLaidOut(mView)) {
       //如果SnackbarLayout 已经摆放好了执行显示动画;
            animateViewIn();
        } else {

            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
                    animateViewIn();
                    mView.setOnLayoutChangeListener(null);
                }
            });
        }

        mView.setOnAttachStateChangeListener(new SnackbarLayout.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                if (isShownOrQueued()) {
                    // If we haven't already been dismissed then this event is coming from a
                    // non-user initiated action. Hence we need to make sure that we callback
                    // and keep our state up to date. We need to post the call since removeView()
                    // will call through to onDetachedFromWindow and thus overflow.
                    //如果view在规定时间内与window 解绑(这时还在排队中), 就执行这个以保证 更新状态,取消显示此Snackbar;
                    sHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            onViewHidden(Callback.DISMISS_EVENT_MANUAL);
                        }
                    });
                }
            }
        });

        if (ViewCompat.isLaidOut(mView)) {
            if (shouldAnimate()) {
                // If animations are enabled, animate it in
                animateViewIn();
            } else {
                // Else if anims are disabled just call back now
                onViewShown();
            }
        } else {
            // Otherwise, add one of our layout change listeners and show it in when laid out
            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
                    mView.setOnLayoutChangeListener(null);

                    if (shouldAnimate()) {
                        // If animations are enabled, animate it in
                        animateViewIn();
                    } else {
                        // Else if anims are disabled just call back now
                        onViewShown();
                    }
                }
            });
        }
    }

那显示出来之后是如何在 之前规定 的一段时间后取消显示呢? 相信大家都已经猜到了, 哈哈就是如下代码:
在animateViewIn()中设置了动画监听器:

anim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationEnd(Animation animation) {
                onViewShown();//在显示动画执行完后调用onViewShown;
            }

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
   //通知SnackbarManager Snackbar 已经显示了。
    private void onViewShown() {
        SnackbarManager.getInstance().onShown(mManagerCallback);
        if (mCallback != null) {
            mCallback.onShown(this);
        }
    }
  // SnackbarManager 中的函数
    public void onShown(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbarLocked(callback)) {
            //这里发送消息 在规定显示时长后取消显示Snackbar;
                scheduleTimeoutLocked(mCurrentSnackbar);
            }
        }
    }
// SnackbarManager 中的函数 用来在规定显示时长后取消Snackbar
    private void scheduleTimeoutLocked(SnackbarRecord r) {
        if (r.duration == Snackbar.LENGTH_INDEFINITE) {
            // If we're set to indefinite, we don't want to set a timeout
            return;
        }

        int durationMs = LONG_DURATION_MS;
        if (r.duration > 0) {
            durationMs = r.duration;
        } else if (r.duration == Snackbar.LENGTH_SHORT) {
            durationMs = SHORT_DURATION_MS;
        }
        mHandler.removeCallbacksAndMessages(r);
        //发送出去的消息被处理后 第一次Snackbar显示与消失就完成啦!
        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
    }

2.如果先后连续显示两个Snackbar(记为A,B)又是如何控制的呢
第一次show 如同以上分析,第二次显示就不同了,第二次会依次调用SnackbarManager 中的show ③④,看到的现象是A在规定时长内取消显示接着显示B;
原因是:


//SnackbarManager的show() 中的④:
 private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
        Callback callback = record.callback.get();
        if (callback != null) {
            // Make sure we remove any timeouts for the SnackbarRecord
            mHandler.removeCallbacksAndMessages(record);
            callback.dismiss(event);//至此A就隐藏了。
            return true;
        }
        return false;
    }

在调用callback.dismiss(event) 后会调用: 
public void onDismissed(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbarLocked(callback)) {
                // If the callback is from a Snackbar currently show, remove it and show a new one
                mCurrentSnackbar = null;
                if (mNextSnackbar != null) {
                //显示下一个Snackbar:至此B就显示出来了
                    showNextSnackbarLocked();
                }
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_17827919/article/details/64513498
今日推荐