Android开发——Snackbar使用详解

article/2025/9/18 18:52:04

Snackbar是Android支持库中用于显示简单消息并且提供和用户的一个简单操作的一种弹出式提醒。当使用Snackbar时,提示会出现在消息最底部,通常含有一段信息和一个可点击的按钮。下图是Gmail中删除一封邮件时弹出的Snackbar:
Gmail中删除邮件时弹出的Snackbar
在上图中,最下方的黑色区域,包含左边文字和右边"撤销"字样的就是Snackbar。Snackbar在显示一段时间后就会自动消失。同样作为消息提示,Snackbar相比于Toast而言,增加了一个用户操作,并且在同时弹出多个消息时,Snackbar会停止前一个,直接显示后一个,也就是说同一时刻只会有一个Snackbar在显示;而Toast则不然,如果不做特殊处理,那么同时可以有多个Toast出现;Snackbar相比于Dialog,操作更少,因为只有一个用户操作的接口,而Dialog最多可以设置三个,另外Snackbar的出现并不影响用户的继续操作,而Dialog则必须需要用户做出响应,所以相比Dialog,Snackbar更轻量。
经过上面的比较,可以看出Snackbar可以用于显示用户信息并且该信息不需要用户立即做出反馈的时候。

一、如何使用Snackbar?

Snackbar没有公有的构造方法,但是提供了静态方法make方法:

static Snackbar	make(View view, CharSequence text, int duration)static Snackbar	make(View view, int resId, int duration)

其中view参数是用于查找合适父布局的一个起点,下面分析源码的时候会解释到。如果父布局是一个CoordinatorLayout,那么Snackbar还会有别的一些特性:可以滑动消除;并且如果有FloatingActionButton时,会将FloatingActionButton上移,而不会挡住Snackbar的显示。

1.1、父布局不是CoordinatorLayout

在创建了一个Snackbar对象后,可以调用一些set**方法进行设置,其中setAction()方法用于设置右侧的文字显示以及点击事件,setCallback()方法用于设置一个状态回调,在Snackbar显示和消失的时候会触发方法。下面是一段创建Snackbar的代码:

 Snackbar.make(view, "已删除一个会话", Snackbar.LENGTH_SHORT).setAction("撤销", new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(Main2Activity.this, "撤销了删除", Toast.LENGTH_SHORT).show();}}).show();

以上代码在一个按钮的点击事件中创建一个Snackbar并显示,内容模仿上面的Gmail例子,并且给“撤销”一个点击事件,只是简单的显示一个Toast。Activity的根布局是一个RelativeLayout,并且下部有一个FloatingActionButton,在Snackbar出现后,可以看到Snackbar遮挡了FlaotingActionButton的一部分,具体效果如下:
父布局RelativeLayout,遮挡FloatingActionButton

1.2、父布局是CoordinatorLayout

在父布局不是CoordinatorLayout的情况下,如果有FloaingActionButton,那么弹出的Snackbar会遮挡FloatingActionButton,为了解决这个问题,可以将父布局改成CoordinatorLayout,并且这会带来一个新特性,就是Snackbar可以通过右滑消失。代码一样,只是布局不同。直接看效果图:
父布局CoordinatorLayout,不遮挡FloatingActionButton
可以看到当Snackbar出现时,FloatingActionButton会上移并且支持右滑消失。

1.3、Snackbar消失的几种方式

Snackbar显示只有一种方式,那就是调用show()方法,但是消失有几种方式:时间到了自动消失、点击了右侧按钮消失、新的Snackbar出现导致旧的Snackbar消失、滑动消失或者通过调用dismiss()消失。这些方式分别对应于Snackbar.Callback中的几个常量值。

  • DISMISS_EVENT_ACTION:点击了右侧按钮导致消失
  • DISMISS_EVENT_CONSECUTIVE:新的Snackbar出现导致旧的消失
  • DISMISS_EVENT_MANUAL:调用了dismiss方法导致消失
  • DISMISS_EVENT_SWIPE:滑动导致消失
  • DISMISS_EVENT_TIMEOUT:设置的显示时间到了导致消失
    Callback有两个方法:
void	onDismissed(Snackbar snackbar, int event)void	onShown(Snackbar snackbar)

