Snackbar源码分析

目录介绍

  • 1.最简单创造方法
    • 1.1 Snackbar做用
    • 1.2 最简单的建立
    • 1.3 Snackbar消失的几种方式
  • 2.源码分析
    • 2.1 Snackbar的make方法源码分析
    • 2.2 对Snackbar属性进行设置
    • 2.3 Snackbar的show显示与点击消失
    • 2.4 显示和隐藏中动画源码分析
  • 3.经典总结
    • 3.1 Snackbar和SnackbarManager类的设计
  • 4.思考问题分析
    • 4.1 Snackbar的设计思路
    • 4.2 何时Snackbar显示会致使FloatingActionButton上移
    • 4.3 Snackbar控件show时为什么从下往上移出来
    • 4.4 为何Snackbar老是显示在最下面
    • 4.5 Snackbar与吐司有何区别
  • 5.Snackbar封装库

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 连接地址:https://github.com/yangchong211/YCBlogs
  • 若是以为好,能够star一下,谢谢!固然也欢迎提出建议,万事起于忽微,量变引发质变!
  • Snackbar封装库项目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源码深度分析
    • 最简单的建立,简单改造避免重复建立,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为什么Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
  • 03.DialogFragment源码分析
    • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展现和销毁源码,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源码分析
    • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为什么弹窗点击一下就dismiss呢?
  • 06.Snackbar源码分析
    • 最简单的建立,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为何Snackbar老是显示在最下面
  • 07.弹窗常见问题
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程致使崩溃如何解决?

1.最简单创造方法

1.1 Snackbar做用

  • Snackbar是Android支持库中用于显示简单消息而且提供和用户的一个简单操做的一种弹出式提醒。当使用Snackbar时,提示会出如今消息最底部,一般含有一段信息和一个可点击的按钮。
  • 一样做为消息提示,Snackbar相比于Toast而言,增长了一个用户操做,而且在同时弹出多个消息时,Snackbar会中止前一个,直接显示后一个,也就是说同一时刻只会有一个Snackbar在显示;而Toast则否则,若是不作特殊处理,那么同时能够有多个Toast出现;Snackbar相比于Dialog,操做更少,由于只有一个用户操做的接口,而Dialog最多能够设置三个,另外Snackbar的出现并不影响用户的继续操做,而Dialog则必须须要用户作出响应,因此相比Dialog,Snackbar更轻量。

1.2 最简单的建立

  • 以下所示
    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();

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(B transientBottomBar, @DismissEvent int event)
      • void onShown(B transientBottomBar)
      • 其中onShown在Snackbar可见时调用,onDismissed在Snackbar准备消失时调用。

2.源码分析

2.1 Snackbar的make方法源码分析

  • 建立Snackbar须要使用静态的make方法,而且其中的view参数是一个查找父布局的起点
    • 这里能够看到,snackBar的布局是design_layout_snackbar_include,假如咱们须要自定义SnackBar而且设置字体颜色,大小等属性。则须要拿到这个布局的控件id等。关于封装库,能够查看:https://github.com/yangchong211/YCDialog
    • image
  • 其中findSuitableParent()方法为以view为起点寻找合适的父布局,下面看看findSuitableParent()如何作的?
    • 看了下面源码可知:能够看到若是view是CoordinatorLayout,那么就直接做为父布局了;若是是FrameLayout,而且若是是android.R.id.content,也就是查找到了DecorView,即最顶部,那么就只用这个view;若是不是的话,先保存下来;接下来就是获取view的父布局,而后循环再次判断。这样致使的结果最终会有两个选择,要么是CoordinatorLayout,要么就是FrameLayout,而且是最顶层的那个布局。
    • 若是从View往上搜寻,若是有CoordinatorLayout,那么就使用该CoordinatorLayout ;若是从View往上搜寻,没有CoordinatorLayout,那么就使用android.R.id.content的FrameLayout
    • image

