Snackbar源码分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
    • 技术分享图片
  • 其中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
    • 技术分享图片

2.2 对Snackbar属性进行设置

2.3 Snackbar的show显示与点击消失

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

  • 在显示的时候是这样设置动画的,具体如下所示
    • 技术分享图片
  • 在隐藏的时候是这样设置动画的,具体如下所示
    • 技术分享图片
  • 最后具体看一下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移除。
    • 技术分享图片

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类的定义:
    • 技术分享图片
  • 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时为何从下往上移出来,看下面这段代码就知道呢,如下所示
    • 技术分享图片

4.4 为什么Snackbar总是显示在最下面

  • 直接找到make方法中的填充布局,然后去看design_layout_snackbar_include的布局参数,结果如下:
    • 技术分享图片

4.5 Snackbar与吐司有何区别

  • 与Toast进行比较,SnackBar有优势:
    • 1.SnackBar可以自动消失,也可以手动取消(侧滑取消,但是需要在特殊的布局中,后面会仔细说)
    • 2.SnackBar可以通过setAction()来与用户进行交互
    • 3.通过CallBack我们可以获取SnackBar的状态
    • 技术分享图片

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.关于我的博客





以上是关于Snackbar源码分析的主要内容,如果未能解决你的问题,请参考以下文章

轻量级控件SnackBar应用&源码分析

特定片段中的 Snackbar

Snackbar 在片段类中不起作用

来自另一个片段的 Snackbar 回调

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段