其中onShown在Snackbar可见时调用,onDismissed在Snackbar准备消失时调用。一般我们可以在onDismissed方法中正在处理我们所需要的操作,比如删除一封邮件,那么如果是点击了“撤销”按钮,那就应该不再删除邮件直接消失就可以了,但是对于其他的几种情况,就需要真正地删除邮件了(发送数据到后台等等…)。下面是模拟这样一段过程:

   Snackbar.make(view, "已删除一个会话", Snackbar.LENGTH_SHORT).setAction("撤销", new View.OnClickListener() {@Overridepublic void onClick(View v) {}}).setCallback(new Snackbar.Callback() {@Overridepublic void onDismissed(Snackbar snackbar, int event) {switch (event) {case Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE:case Snackbar.Callback.DISMISS_EVENT_MANUAL:case Snackbar.Callback.DISMISS_EVENT_SWIPE:case Snackbar.Callback.DISMISS_EVENT_TIMEOUT://TODO 网络操作Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_SHORT).show();break;case Snackbar.Callback.DISMISS_EVENT_ACTION:Toast.makeText(MainActivity.this, "撤销了删除操作", Toast.LENGTH_SHORT).show();break;}}@Overridepublic void onShown(Snackbar snackbar) {super.onShown(snackbar);Log.i(TAG, "onShown");}}).show();

上述代码在onDismissed中根据消失类型进行不同的处理。效果如下:
处理Snackbar的消失事件

二、Snackbar源码分析

2.1、Snackbar的创建分析

从前面的段落知道,创建Snackbar需要使用静态的make方法,并且其中的view参数是一个查找父布局的起点。下面是make方法的实现:

public static Snackbar make(@NonNull View view, @NonNull CharSequence text,@Duration int duration) {Snackbar snackbar = new Snackbar(findSuitableParent(view));snackbar.setText(text);snackbar.setDuration(duration);return snackbar;}

其中findSuitableParent()方法为以view为起点寻找合适的父布局,下面是findSuitableParent方法的实现:

private static ViewGroup findSuitableParent(View view) {ViewGroup fallback = null;do {if (view instanceof CoordinatorLayout) {// We've found a CoordinatorLayout, use itreturn (ViewGroup) view;} else if (view instanceof FrameLayout) {if (view.getId() == android.R.id.content) {// If we've hit the decor content view, then we didn't find a CoL in the// hierarchy, so use it.return (ViewGroup) view;} else {// It's not the content view but we'll use it as our fallbackfallback = (ViewGroup) view;}}if (view != null) {// Else, we will loop and crawl up the view hierarchy and try to find a parentfinal ViewParent parent = view.getParent();view = parent instanceof View ? (View) parent : null;}} while (view != null);// If we reach here then we didn't find a CoL or a suitable content view so we'll fallbackreturn fallback;}

可以看到如果view是CoordinatorLayout,那么就直接作为父布局了;如果是FrameLayout,并且如果是android.R.id.content,也就是查找到了DecorView,即最顶部,那么就只用这个view;如果不是的话,先保存下来;接下来就是获取view的父布局,然后循环再次判断。这样导致的结果最终会有两个选择,要么是CoordinatorLayout,要么就是FrameLayout,并且是最顶层的那个布局。具体情况是这样的:

  • 如果从View往上搜寻,如果有CoordinatorLayout,那么就使用该CoordinatorLayout
  • 如果从View往上搜寻,没有CoordinatorLayout,那么就使用android.R.id.content的FrameLayout
    接下来再看Snackbar的构造方法:
 private Snackbar(ViewGroup parent) {mTargetParent = parent;mContext = parent.getContext();ThemeUtils.checkAppCompatTheme(mContext);LayoutInflater inflater = LayoutInflater.from(mContext);mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mTargetParent, false);mAccessibilityManager = (AccessibilityManager)mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);}

其中SnackbarLayout就是Snackbar的样式,SnackbarLayout继承自LinearLayout并且有一个TextView和一个Button,其中TextView就是左边用于显示文字,Button就是右边用于设置点击事件的。SnackbarLayout的部分代码如下:

public static class SnackbarLayout extends LinearLayout {private TextView mMessageView;private Button mActionView;...public SnackbarLayout(Context context, AttributeSet attrs) {super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1);mMaxInlineActionWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_maxActionInlineWidth, -1);if (a.hasValue(R.styleable.SnackbarLayout_elevation)) {ViewCompat.setElevation(this, a.getDimensionPixelSize(R.styleable.SnackbarLayout_elevation, 0));}a.recycle();setClickable(true);// Now inflate our content. We need to do this manually rather than using an <include>// in the layout since older versions of the Android do not inflate includes with// the correct Context.LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);ViewCompat.setAccessibilityLiveRegion(this,ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);ViewCompat.setImportantForAccessibility(this,ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);// Make sure that we fit system windows and have a listener to apply any insetsViewCompat.setFitsSystemWindows(this, true);ViewCompat.setOnApplyWindowInsetsListener(this,new android.support.v4.view.OnApplyWindowInsetsListener() {@Overridepublic WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {// Copy over the bottom inset as padding so that we're displayed above the// navigation barv.setPadding(v.getPaddingLeft(), v.getPaddingTop(),v.getPaddingRight(), insets.getSystemWindowInsetBottom());return insets;}});}@Overrideprotected void onFinishInflate() {super.onFinishInflate();mMessageView = (TextView) findViewById(R.id.snackbar_text);mActionView = (Button) findViewById(R.id.snackbar_action);}TextView getMessageView() {return mMessageView;}Button getActionView() {return mActionView;}...
}

至此,Snackbar被创建了。

2.2、对Snackbar进行设置

Snackbar有一些setXX方法,比如setAction、setActionTextColor等方法,这里我们主要介绍setAction和setActionTextColor方法的实现,其余的类似。从2.1的分析我们知道,Snackbar其实就是一个包含了TextView和Button的LinearLayout。明白了这一点之后,就好理解setXX方法了,首先看setAction()方法的实现:

 /*** Set the action to be displayed in this {@link Snackbar}.** @param text     Text to display* @param listener callback to be invoked when the action is clicked*/@NonNullpublic 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() {@Overridepublic void onClick(View view) {listener.onClick(view);// Now dismiss the SnackbardispatchDismiss(Callback.DISMISS_EVENT_ACTION);}});}return this;}

首先调用mView.getActionView()方法,返回的tv其实就是右边的Button,然后判断文本和监听器,设置可见性、文本、监听器。在前面的例子中,我们知道一旦点击了按钮,Snackbar就会消失,处理消失的逻辑在dispatchDismiss()方法中,下面是dispatchDismiss()方法的实现:

void dispatchDismiss(@Callback.DismissEvent int event) {SnackbarManager.getInstance().dismiss(mManagerCallback, event);}

可以看到,会获取一个SnackbarManager对象的实例,然后调用dismiss方法,具体的消失稍后再讲。
下面看setActionTextColor方法,该方法用于设置按钮文本颜色,方法如下:

/*** Sets the text color of the action specified in* {@link #setAction(CharSequence, View.OnClickListener)}.*/@NonNullpublic Snackbar setActionTextColor(@ColorInt int color) {final TextView tv = mView.getActionView();tv.setTextColor(color);return this;}

首先是获取到Button实例,然后调用setTextColor方法,其余setXX之类的设置样式方法类似

2.3、Snackbar的显示与消失

如果需要让Snackbar显示,那么需要调用show方法,下面是show方法的实现:

/*** Show the {@link Snackbar}.*/public void show() {SnackbarManager.getInstance().show(mDuration, mManagerCallback);}

首先获取一个SnackbarManager对象,然后调用它的show方法,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 durationmCurrentSnackbar.duration = duration;// If this is the Snackbar currently being shown, call re-schedule it's// timeoutmHandler.removeCallbacksAndMessages(mCurrentSnackbar);scheduleTimeoutLocked(mCurrentSnackbar);return;} else if (isNextSnackbarLocked(callback)) {// We'll just update the durationmNextSnackbar.duration = duration;} else {// Else, we need to create a new record and queue itmNextSnackbar = 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 linereturn;} else {// Clear out the current snackbarmCurrentSnackbar = null;// Otherwise, just show it nowshowNextSnackbarLocked();}}}

上面的代码比较复杂,下面根据具体情况来分析,首先看其中的参数Callback。
其中mManagerCallback是SnackbarManager的Callback,每一个Snackbar都会有一个这样的对象,定义如下:

final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {@Overridepublic void show() {sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));}@Overridepublic void dismiss(int event) {sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));}};

在show和dismiss方法中就是通过Handler发送了一个消息,sHandler的定义如下:

static final Handler sHandler;static final int MSG_SHOW = 0;static final int MSG_DISMISS = 1;static {sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {@Overridepublic boolean handleMessage(Message message) {switch (message.what) {case MSG_SHOW:((Snackbar) message.obj).showView();return true;case MSG_DISMISS:((Snackbar) message.obj).hideView(message.arg1);return true;}return false;}});}

可以看到sHandler是一个静态的并且在Snackbar被加载进类加载器的时候就会创建,handlerMessage方法就是调用Snackbar的showView()显示和hideView()消失。showView和hideView方法后面再看。
下面针对show方法进行分析:

  1. 如果当前没有Snackbar显示,这时显示一个Snackbar并调用了show方法,那么最终会进入到SnackbarManager的show方法中,由于是第一个Snackbar,那么mCurrentSnackbar、mNextSnackbar均为null,则首先执行这一行代码,
mNextSnackbar = new SnackbarRecord(duration, callback);

接下来,由于mCurrentShackbar为null,则会执行else的代码:

 // Clear out the current snackbarmCurrentSnackbar = null;// Otherwise, just show it nowshowNextSnackbarLocked();

由于执行mNextSnackbar,自然要将mCurrentSnackbar置为null,然后调用showNextSnackbarLocked()方法,下面是该方法的实现:

private void showNextSnackbarLocked() {if (mNextSnackbar != null) {mCurrentSnackbar = mNextSnackbar;mNextSnackbar = null;final Callback callback = mCurrentSnackbar.callback.get();if (callback != null) {callback.show();} else {// The callback doesn't exist any more, clear out the SnackbarmCurrentSnackbar = null;}}}

首先将mCurrntSnackbar设为mNextSnackbar,然后获取Callback,调用Callback的show方法,从前面的分析知道show方法中向Snackbar的Handler发送一个消息,最后调用Snackbar的showView()方法显示Snackbar。
2. 如果当前已经有一个Snackbar显示了,又再调用了该对象的show方法,但是只是设置了不同时间,那么就会执行下段代码:

if (isCurrentSnackbarLocked(callback)) {// Means that the callback is already in the queue. We'll just update the durationmCurrentSnackbar.duration = duration;// If this is the Snackbar currently being shown, call re-schedule it's// timeoutmHandler.removeCallbacksAndMessages(mCurrentSnackbar);scheduleTimeoutLocked(mCurrentSnackbar);return;}

重置mCurrentSnackbar的时间,然后移除mCureentSnackbar发出的消息和回调,mCurrentSnackbar会发出什么消息呢?mCurrentSnackbar会在Snackbar的时间到了后发送一个超时的消息给Handler,下面是handler的实现:

mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {@Overridepublic boolean handleMessage(Message message) {switch (message.what) {case MSG_TIMEOUT:handleTimeout((SnackbarRecord) message.obj);return true;}return false;}});

Handler的处理又是调用handleTimeout方法,handleTimeout方法的实现如下:

 void handleTimeout(SnackbarRecord record) {synchronized (mLock) {if (mCurrentSnackbar == record || mNextSnackbar == record) {cancelSnackbarLocked(record, Snackbar.Callback.DISMISS_EVENT_TIMEOUT);}}}

从上面可以知道会调用cancelSnackbarLocked方法,实现如下:

private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {final Callback callback = record.callback.get();if (callback != null) {// Make sure we remove any timeouts for the SnackbarRecordmHandler.removeCallbacksAndMessages(record);callback.dismiss(event);return true;}return false;}

从上面可以看出,首先移除SnackbarRecord发出的所有消息,然后调用Callback的dismiss方法,从上面我们知道最终是向Snackbar的sHandler发送了一条消息,最终是调用Snackbar的hideView消失。
show方法中重置了时间以及删除了Handler中的消息后就是调用了scheduleTimeoutLocked方法

 private void scheduleTimeoutLocked(SnackbarRecord r) {if (r.duration == Snackbar.LENGTH_INDEFINITE) {// If we're set to indefinite, we don't want to set a timeoutreturn;}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);mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);}

从上面可以看出,如果显示的时间是一个不定的,那么就不管;然后设置时间,最后调用sendMessageDelayed,由于该Snackbar目前正在显示,所以就会在durationMs后发送MSG_TIMEOUT的消息,从上面的分析知道,SnackbarManager的Handler在收到MSG_TIMEOUT后最终会将消息发送给Snackbar的sHandler,最后调用hideView方法。
3. 如果当前已有一个Snackbar正在显示,又创建了一个新的Snackbar并调用show方法,那么SnackbarManager的show方法会执行

else if (isNextSnackbarLocked(callback)) {// We'll just update the durationmNextSnackbar.duration = duration;} else {// Else, we need to create a new record and queue itmNextSnackbar = new SnackbarRecord(duration, callback);}

首先进入isNextSnackbarLocked方法,就是判断该callback是否是mNextSnackbar的,按照我们这个情况不是的,那么就会else语句创建mNextSnackbar。接下来执行下段代码:

 if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {// If we currently have a Snackbar, try and cancel it and wait in linereturn;} else {// Clear out the current snackbarmCurrentSnackbar = null;// Otherwise, just show it nowshowNextSnackbarLocked();}

这时,mCurrentSnackbar不为null,然后调用cancelSnackbarLocked方法,cancelSnackbarLocked方法在前面已经提到就是在Handler中移除mCurrentSnackbar发出的消息,然后调用Callback的dismiss方法,最终是调用Snackbar的hideView方法,并且注意到传入的参数为DISMISS_EVENT_CONSECUTIVE,该参数代表新的Snackbar出现导致旧的消失。在这里我们只看到了旧的消失,而没有看到新的显示,答案在Snackbar的hideView中,下面是hideView的实现:

final void hideView(@Callback.DismissEvent final int event) {if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) {animateViewOut(event);} else {// If anims are disabled or the view isn't visible, just call back nowonViewHidden(event);}}

首先判断是调用animateViewOut还是onViewHidden方法,下面是animateViewOut方法的实现:

private void animateViewOut(final int event) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {ViewCompat.animate(mView).translationY(mView.getHeight()).setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR).setDuration(ANIMATION_DURATION).setListener(new ViewPropertyAnimatorListenerAdapter() {@Overridepublic void onAnimationStart(View view) {mView.animateChildrenOut(0, ANIMATION_FADE_DURATION);}@Overridepublic void onAnimationEnd(View view) {onViewHidden(event);}}).start();} else {Animation anim = AnimationUtils.loadAnimation(mView.getContext(),R.anim.design_snackbar_out);anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);anim.setDuration(ANIMATION_DURATION);anim.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationEnd(Animation animation) {onViewHidden(event);}@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationRepeat(Animation animation) {}});mView.startAnimation(anim);}}

可以看到在动画结束的最后都调用了onViewHidden方法,所以最终都是要调用onViewHidden方法的。animateViewOut提供动画效果,onViewHidden提供具体的业务处理,下面是onViewHidden方法

void onViewHidden(int event) {// First tell the SnackbarManager that it has been dismissedSnackbarManager.getInstance().onDismissed(mManagerCallback);// Now call the dismiss listener (if available)if (mCallback != null) {mCallback.onDismissed(this, event);}if (Build.VERSION.SDK_INT < 11) {// We need to hide the Snackbar on pre-v11 since it uses an old style Animation.// ViewGroup has special handling in removeView() when getAnimation() != null in// that it waits. This then means that the calculated insets are wrong and the// any dodging views do not return. We workaround it by setting the view to gone while// ViewGroup actually gets around to removing it.mView.setVisibility(View.GONE);}// Lastly, hide and remove the view from the parent (if attached)final ViewParent parent = mView.getParent();if (parent instanceof ViewGroup) {((ViewGroup) parent).removeView(mView);}}

从代码中可以看出,首先调用SnackbarManager的onDismissed方法,然后判断Snackbar.Callback是不是null,调用Snackbar.Callback的onDismissed方法,就是我们上面介绍的处理Snackbar消失的方法。最后就是将Snackbar的mView移除。下面看SnackbarManager的onDismissed方法:

/*** Should be called when a Snackbar is no longer displayed. This is after any exit* animation has finished.*/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 onemCurrentSnackbar = null;if (mNextSnackbar != null) {showNextSnackbarLocked();}}}}

从上面的方法可以看到,将mCurrentSnackbar置为null,然后因为mNextSnackbar不为null,所以调用showNextSnackbarLocked方法,从上面的介绍知道showNextSnackbarLocked就是将其置为mCurrentSnackbar然后最后调用了Snackbar的showView方法显示。
下面我们看一下Snackbar的showView方法是如何实现的:

 final void showView() {if (mView.getParent() == null) {final ViewGroup.LayoutParams lp = mView.getLayoutParams();if (lp instanceof CoordinatorLayout.LayoutParams) {// If our LayoutParams are from a CoordinatorLayout, we'll setup our Behaviorfinal CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;final Behavior behavior = new Behavior();behavior.setStartAlphaSwipeDistance(0.1f);behavior.setEndAlphaSwipeDistance(0.6f);behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {@Overridepublic void onDismiss(View view) {view.setVisibility(View.GONE);dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);}@Overridepublic void onDragStateChanged(int state) {switch (state) {case SwipeDismissBehavior.STATE_DRAGGING:case SwipeDismissBehavior.STATE_SETTLING:// If the view is being dragged or settling, cancel the timeoutSnackbarManager.getInstance().cancelTimeout(mManagerCallback);break;case SwipeDismissBehavior.STATE_IDLE:// If the view has been released and is idle, restore the timeoutSnackbarManager.getInstance().restoreTimeout(mManagerCallback);break;}}});clp.setBehavior(behavior);// Also set the inset edge so that views can dodge the snackbar correctlyclp.insetEdge = Gravity.BOTTOM;}mTargetParent.addView(mView);}mView.setOnAttachStateChangeListener(new SnackbarLayout.OnAttachStateChangeListener() {@Overridepublic void onViewAttachedToWindow(View v) {}@Overridepublic 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() {@Overridepublic void run() {onViewHidden(Callback.DISMISS_EVENT_MANUAL);}});}}});if (ViewCompat.isLaidOut(mView)) {if (shouldAnimate()) {// If animations are enabled, animate it inanimateViewIn();} else {// Else if anims are disabled just call back nowonViewShown();}} else {// Otherwise, add one of our layout change listeners and show it in when laid outmView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {@Overridepublic void onLayoutChange(View view, int left, int top, int right, int bottom) {mView.setOnLayoutChangeListener(null);if (shouldAnimate()) {// If animations are enabled, animate it inanimateViewIn();} else {// Else if anims are disabled just call back nowonViewShown();}}});}}

前面的先不看,后面的和hideView类似,animateViewIn负责动画,但是最终会调用onViewShown,所以直接看onViewShown方法,

void onViewShown() {SnackbarManager.getInstance().onShown(mManagerCallback);if (mCallback != null) {mCallback.onShown(this);}}

可以看到会调用SnackbarManager的onShown方法,然后如果Snackbar.Callback不为null,就调用其onShown回调。下面是SnackbarManager的onShown方法:

/*** Should be called when a Snackbar is being shown. This is after any entrance animation has* finished.*/public void onShown(Callback callback) {synchronized (mLock) {if (isCurrentSnackbarLocked(callback)) {scheduleTimeoutLocked(mCurrentSnackbar);}}}

可以看到最终调用了scheduleTimeoutLocked方法,从上面的分析知道scheduleTimeoutLocked方法就是在设定的时间到达后发送一条MSG_TIMEOUT消息给SnackbarManager的Handler,最后又是回到了Snackbar的hideView方法。
4. 显式调用dismiss方法,Snackbar的dismiss方法如下:

/*** Dismiss the {@link Snackbar}.*/public void dismiss() {dispatchDismiss(Callback.DISMISS_EVENT_MANUAL);}

前面介绍过dispatchDismiss方法,最终是调用SnackbarManager的dismiss方法,如下:

public void dismiss(Callback callback, int event) {synchronized (mLock) {if (isCurrentSnackbarLocked(callback)) {cancelSnackbarLocked(mCurrentSnackbar, event);} else if (isNextSnackbarLocked(callback)) {cancelSnackbarLocked(mNextSnackbar, event);}}}

从上面的代码可以看出,就是调用cancelSnackbarLocked方法,而cancelSnackbarLocked方法如下:

private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {final Callback callback = record.callback.get();if (callback != null) {// Make sure we remove any timeouts for the SnackbarRecordmHandler.removeCallbacksAndMessages(record);callback.dismiss(event);return true;}return false;}

可以看到该方法首先移除Handler中的消息,然后调用dismiss方法,最终还是回到Snackbar的hideView方法。

2.4、总结

上面设计到两个类,Snackbar和SnackbarManager,SnackbarManager内部有两个SnackbarRecord,一个mCurrentSnackbar,一个mNextSnackbar,SnackbarManager通过这两个对象实现Snackbar的顺序显示,如果在一个Snackbar显示之前有Snackbar正在显示,那么使用mNextSnackbar保存第二个Snackbar,然后让第一个Snackbar消失,然后消失之后再调用SnackbarManager显示下一个Snackbar,如此循环,实现了Snackbar的顺序显示。
Snackbar负责显示和消失,具体来说其实就是添加和移除View的过程。
Snackbar和SnackbarManager的设计很巧妙,利用一个SnackbarRecord对象保存Snackbar的显示时间以及SnackbarManager.Callback对象,前面说到每一个Snackbar都有一个叫做mManagerCallback的SnackbarManager.Callback对象,下面看一下SnackRecord类的定义:

private static class SnackbarRecord {final WeakReference<Callback> callback;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;}}

Snackbar向SnackbarManager发送消息主要是调用SnackbarManager.getInstace()返回一个单例对象;而SnackManager向Snackbar发送消息就是通过show方法传入的Callback对象。
SnackbarManager中的Handler只处理一个MSG_TIMEOUT事件,最后是调用Snackbar的hideView消失的;Snackbar的sHandler处理两个消息,showView和hideView,而消息的发送者是mManagerCallback,控制者是SnackbarManager。

关注我的技术公众号,不定期会有优质技术文章推送。

微信扫一扫下方二维码即可关注:
微信公众号二维码


http://chatgpt.dhexx.cn/article/WecI0803.shtml

相关文章

Android Snackbar的使用

在项目中肯定有不少地方用到Toast&#xff0c;但是在项目各种各样的需求上&#xff0c;Toast已经不能满足我们的需求了。 其实google在Android 5.0的时候就推出了Snackbar&#xff0c;它算是Toast的一个进阶控件。 它是Material Design中的一个控件&#xff0c;与Toast的最大区…

Snackbar的使用和顶部Snackbar实现

下载链接&#xff1a;Snackbar顶部显示 也可以module的形式直接导入使用 &#xff1a;module导入教程 谷歌在android 5.0 发布后&#xff0c;随后公布了design库和设计理念&#xff08;需翻墙&#xff09;&#xff0c;意图在于规范安卓app的风格&#xff0c;类似而不仅仅ios的…

SnackBar 简单使用

1.简介 Snackbar是Android Support Design Library库中的一个控件&#xff0c;可以在屏幕底部快速弹出消息&#xff0c;比Toast更加好用.可以添加点击行为。多用于结合协调布局使用&#xff08;CoordinatorLayout&#xff09;。 implementation com.android.support:design:28…

MaterialDesign之Snackbar学习笔记

近期实在太忙了&#xff0c;看到好的文章先转载&#xff0c;后续自己慢慢消化吧&#xff01; 转载 http://www.jianshu.com/p/cd1e80e64311#rd 没时间解释了&#xff0c;快使用Snackbar!——Android Snackbar花式使用指南 字数2405 阅读1628 评论4 喜欢54 本文是在《Design…

Snackbar使用详解

Snackbar Snackbar是Android Support Design Library库中的一个控件&#xff0c;可以在屏幕底部快速弹出消息&#xff0c;比Toast更加好用。 开发出一个好的产品&#xff0c;友好的交互是不可缺少的&#xff0c;通常给用户提示信息的方式有三种&#xff1a;Dialog、Toast、Sn…

Snackbar使用详解及其相关框架TSnackbar

简述&#xff1a; Snackbar 是 Android design support library 中的一个组件&#xff0c;它的作用和Toast类似&#xff0c;显示吐司&#xff0c;但Snackbar的特别之处在于Snackbar显示的提示信息可以和用户交互&#xff0c;更好地获取用户反馈信息。同时&#xff0c;它显示的吐…

HAWQ:基于 Hessian 的混合精度神经网络量化

HAWQ&#xff1a;基于 Hessian 的混合精度神经网络量化 摘要动机方法海森方法的有效性分析海森矩阵方法推导根据幂迭代求海森矩阵的最大特征值根据海森矩阵最大特征值确定量化精度与顺序 实验结果ResNet20 On CIFAR-10ResNet50 on ImageNetSqueezeNext on ImageNetInception-V3…

HAWQ技术解析(二) —— 安装部署

一、安装规划1. 选择安装介质 HAWQ的安装介质有两种选择&#xff0c;一是下载源码手工编译&#xff0c;二是使用Pivotal公司提供的HDB安装包。源码的下载地址为 http://apache.org/dyn/closer.cgi/incubator/hawq/2.0.0.0-incubating/apache-hawq-src-2.0.0.0-incubating.tar.…

HAWQ取代传统数仓实践(二)——搭建示例模型(MySQL、HAWQ)

一、业务场景 本系列实验将应用HAWQ数据库&#xff0c;为一个销售订单系统建立数据仓库。本篇说明示例的业务场景、数据仓库架构、实验环境、源和目标库的建立过程、测试数据和日期维度的生成。后面陆续进行初始数据装载、定期数据装载、调度ETL工作流自动执行、维度表技术、事…

HAWQ从0.5开始安装说明.包含hadoop和hawq

文档是从自己的云笔记中复制,格式什么的可能有问题.已修复了一遍. 零.前提 一.安装hadoop 1.1下载并复制hadoop2.6.5 1.2编辑系统配置文件 1.3创建hadoop的tmp临时目录 1.4开始修改配置文件 1.5复制hadoop到其他所有节点上 1.6开始初始化hadoop 1.7 如果服务器重启了,…

开源数据库HAWQ,架构调研

hawq的简介 https://cloud.tencent.com/developer/article/1433137 HAWQ&#xff0c;全称Hadoop With Query&#xff08;带查询Hadoop&#xff09;。HAWQ使企业能够获益于经过锤炼的基于MPP的分析功能及其查询性能&#xff0c;同时利用Hadoop堆栈。HAWQ是一个Hadoop原生大规模…

apache hawq

为什么80%的码农都做不了架构师&#xff1f;>>> 资料地址&#xff1a; 导航页 What is HAWQ? HAWQ Architecture How HAWQ Manages Resources Understanding the Fault Tolerance Service Table Distribution and Storage Choosing the Table Distribution Pol…

HAWQ技术解析(一) —— HAWQ简介

一、SQL on Hadoop 过去五年里&#xff0c;许多企业已慢慢开始接受Hadoop生态系统&#xff0c;将它用作其大数据分析堆栈的核心组件。尽管Hadoop生态系统的MapReduce组件是一个强大的典范&#xff0c;但随着时间的推移&#xff0c;MapReduce自身并不是连接存储在Hadoop生态系统…

《HAWQ-V3: Dyadic Neural Network Quantization》论文阅读

HAWQ-V3阅读笔记 Abstract 混合精度量化&#xff0c;integer-only&#xff0c; Methodology 只采用均匀量化 权重对称量化&#xff0c;激活非对称量化&#xff0c;对量化步长S采用静态量化&#xff0c;采用per-channel的量化方式 3.1量化矩阵的乘法与卷积&#xff08;核心…

HAWQ手动安装

HAWQ手动安装及使用手册 1 HAWQ简介 HAWQ 是 Pivotal 设计的一个大规模并行 SQL 分析处理引擎&#xff0c;支持事务处理。HAWQ 将复杂的查询分割成简单的任何&#xff0c;并分发到并行处理系统中的处理单元执行。包括查询规划器、动态管道、前沿互联和查询执行优化器等等。提…

HAWQ上安装PXF插件,并访问HDFS文件数据

1、说明 HAWQ在github上的地址为&#xff1a;https://github.com/apache/hawq 在安装pxf插件之前&#xff0c;可以先查看一下基础软件对应的版本信息&#xff1a;在hawq目录下的pxf/gradle.properties文件中 因我在安装pxf之前&#xff0c;已经把hadoop及hawq安装完&#xff…

HAWQ技术解析(五) —— 连接管理

服务器启动后&#xff0c;还要经过一系列配置&#xff0c;才能被客户端程序所连接。本篇说明如何配置客户端身份认证&#xff0c;HAWQ的权限管理机制&#xff0c;HAWQ最常用的命令行客户端工具psql及与mysql命令行常用命令类比&#xff0c;最后还将列举一些客户端连接HAWQ数据库…

HAWQ-V3: Dyadic Neural Network Quantization论文学习

论文链接 https://arxiv.org/abs/2011.10680 摘要 目前的低精度量化算法往往具有从浮点值到量化整数值的来回转换的隐藏代价。这种隐藏的成本限制了通过量化神经网络所实现的延迟改进。为了解决这个问题&#xff0c;我们提出了HAWQ-V3&#xff0c;一个新的混合精度纯整数量化框…

HAWQ取代传统数仓实践(一)——为什么选择HAWQ

一、HAWQ取代传统数仓实践&#xff08;一&#xff09;——为什么选择HAWQ 为了跟上所谓“大数据”技术的脚步&#xff0c;从两年前开始着手实践各种SQL-on-Hadoop技术&#xff0c;从最初的Hive&#xff0c;到SparkSQL&#xff0c;再到Impala&#xff0c;进行了一系列ETL、CDC、…

在centos 7.3上进行Apache HAWQ集群安装部署

一、前期准备工作 1、准备三台物理机&#xff0c;master&#xff08;192.168.251.8&#xff09;,dataserver1&#xff08;192.168.251.9&#xff09;,dataserver2&#xff08;192.168.251.10&#xff09;&#xff1b; 2、目前最新版本是2.4.0&#xff0c; 官网下载地址&…