2.2 对Snackbar属性进行设置

  • 2.2.1 setActionTextColor设置action颜色php

    • 能够看到先是获取父布局contentLayout,而后在获取snackbar_action的mActionView
      @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方法br/>@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;
    }android

     
  • 2.2.2 看setAction()方法的实现git

    • 首先是获取父布局contentLayout,而后经过contentLayout调用getActionView()方法,返回的tv其实就是右边的Button,而后判断文本和监听器,设置可见性、文本、监听器。github

      @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 Snackbar的show显示与点击消失

  • 2.3.1 show显示面试

    • 能够看到,首先获取一个SnackbarManager对象,而后调用它的show方法。能够看到在这个方法中,先判断若是是当前正在显示的SnackBar对应的CallBack,则更新显示时长,而后从消息队列中移除,最后调用scheduleTimeoutLocked方法发送定时消息dismiss;若是是下一个要显示的,则更新显示时长;若是都不是,那么就建立一个SnackbarRecord对象。
    • isCurrentSnackbarLocked:若是当前已经有一个Snackbar显示了,又再调用了该对象的show方法,可是只是设置了不一样时间,那么isCurrentSnackbarLocked就会是true,执行里面的方法。
    • isNextSnackbarLocked:若是当前已有一个Snackbar正在显示,又建立了一个新的Snackbar并调用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();
        }
    }

    }segmentfault

    //注意这个callback方法
    final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {br/>@Override
    public void show() {
    sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this));
    }markdown

    @Override
    public void dismiss(int event) {
        sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,
                BaseTransientBottomBar.this));
    }

    };ide

    //处理sHandler发送的消息
    static {
    sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {br/>@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;
    }
    });
    }函数

    - 而后看看showNextSnackbarLocked这个方法,注意:mCurrentSnackbar当前正在显示的,而mNextSnackbar是下一个要显示的。能看到会调用callback的show方法,而这个calllback对象就是咱们在调用snackbar的show方法是传进去的那个。向Snackbar的Handler发送一个消息,最后显示Snackbar。

    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

    • 能够发现,若是咱们设置为无限期,则不会设置超时,直接return函数。而后发送了一个叫作MSG_TIMEOUT的消息,继续追终,最后会到达cancelSnackbarLocked方法。在cancelSnackbarLocked这个方法中,首先移除SnackbarRecord发出的全部消息,而后调用Callback的dismiss方法,从上面咱们知道最终是向Snackbar的sHandler发送了一条消息,最终是调用Snackbar的hideView消失。

      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() {br/>@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;
    }

     

2.4 显示和隐藏中动画源码分析

  • 在显示的时候是这样设置动画的,具体以下所示
    • image
  • 在隐藏的时候是这样设置动画的,具体以下所示
    • image
  • 最后具体看一下animateViewOut部分源码

    • 能够看到在动画结束的最后都调用了onViewHidden方法,因此最终都是要调用onViewHidden方法的。

      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提供具体的业务处理,具体以下所示
    • 首先调用SnackbarManager的onDismissed方法,而后判断Snackbar.Callback是否是null,调用Snackbar.Callback的onDismissed方法,就是咱们上面介绍的处理Snackbar消失的方法。最后就是将Snackbar的mView移除。
    • image

3.经典总结

3.1 Snackbar和SnackbarManager类的设计

  • 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类的定义:
    • image
  • Snackbar向SnackbarManager发送消息主要是调用SnackbarManager.getInstace()返回一个单例对象;而SnackManager向Snackbar发送消息就是经过show方法传入的Callback对象。SnackbarManager中的Handler只处理一个MSG_TIMEOUT事件,最后是调用Snackbar的hideView消失的;Snackbar的sHandler处理两个消息,showView和hideView,而消息的发送者是mManagerCallback,控制者是SnackbarManager。

4.思考问题分析

4.1 Snackbar的设计思路

  • 具体能够看经典总结3.1

4.2 何时Snackbar显示会致使FloatingActionButton上移

  • 为何CoordinatorLayout + FloatingActionButton,当Snackbar显示的时候FloatingActionButton会上移呢,这个是怎么实现的?
    • 把CoordinatorLayout替换成FrameLayout确不行。这个问题咱们还没说。其实这个不是在Snackbar里面处理的,是经过CoordinatorLayout和Behavior来处理的。那具体的处理在哪里呢。FloatingActionButton类里面Behavior类。正是Behavior里面的两个函数layoutDependsOn()和onDependentViewChanged()函数做用的结果。直接进去看下FloatingActionButton内部类Behavior里面这两个函数的代码。

4.3 Snackbar控件show时为什么从下往上移出来

  • 至于说Snackbar控件show时为什么从下往上移出来,看下面这段代码就知道呢,以下所示
    • image

4.4 为何Snackbar老是显示在最下面

  • 直接找到make方法中的填充布局,而后去看design_layout_snackbar_include的布局参数,结果以下:
    • image

4.5 Snackbar与吐司有何区别

  • 与Toast进行比较,SnackBar有优点:
    • 1.SnackBar能够自动消失,也能够手动取消(侧滑取消,可是须要在特殊的布局中,后面会仔细说)
    • 2.SnackBar能够经过setAction()来与用户进行交互
    • 3.经过CallBack咱们能够获取SnackBar的状态
    • image

5.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();

关于其余内容介绍

01.关于博客汇总连接

02.关于个人博客

相关文章
相关标签/搜索