SnackBar源码解析及封装

废话不多说,先看效果图,没有效果图就等于扯淡。

在这里插入图片描述

因为上传大小有限制,并且GIF有卡顿。所以大家下载源码可以看到更多效果

SnackBar

前两天创建新项目的activity的时候,不小心选择了ScrollingActivity,打开一看里面有一个SnackBar来显示出吐司。感觉跟Toast一样,于是研究了一番。先来简单的介绍一下SnackBar,Snackbar 是 Android 5.0 新特性——Material Design 中的一个控件,用来代替 Toast ,Snackbar与Toast的主要区别是:Snackbar可以滑动退出,也可以处理用户交互(点击)事件。总之Toast能做的它也可以做,而且部分功能做的更好。比如显示时长,Toast最多3s,SnackBar可以自定义时长。这一点比Toast强大很多。

Snackbar特性

  1. 屏幕上同时最多只能显示一个Snackbar;
  2. 可以在Snackbar中添加一个按钮,处理用户点击事件;
  3. Snackbar一般需要CoordinatorLayout来作为父容器,CoordinatorLayout保证Snackbar可以右滑退出;
  4. 出现时不会阻碍用户在屏幕上的输入;
  5. 可以自定义显示时长;
  6. 可以监听显示隐藏事件,便于用户操作更多。

扩展功能

因为SnackBar并不能完全支撑我们的开发需要,所以这里通过修改源码拓展了一些功能

  1. SnackBar展示位置(SnackBar默认只能在底部显示,并且动画也只是根据底部弹出的,所以如果想要在其他位置显示,需要通过修改源码在修改;具体代码在BaseTransientBottomBar.java中的445行设置,如果想要拓展的,可以直接修改此类)
  2. 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

如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。

											                               	---财财亲笔
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁抢我的小口口

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值