了不起的厂长 2019-06-28
1.最简单创造方法
2.源码分析
3.经典总结
4.思考问题分析
Snackbar sb = Snackbar.make(v,"潇湘剑雨",Snackbar.LENGTH_LONG) .setAction("删除吗?", new View.OnClickListener() { @Override public void onClick(View v) { //点击了"是吗?"字符串操作 ToastUtils.showRoundRectToast("逗比"); } }) .setActionTextColor(Color.RED) .setText("杨充是个逗比") .addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() { @Override public void onDismissed(Snackbar transientBottomBar, int event) { super.onDismissed(transientBottomBar, 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: ToastUtils.showRoundRectToast("删除成功"); break; case Snackbar.Callback.DISMISS_EVENT_ACTION: ToastUtils.showRoundRectToast("撤销了删除操作"); break; } Log.d("MainActivity","onDismissed"); } @Override public void onShown(Snackbar transientBottomBar) { super.onShown(transientBottomBar); Log.d("MainActivity","onShown"); } }); sb.show();
Snackbar显示只有一种方式,那就是调用show()方法,但是消失有几种方式:时间到了自动消失、点击了右侧按钮消失、新的Snackbar出现导致旧的Snackbar消失、滑动消失或者通过调用dismiss()消失。
分别对应于Snackbar.Callback中的几个常量值。
Callback有两个方法
创建Snackbar需要使用静态的make方法,并且其中的view参数是一个查找父布局的起点
其中findSuitableParent()方法为以view为起点寻找合适的父布局,下面看看findSuitableParent()如何做的?
2.2.1 setActionTextColor设置action颜色
@NonNull public Snackbar setActionTextColor(@ColorInt int color) { final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); final TextView tv = contentLayout.getActionView(); tv.setTextColor(color); return this; } //然后看SnackbarContentLayout类中getActionView方法 @Override protected void onFinishInflate() { super.onFinishInflate(); mMessageView = (TextView) findViewById(R.id.snackbar_text); mActionView = (Button) findViewById(R.id.snackbar_action); } public Button getActionView() { return mActionView; }
2.2.2 看setAction()方法的实现
@NonNull public Snackbar setAction(CharSequence text, final View.OnClickListener listener) { final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); final TextView tv = contentLayout.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); // Now dismiss the Snackbar dispatchDismiss(BaseCallback.DISMISS_EVENT_ACTION); } }); } return this; }
2.3.1 show显示
public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); } public void show(int duration, Callback callback) { synchronized (mLock) { if (isCurrentSnackbarLocked(callback)) { // 表示回调已在队列中。我们只需更新持续时间 mCurrentSnackbar.duration = duration; // 如果这是当前正在显示的Snackbar,请调用重新调度它的 // timeout mHandler.removeCallbacksAndMessages(mCurrentSnackbar); // 这个方法很重要,当执行时间结束后,就会自动dismiss。下面再详细分析 scheduleTimeoutLocked(mCurrentSnackbar); return; } else if (isNextSnackbarLocked(callback)) { //我们只需更新持续时间 mNextSnackbar.duration = duration; } else { //否则,我们需要创建一个新记录并对其进行排队。 mNextSnackbar = new SnackbarRecord(duration, callback); } if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { // 如果我们目前有一个Snackbar,请尝试取消它并排队等待。 return; } else { // 清除当前的快捷键 mCurrentSnackbar = null; //很重要 showNextSnackbarLocked(); } } } //注意这个callback方法 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)); } }; //处理sHandler发送的消息 static { sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_SHOW: ((BaseTransientBottomBar) message.obj).showView(); return true; case MSG_DISMISS: ((BaseTransientBottomBar) message.obj).hideView(message.arg1); return true; } return false; } }); }
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 Snackbar mCurrentSnackbar = null; } } }
2.3.2 看看scheduleTimeoutLocked源码如何销毁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); mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs); } //接受mHandler消息并且处理 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; } }); // void handleTimeout(SnackbarRecord record) { synchronized (mLock) { if (mCurrentSnackbar == record || mNextSnackbar == record) { cancelSnackbarLocked(record, Snackbar.Callback.DISMISS_EVENT_TIMEOUT); } } } //最终可以追踪到这个方法 private boolean cancelSnackbarLocked(SnackbarRecord record, int event) { final Callback callback = record.callback.get(); if (callback != null) { // Make sure we remove any timeouts for the SnackbarRecord mHandler.removeCallbacksAndMessages(record); callback.dismiss(event); return true; } return false; }
在显示的时候是这样设置动画的,具体如下所示
在隐藏的时候是这样设置动画的,具体如下所示
最后具体看一下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() { @Override public void onAnimationStart(View view) { mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION); } @Override public 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() { @Override public void onAnimationEnd(Animation animation) { onViewHidden(event); } @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} }); mView.startAnimation(anim); } }
onViewHidden提供具体的业务处理,具体如下所示
Snackbar负责显示和消失,具体来说其实就是添加和移除View的过程。Snackbar和SnackbarManager的设计很巧妙,利用一个SnackbarRecord对象保存Snackbar的显示时间以及SnackbarManager.Callback对象,前面说到每一个Snackbar都有一个叫做mManagerCallback的SnackbarManager.Callback对象,下面看一下SnackRecord类的定义:
为什么CoordinatorLayout + FloatingActionButton,当Snackbar显示的时候FloatingActionButton会上移呢,这个是怎么实现的?
至于说Snackbar控件show时为何从下往上移出来,看下面这段代码就知道呢,如下所示
直接找到make方法中的填充布局,然后去看design_layout_snackbar_include的布局参数,结果如下:
与Toast进行比较,SnackBar有优势:
可以一行代码调用,也可以自己使用链式编程调用。支持设置显示时长属性;可以设置背景色;可以设置文字大小,颜色;可以设置action内容,文字大小,颜色,还有点击事件;可以设置icon;代码如下所示,更多内容可以直接运行demo哦!
//1.只设置text SnackBarUtils.showSnackBar(this,"滚犊子"); //2.设置text,action,和点击事件 SnackBarUtils.showSnackBar(this, "滚犊子", "ACTION", new View.OnClickListener() { @Override public void onClick(View v) { ToastUtils.showRoundRectToast("滚犊子啦?"); } }); //3.设置text,action,和点击事件,和icon SnackBarUtils.showSnackBar(this, "滚犊子", "ACTION",R.drawable.icon_cancel, new View.OnClickListener() { @Override public void onClick(View v) { ToastUtils.showRoundRectToast("滚犊子啦?"); } }); //4.链式调用 SnackBarUtils.builder() .setBackgroundColor(this.getResources().getColor(R.color.color_7f000000)) .setTextSize(14) .setTextColor(this.getResources().getColor(R.color.white)) .setTextTypefaceStyle(Typeface.BOLD) .setText("滚犊子") .setMaxLines(4) .centerText() .setActionText("收到") .setActionTextColor(this.getResources().getColor(R.color.color_f25057)) .setActionTextSize(16) .setActionTextTypefaceStyle(Typeface.BOLD) .setActionClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ToastUtils.showRoundRectToast("滚犊子啦?"); } }) .setIcon(R.drawable.icon_cancel) .setActivity(MainActivity.this) .setDuration(SnackBarUtils.DurationType.LENGTH_INDEFINITE) .build() .show();