目录介绍

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万字],转载请注明出处,谢谢!

如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

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() {

@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.2.1 setActionTextColor设置action颜色

可以看到先是获取父布局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;

}

2.2.2 看setAction()方法的实现

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

@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();

}

}

}

@Override

public void dismiss(int event) {

sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,

BaseTransientBottomBar.this));

}

};

- 然后看看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);

}

//

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 显示和隐藏中动画源码分析

在显示的时候是这样设置动画的,具体如下所示

在隐藏的时候是这样设置动画的,具体如下所示

最后具体看一下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.关于我的博客

android snackbar源码,Snackbar源码分析相关推荐

  1. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  2. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  3. Android shortcut的使用及源码分析

    Android shortcut的使用及源码分析 最近遇到了一个切换国家码后部分应用的shortcut未更新的问题,就学习了shortcut的相关知识,在这里分享一下我了解的知识,希望能对大家有帮助. ...

  4. Android 异步处理之IntentService源码分析

    本文授权发布公众号[刘桂林],星球[Hi Android] 今天介绍一下IntentService,他和Service其实差不多,只是内部实现了一个HandlerThread,这点我们看源码其实就可以 ...

  5. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  6. 【SemiDrive源码分析】【X9芯片启动流程】30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一)

    [SemiDrive源码分析][X9芯片启动流程]30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一) 一.Android Kernel 启动流程分析 ...

  7. 【Android源码】源码分析深度好文+精编内核解析分享

    阅读Android源码的好处有很多,比如:可以加深我们对系统的了解:可以参考牛人优雅的代码实现:可以从根本上找出一些bug的原因-我们应该庆幸Android是开源的,所有的功能都可以看到实现,所有的b ...

  8. Android Q 基站刷新接口源码分析 适配双卡手机基站刷新逻辑

    目录 一.获取基站信息的两个关键方法 getAllCellInfo调用流程总结 requestCellInfoUpdate 流程总结 问题 二.双卡手机适配 Android Q requestCell ...

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

    文章目录 一.目前的 API 现状 二.安装应用源码分析 1.安装按钮执行的操作 2.返回到 HomeActivity 执行的操作 一.目前的 API 现状 下图是 VirtualApp 官方给出的集 ...

最新文章

  1. 0基础小白学好JAVA的5个方法
  2. 【c语言】蓝桥杯算法提高 数的运算
  3. leetcode 46 java,leetcode46.java
  4. maven2学习总结(3,maven2在淘宝项目的应用)
  5. 关于SQL优化这些你了解吗?
  6. 【年度重磅】2020华为云社区年度技术精选合集,700页+免费下载!
  7. 时间序列趋势判断(三)——Mann-Kendall趋势检验
  8. Lec 16 Projection matrices and least squares
  9. Unity 5.6 UGUI 按钮被遮挡
  10. C#电子病历管理系统源码 医院电子病历源码
  11. 怎么看服务器cpu温度命令_ubuntu 命令行下查看及监测CPU温度的方法
  12. Coursera视频无法播放问题解决
  13. 基于51单片机的扫地小车,扫地机器人设计。 有原理图,程序代码,原文
  14. 深交所股票交易接口的概述
  15. CSS3 制作旋转的大风车
  16. 驱动能力,带负载能力
  17. 国家/行业标准查询及下载全流程
  18. 【框架大全】2022年可用QQ机器人框架价格表
  19. vb.net 获取系统图标_【系统更新V4】优麒麟 20.04 LTS持续完善!优化高清屏支持,上架6款新应用!...
  20. 51nod1486 大大走格子

热门文章

  1. 云服务+开源,建设活力开源社区,我们一直在路上!
  2. db2move 导入导出数据库
  3. 每天回顾linux命令(ls)
  4. 与王建硕的对话:写Blog促进思考,喜爱Linux和PHP
  5. STM32CubeMX配置PWM驱动舵机
  6. 领夹式无线麦克风方案
  7. obs多开教程_OBS多路推流插件
  8. WM8976G声卡驱动的研究
  9. windows用户管理
  10. 正则表达式工具 RegexBuddy 简单使用