class=“android.support.design.widget.Snackbar$SnackbarLayout”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_gravity=“bottom”
/>
注意到class了吗,没错,此View是Snackbar中定义的SnackbarLayout,继承自baseTransientBottomBar中的SnackbarbaseLayout,而SnackbarbaseLayout继承自frameLayout。SnackbarLaout中只重新了onMeasure方法,其他实现都在SnackbarbaseLayout中。
还有一个关键的地方,layout_gravity被设置成了bottom,这也是为什么Snackbar总显示在底部的原因。
到这里我们已经知道了**Snackbar的布局实际上是一个frameLayout,其内容是一个LinearLayout。baseTransientBottomBar提供了getView方法来获取mView,mView即为Snackbar的根布局frameLayout。**既然能获取到根布局,那往此布局中addView肯定是没问题了,之前提到的问题3也可以利用这一点解决了。
show方法
为什么不是action,而是show。因为我关心的是Snackbar如何显示的。
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
看到这里是不是有点蒙蔽了,怎么到了SnackbarManager的show方法了,不着急,我们先来看看mManagerCallback:
final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
@Override
public void show() {
sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, baseTransientBottomBar.this));
}
@Override
public void dismiss(int event) {
sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,
baseTransientBottomBar.this));
}
};
mManagerCallback内部使用了Handler来控制show和dismiss,最终sHandler会调用showView:
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
// 如果LayoutParams是CoordinatorLayout的,就设置Behavior
final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
final Behavior behavior = new Behavior();
behavior.setStartAlphaSwipeDistance(0.1f);
behavior.setEndAlphaSwipeDistance(0.6f);
//设置SwipeDismissBehavior,具体作用就是滑动删除view behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.onDismissListener() {
@Override
public void onDismiss(View view) {
view.setVisibility(View.GONE);
dispatchDismiss(baseCallback.DISMISS_EVENT_SWIPE);
}
@Override
public void onDragStateChanged(int state) {
switch (state) {
case SwipeDismissBehavior.STATE_DRAGGING:
case SwipeDismissBehavior.STATE_SETTLING:
// If the view is being dragged or settling, pause the timeout
SnackbarManager.getInstance().pauseTimeout(mManagerCallback);
break;
case SwipeDismissBehavior.STATE_IDLE:
// If the view has been released and is idle, restore the timeout
SnackbarManager.getInstance()
.restoreTimeoutIfPaused(mManagerCallback);
break;
}
}
});
clp.setBehavior(behavior);
// Also set the inset edge so that views can dodge the bar correctly
clp.insetEdge = Gravity.BOTTOM;
}
//重点来了,mView被添加到了mTargetParent中,之前向上遍历view获取的ViewGroup
mTargetParent.addView(mView);
}
mView.setonAttachStateChangeListener(
new baseTransientBottomBar.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.
sHandler.post(new Runnable() {
@Override
public void run() {
onViewHidden(baseCallback.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 baseTransientBottomBar.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();
}
}
});
}
}
通过showView中的代码,终于了解到Snackbar是如何显示的。Snackbar被直接添加到mTargetParent中,就是make方法传递进来的View的父CoordinatorLayout或根布局。
根据Snackbar的布局文件我们知道其layout_gravity为bottom,也就是会显示在mTargetParent的底部。那我们是不是只要将一个有固定高度的CoordinatorLayout传递个给Snackbar的make,就可以改变Snackbar的显示位置了?答案是肯定的!
到这里我们的问题1和问题2都明了了。那问题4应该如何解决呢?
SnackbarManager
根据上面的分析,我们知道show方法会调用SnackbarManager的show方法,那我们就来看看SnackBarManager的源码:
class SnackbarManager {
static final int MSG_TIMEOUT = 0;
private static final int SHORT_DURATION_MS = 1500;
private static final int LONG_DURATION_MS = 2750;
private static SnackbarManager sSnackbarManager;
//单例模式
static SnackbarManager getInstance() {
if (sSnackbarManager == null) {
sSnackbarManager = new SnackbarManager();
}
return sSnackbarManager;
}
private final Object mLock;
private final Handler mHandler;
//用来存储当前显示Snackbar的duration和Callback
private SnackbarRecord mCurrentSnackbar;
//用来存储接下来要显示的Snackbar的duration和Callback
private SnackbarRecord mNextSnackbar;
private SnackbarManager() {
mLock = new Object();
mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case MSG_TIMEOUT:
handleTimeout((SnackbarRecord) message.obj);
return true;
}
return false;
}
});
}
interface Callback {
void show();
void dismiss(int event);
}
public void show(int duration, Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) { //判断是否是当前显示的Snackbar,更新duration
// 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);//移除Callback,避免内存泄露
scheduleTimeoutLocked(mCurrentSnackbar);//重新关联设置duration和Callback
return;
} else if (isNextSnackbarLocked(callback)) { //判断是否是接下来要显示的Snackbar,更新duration
// We’ll just update the duration
mNextSnackbar.duration = duration;
} else {
// Else, we need to create a new record and queue it
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
return;
} else {
// Clear out the current snackbar
mCurrentSnackbar = null;
// Otherwise, just show it now
showNextSnackbarLocked();
}
}
}
public void dismiss(Callback callback, int event) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) {
cancelSnackbarLocked(mCurrentSnackbar, event);
} else if (isNextSnackbarLocked(callback)) {
cancelSnackbarLocked(mNextSnackbar, 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) {
showNextSnackbarLocked();
}
}
}
}
public void onShown(Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) {
scheduleTimeoutLocked(mCurrentSnackbar);
}
}
}
public void pauseTimeout(Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback) && !mCurrentSnackbar.paused) {
mCurrentSnackbar.paused = true;
mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
}
}
}
public void restoreTimeoutIfPaused(Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback) && mCurrentSnackbar.paused) {
mCurrentSnackbar.paused = false;
scheduleTimeoutLocked(mCurrentSnackbar);
}
}
}
public boolean isCurrent(Callback callback) {
synchronized (mLock) {
return isCurrentSnackbarLocked(callback);
}
}
public boolean isCurrentOrNext(Callback callback) {
synchronized (mLock) {
return isCurrentSnackbarLocked(callback) || isNextSnackbarLocked(callback);
}
}
private static class SnackbarRecord {
final WeakReference callback;
int duration;
boolean paused;
SnackbarRecord(int duration, Callback callback) {
this.callback = new WeakReference<>(callback);
this.duration = duration;
}
boolean isSnackbar(Callback callback) {
return callback != null && this.callback.get() == callback;
}
}
private void showNextSnackbarLocked() {
if (mNextSnackbar != null) {
mCurrentSnackbar = mNextSnackbar;
mNextSnackbar = null
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
;
final Callback callback = mCurrentSnackbar.callback.get();
if (callback != null) {
callback.show();
} else {
// The callback doesn’t exist any more, clear out the Snackbar
mCurrentSnackbar = null;