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();
}
}
}
}