SnackBar源码解析及封装
废话不多说,先看效果图,没有效果图就等于扯淡。
因为上传大小有限制,并且GIF有卡顿。所以大家下载源码可以看到更多效果
SnackBar
前两天创建新项目的activity的时候,不小心选择了ScrollingActivity,打开一看里面有一个SnackBar来显示出吐司。感觉跟Toast一样,于是研究了一番。先来简单的介绍一下SnackBar,Snackbar 是 Android 5.0 新特性——Material Design 中的一个控件,用来代替 Toast ,Snackbar与Toast的主要区别是:Snackbar可以滑动退出,也可以处理用户交互(点击)事件。总之Toast能做的它也可以做,而且部分功能做的更好。比如显示时长,Toast最多3s,SnackBar可以自定义时长。这一点比Toast强大很多。
Snackbar特性
- 屏幕上同时最多只能显示一个Snackbar;
- 可以在Snackbar中添加一个按钮,处理用户点击事件;
- Snackbar一般需要CoordinatorLayout来作为父容器,CoordinatorLayout保证Snackbar可以右滑退出;
- 出现时不会阻碍用户在屏幕上的输入;
- 可以自定义显示时长;
- 可以监听显示隐藏事件,便于用户操作更多。
扩展功能
因为SnackBar并不能完全支撑我们的开发需要,所以这里通过修改源码拓展了一些功能
- SnackBar展示位置(SnackBar默认只能在底部显示,并且动画也只是根据底部弹出的,所以如果想要在其他位置显示,需要通过修改源码在修改;具体代码在BaseTransientBottomBar.java中的445行设置,如果想要拓展的,可以直接修改此类)
- SnackBar展示动画(跟上面一样,动画默认是底部弹出。如果需要修改,可以在源码MySnackBar.java中的256行通过setGravity()方法来追踪,具体都有注释,想必不难理解。)
这里的拓展功能是我根据我当前项目需要,就只修改了这两处的源码。大家可以根据项目需求自行拓展
我修改的源码
1.MySnackBar.java(这个类没有修改,新增了一个方法,用来调用)
/** * 自定义SnackBar显示位置 * @param type */ public void setGravity(int type) { BaseTransientBottomBar.DISPLAY_LOCATION_TYPE = type; }
2.BaseTransientBottomBar.java(这个类里面修改了两处,一个是显示位置,一个是显示动画)
这里是显示位置修改的代码
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 Behavior final 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() { @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; } //SnackBar源码为 mTargetParent.addView(mView),但是为了兼容动态显示的位置,这里使用了动态布局加载包裹,可以实现自由切换底部还是顶部 LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); //此处相当于布局文件中的Android:layout_gravity属性 if(DISPLAY_LOCATION_TYPE == 1){ param.gravity = Gravity.TOP; } else if(DISPLAY_LOCATION_TYPE == 2){ param.gravity = Gravity.BOTTOM; } else if(DISPLAY_LOCATION_TYPE == 3){ param.gravity = Gravity.CENTER; } mView.setLayoutParams(param); LinearLayout linear = new LinearLayout(mContext); //注意,对于LinearLayout布局来说,设置横向还是纵向是必须的!否则就看不到效果了。 linear.setOrientation(LinearLayout.HORIZONTAL); linear.addView(mView); mTargetParent.addView(linear); } mView.setOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { } @Override public void onViewDetachedFromWindow(View view) { 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 View.OnLayoutChangeListener() { @Override public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) { mView.setOnLayoutChangeListener(null); if (shouldAnimate()) { // If animations are enabled, animate it in animateViewIn(); } else { // Else if anims are disabled just call back now onViewShown(); } } }); } }
这里是显示动画修改的代码
/** * 动画开始方法 */ void animateViewIn() { if (Build.VERSION.SDK_INT >= 12) { final int viewHeight; if(DISPLAY_LOCATION_TYPE == 1){ //顶部显示 viewHeight = -mView.getHeight(); }else{ //底部显示 viewHeight = mView.getHeight(); } if (USE_OFFSET_API) { ViewCompat.offsetTopAndBottom(mView, viewHeight); } else { mView.setTranslationY(viewHeight); } final ValueAnimator animator = new ValueAnimator(); animator.setIntValues(viewHeight, 0); animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); animator.setDuration(ANIMATION_DURATION); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { mContentViewCallback.animateContentIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION,ANIMATION_FADE_DURATION); } @Override public void onAnimationEnd(Animator animator) { onViewShown(); } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { private int mPreviousAnimatedIntValue = viewHeight; @Override public void onAnimationUpdate(ValueAnimator animator) { int currentAnimatedIntValue = (int) animator.getAnimatedValue(); if (USE_OFFSET_API) { ViewCompat.offsetTopAndBottom(mView, currentAnimatedIntValue - mPreviousAnimatedIntValue); } else { mView.setTranslationY(currentAnimatedIntValue); } mPreviousAnimatedIntValue = currentAnimatedIntValue; } }); animator.start(); } else { final Animation anim = android.view.animation.AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_in); anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); anim.setDuration(ANIMATION_DURATION); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationEnd(Animation animation) { onViewShown(); } @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} }); mView.startAnimation(anim); } } /** * 动画结束方法 * @param event */ private void animateViewOut(final int event) { if (Build.VERSION.SDK_INT >= 12) { final ValueAnimator animator = new ValueAnimator(); if(DISPLAY_LOCATION_TYPE == 1){ //改成0,顶部显示才会生效(design_layout_snackbar.xml里的layout_gravity由bottom改为top) //如果要底部显示(注释这一行即可) mView.setTranslationY(0); //顶部显示 animator.setIntValues(0, -mView.getHeight()); }else{ //底部显示 animator.setIntValues(0, mView.getHeight()); } animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); animator.setDuration(ANIMATION_DURATION); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION); } @Override public void onAnimationEnd(Animator animator) { onViewHidden(event); } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { private int mPreviousAnimatedIntValue = 0; @Override public void onAnimationUpdate(ValueAnimator animator) { int currentAnimatedIntValue = (int) animator.getAnimatedValue(); if (USE_OFFSET_API) { ViewCompat.offsetTopAndBottom(mView, currentAnimatedIntValue - mPreviousAnimatedIntValue); } else { mView.setTranslationY(currentAnimatedIntValue); } mPreviousAnimatedIntValue = currentAnimatedIntValue; } }); animator.start(); } else { final Animation anim = android.view.animation.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); } }
我修改的代码就上面这么多
以上就是所有的代码
附上demo源码。
源码:源码请点这里
如果下不了源码,可以加微信,手机号在下面。
Q:486789970(QQ现在很少用)
V:18588400509(如果着急,可以直接加微信)
email:mr.cai_cai@foxmail.com
如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。
---财财亲笔