(三十)Snackbar 使用及其源码分析
来源:互联网 发布:百度地图js api ios10 编辑:程序博客网 时间:2024/05/22 15:24
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、Snackbar、Dialog、Toast
Dialog :交互性太强。当弹出的时候会阻断用户操作的连段性,降低用户体验
Toast:没有交互性,用户不能选择
Snackbar:介于 Dialog 和 Toast 之间,既不会打断用户操作,又可以与用户进行交互
二、Snackbar Demo
1.show()
activity_main:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.xiaoyue.snackbar.MainActivity"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击" android:gravity="center" android:onClick="click"/></RelativeLayout>
MainActivity:
public class MainActivity extends AppCompatActivity { Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.btn); } public void click(View view) { Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_LONG).show(); }}
效果:
Snackbar 使用 show 方法的时候,效果与 Toast 类似,只是换成从底部弹出提示信息。
注:SnackBar 的显示时间有三种模式,比 Toast 多一种。
public static final int LENGTH_INDEFINITE = BaseTransientBottomBar.LENGTH_INDEFINITE;
public static final int LENGTH_SHORT = BaseTransientBottomBar.LENGTH_SHORT;
public static final int LENGTH_LONG = BaseTransientBottomBar.LENGTH_LONG;
使用 LENGTH_INDEFINITE 则不会自动消失,需要手动调用 dismiss() 方法。
2.setAction()
为了添加与用户的交互性,需要调用 setAction 方法。
修改 OnClick 方法:
public void click(View view) { Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_SHORT).setAction("确定", new View.OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "点击确认了", Toast.LENGTH_SHORT).show(); } }).show(); }
效果:
3.setCallback(Callback callback)
添加 setCallback 可以对 Snackbar 的 onShown 和 onDismissed 进行监听。
修改 OnClick 方法:
public void click(View view) { Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_SHORT).setAction("确定", new View.OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "点击确认了", Toast.LENGTH_SHORT).show(); } }).setCallback(new Snackbar.Callback(){ @Override public void onShown(Snackbar sb) { Toast.makeText(MainActivity.this, "Snackbar onShown", Toast.LENGTH_SHORT).show(); } @Override public void onDismissed(Snackbar transientBottomBar, @DismissEvent int event) { Toast.makeText(MainActivity.this, "Snackbar onDismissed", Toast.LENGTH_SHORT).show(); } }).show(); }
效果:
4.setActionTextColor()
设置弹出字体的颜色。
修改 OnClick 方法:
public void click(View view) { Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_SHORT).setAction("确定", new View.OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "点击确认了", Toast.LENGTH_SHORT).show(); } }).setCallback(new Snackbar.Callback(){ @Override public void onShown(Snackbar sb) { Toast.makeText(MainActivity.this, "Snackbar onShown", Toast.LENGTH_SHORT).show(); } @Override public void onDismissed(Snackbar transientBottomBar, @DismissEvent int event) { Toast.makeText(MainActivity.this, "Snackbar onDismissed", Toast.LENGTH_SHORT).show(); } }).setActionTextColor(Color.BLUE).show(); }
效果:
5.修改提示信息字体样式
修改 OnClick 方法:
public void click(View view) { Snackbar snackbar = Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_SHORT).setAction("确定", new View.OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "点击确认了", Toast.LENGTH_SHORT).show(); } }).setCallback(new Snackbar.Callback(){ @Override public void onShown(Snackbar sb) { Toast.makeText(MainActivity.this, "Snackbar onShown", Toast.LENGTH_SHORT).show(); } @Override public void onDismissed(Snackbar transientBottomBar, @DismissEvent int event) { Toast.makeText(MainActivity.this, "Snackbar onDismissed", Toast.LENGTH_SHORT).show(); } }).setActionTextColor(Color.BLUE); View view1 = snackbar.getView(); TextView textView = view1.findViewById(R.id.snackbar_text); textView.setTextColor(Color.RED); snackbar.show(); }
效果:
Snackbar 没有提供直接的方法或接口供我们去修改提示信息的样式,也可以去重写 Snackbar 实现这个样式的改变。这边是通过 getView() 获取到 SnackBar 的布局,再通过 findViewById()获取到对应的 TextView ,进行修改。
三、源码分析
1.make
Snackbar.make(button, "SnackBar Test", Snackbar.LENGTH_SHORT).show();
Snackbar 的 make 方法:
public static Snackbar make(@NonNull View view, @NonNull CharSequence text, @Duration int duration) { //获取到当前界面的跟布局 final ViewGroup parent = findSuitableParent(view); if (parent == null) { throw new IllegalArgumentException("No suitable parent found from the given view. " + "Please provide a valid view."); } //加载布局 final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final SnackbarContentLayout content = (SnackbarContentLayout) inflater.inflate( R.layout.design_layout_snackbar_include, parent, false); final Snackbar snackbar = new Snackbar(parent, content, content); snackbar.setText(text); snackbar.setDuration(duration); return snackbar; }
Snackbar 的 findSuitableParent 方法:
private static ViewGroup findSuitableParent(View view) { ViewGroup fallback = null; do { //CoordinatorLayout 可以作为 Material Design 其他控件的跟布局 if (view instanceof CoordinatorLayout) { // We've found a CoordinatorLayout, use it return (ViewGroup) view; } else if (view instanceof FrameLayout) { if (view.getId() == android.R.id.content) { // If we've hit the decor content view, then we didn't find a CoL in the // hierarchy, so use it. return (ViewGroup) view; } else { // It's not the content view but we'll use it as our fallback fallback = (ViewGroup) view; } } if (view != null) { // Else, we will loop and crawl up the view hierarchy and try to find a parent final ViewParent parent = view.getParent(); view = parent instanceof View ? (View) parent : null; } } while (view != null); // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback return fallback; }
findSuitableParent 通过循环,不断的获取父容器,最终获取到当前 View 的根节点。CoordinatorLayout 是跟 SnackBar 同属于 Material Design,可以作为 Material Design 下其他控件的跟布局。所以碰见 CoordinatorLayout 就可以直接返回,否则的话会去寻找 最顶层窗口 DecorView 下的一个 FrameLayout 布局,他的 id 是 content,这个系统都会帮我们添加。
可以利用 CoordinatorLayout 可以作为其他控件的跟布局进行修改 SnackBar 的显示位置。
修改 activity_main:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.xiaoyue.snackbar.MainActivity"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="500dp"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击" android:gravity="center" android:onClick="click"/> </android.support.design.widget.CoordinatorLayout></RelativeLayout>
效果:
继续 make 方法往下:
final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final SnackbarContentLayout content = (SnackbarContentLayout) inflater.inflate( R.layout.design_layout_snackbar_include, parent, false);
R.layout.design_layout_snackbar_include 这个布局就是显示出来的 SnackBar 的布局,这个代码在对应的缓存里面。
design_layout_snackbar_include:
<view xmlns:android="http://schemas.android.com/apk/res/android" class="android.support.design.internal.SnackbarContentLayout" android:theme="@style/ThemeOverlay.AppCompat.Dark" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom"> <TextView android:id="@+id/snackbar_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:paddingTop="@dimen/design_snackbar_padding_vertical" android:paddingBottom="@dimen/design_snackbar_padding_vertical" android:paddingLeft="@dimen/design_snackbar_padding_horizontal" android:paddingRight="@dimen/design_snackbar_padding_horizontal" android:textAppearance="@style/TextAppearance.Design.Snackbar.Message" android:maxLines="@integer/design_snackbar_text_max_lines" android:layout_gravity="center_vertical|left|start" android:ellipsize="end" android:textAlignment="viewStart"/> <Button android:id="@+id/snackbar_action" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal" android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal" android:layout_gravity="center_vertical|right|end" android:minWidth="48dp" android:visibility="gone" android:textColor="?attr/colorAccent" style="?attr/borderlessButtonStyle"/></view>
接下去是把获取到的布局传递给 Snackbar 的构造函数。
final Snackbar snackbar = new Snackbar(parent, content, content);
Snackbar 构造函数:
private Snackbar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) { super(parent, content, contentViewCallback); }
直接调用了父类 BaseTransientBottomBar 的构造函数。
BaseTransientBottomBar 的构造函数:
protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content, ... mView = (SnackbarBaseLayout) inflater.inflate( R.layout.design_layout_snackbar, mTargetParent, false); mView.addView(content); ... }
BaseTransientBottomBar 有重新加载了一个布局 R.layout.design_layout_snackbar,然后把 上面加载的 SnackBar 布局添加进来。
design_layout_snackbar:
<view xmlns:android="http://schemas.android.com/apk/res/android" class="android.support.design.widget.Snackbar$SnackbarLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:theme="@style/ThemeOverlay.AppCompat.Dark" style="@style/Widget.Design.Snackbar" />
2.show
接下去看一下 show 方法。
show:
public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); } public void show(int duration, Callback callback) { synchronized (mLock) { //判断是否有 SnackBar 已经显示出来 if (isCurrentSnackbarLocked(callback)) { // Means that the callback is already in the queue. We'll just update the duration mCurrentSnackbar.duration = duration; // If this is the Snackbar currently being shown, call re-schedule it's // timeout mHandler.removeCallbacksAndMessages(mCurrentSnackbar); scheduleTimeoutLocked(mCurrentSnackbar); return; //判断当前的这个 SnackBar 是否是再次调用显示,是的话,只更新时间 } else if (isNextSnackbarLocked(callback)) { // We'll just update the duration mNextSnackbar.duration = duration; } else { //第一次进来的时候,创建一个 SnackBar 记录 SnackbarRecord // Else, we need to create a new record and queue it mNextSnackbar = new SnackbarRecord(duration, callback); } //是否取消 SnackBar if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { // If we currently have a Snackbar, try and cancel it and wait in line return; } else { // Clear out the current snackbar mCurrentSnackbar = null; // Otherwise, just show it now showNextSnackbarLocked(); } } }
mManagerCallback :
final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { @Override public void show() { sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this)); } @Override public void dismiss(int event) { sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, BaseTransientBottomBar.this)); } };
这是 show 传递进来的 Callback,在后面的 showNextSnackbarLocked()中被调用 。
SnackbarRecord :
private static class SnackbarRecord { final WeakReference<Callback> callback; int duration; boolean paused; SnackbarRecord(int duration, Callback callback) { this.callback = new WeakReference<>(callback); this.duration = duration; } boolean isSnackbar(Callback callback) { return callback != null && this.callback.get() == callback; } }
SnackbarRecord 是一个内部类,就是一个简单的赋值,要注意的地方是在这里,传进来的 callback 变成了弱引用,避免内存泄漏。
showNextSnackbarLocked:
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; } } }
show 方法最后是调用到 showNextSnackbarLocked (),在这里又重新获取前面的 Callback (这样在这边就是弱引用了),然后调用 Callback 的 show 方法,调用了 sHandler 发送 MSG_SHOW 消息。
sHandler :
static { sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @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; } }); }
sHandler 发送消息,调用了 BaseTransientBottomBar (这里的 BaseTransientBottomBar 即要显示的 SnackBar)的 showView ()。
BaseTransientBottomBar 的 showView:
final void showView() { if (mView.getParent() == null) { final ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (lp instanceof CoordinatorLayout.LayoutParams) { ... } mTargetParent.addView(mView); }
BaseTransientBottomBar 的 showView () 中间有一段对父容器是 CoordinatorLayout 的时候进行处理,这个不管。在最后,BaseTransientBottomBar 的 showView ()会调用 mTargetParent.addView(mView) 这个语句,mTargetParent 就是在上面 make ()中获取到的根节点,mView 就是我们包装了一层的 SnackBar 布局,这样就把 SnackBar 布局添加到界面上了。
三、扩展
可以学习这个实现把一个 View 添加到全局窗口中。有时间回来实现。。。
- (三十)Snackbar 使用及其源码分析
- 轻量级控件SnackBar使用以及源码分析
- Snackbar使用及其注意事项
- Snackbar使用及其注意事项
- Snackbar使用及其注意事项
- SnackBar源码分析(来自design包)
- Snackbar源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- (4.1.43.3)design support library:Snackbar使用及其注意事项
- Snackbar使用详解及其相关框架TSnackbar
- Snackbar使用详解及其相关框架TSnackbar
- Android-Snackbar用法及源码分析
- 轻量级控件SnackBar应用&源码分析
- Redis源码分析(三十)--- pubsub发布订阅模式
- iOS开发-dispatch_sync阻塞主线程造成死锁
- 学习二叉树
- VSS无法访问 (0x80072EFD) 转载
- Search in Rotated Sorted Array I
- 软件工程
- (三十)Snackbar 使用及其源码分析
- java邮件发送的简单实现
- 爬虫系列0安装虚拟环境
- Android Framework学习笔记 -- Audio 混音
- 面向对象---接口
- git获取与创建项目命令, 基本快照
- git提交规范
- 第六讲:构造拷贝析构
- 奥比中光Orbbec Astra Pro RGBD 3D视觉传感器在ROS(indigo和kinetic)使用说明 rgb depth同时显示