SnackBar源码解析-了解它的工作原理
来源:互联网 发布:上海钧珩大数据科技 编辑:程序博客网 时间:2024/05/03 15:58
Android Material Design中添加了许多东西,以前只是用一下(自己做demo玩,并没有用在实际项目中)。接下来的一系列文章,要给大家分析源码了,不知道你们高不高兴,开不开心,满不满足。
1.前言
Snackbar是一个控件,在屏幕底部快速弹出控件,与Toast不同的是,Snackbar支持相应用户操作。那么接下来就从源码的角度来告诉大家它的工作原理,方便大家更好的使用。
这篇博客涉及到的东西如下
- Android消息机制
- Snackbar.java
- SnackbarManager.java
- design_layout_snackbar.xml
- design_layout_snackbar_include.xml
- design包下的values.xml文件
2.SnackBar的创建过程
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show();
接下来看SnackBar的make方法。
public static Snackbar make(@NonNull View view,NonNull CharSequence text,@Duration int duration) { Snackbar snackbar = newSnackbar(findSuitableParent(view)); snackbar.setText(text); snackbar.setDuration(duration); return snackbar; }
我们来看下newSnackbar方法,看这个之前,我们要先了解了解下 findSuitableParent方法
private static ViewGroup findSuitableParent(View view) { ViewGroup fallback = null; do { 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; }
从上面的代码可以看出,直到返回CoordinatorLayout或者是decorview中的content部分(通过setContentView设置的那个啦)。
private Snackbar(ViewGroup parent) { mTargetParent = parent; mContext = parent.getContext(); ThemeUtils.checkAppCompatTheme(mContext); LayoutInflater inflater = LayoutInflater.from(mContext); mView = (SnackbarLayout) inflater.inflate( R.layout.design_layout_snackbar, mTargetParent, false); }
这个方法中,检查主题,加载布局等等。我们接下来看看加载布局的具体操作,在SnackbarLayout中。
3.布局的加载及初始化过程
在SnackLayout的构造方法中,做了如下操作,我们来一一说明。
3.1 属性初始化
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout); mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1); mMaxInlineActionWidth = a.getDimensionPixelSize( R.styleable.SnackbarLayout_maxActionInlineWidth, -1); if (a.hasValue(R.styleable.SnackbarLayout_elevation)) { ViewCompat.setElevation(this, a.getDimensionPixelSize( R.styleable.SnackbarLayout_elevation, 0)); }
3.2 布局加载
这里的属性不在多介绍,我们重点看下下面这句话
LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);
结合上下文,就是将design_layout_snackbar_include.xml文件的布局加载到design_layout_snackbar.xml文件中。(因为使用了merge标签)
我们分别来看看这2个文件
<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" style="@style/Widget.Design.Snackbar" />
而上面布局中的style是在design包中values文件中定义好的一个style,包括高度、背景色等属性,代码如下:
<style name="Widget.Design.Snackbar" parent="android:Widget"> <item name="android:theme">@style/ThemeOverlay.AppCompat.Dark</item> <item name="android:minWidth">@dimen/design_snackbar_min_width</item> <item name="android:maxWidth">@dimen/design_snackbar_max_width</item> <item name="android:background">@drawable/design_snackbar_background</item> <item name="android:paddingLeft">@dimen/design_snackbar_padding_horizontal</item> <item name="android:paddingRight">@dimen/design_snackbar_padding_horizontal</item> <item name="elevation">@dimen/design_snackbar_elevation</item> <item name="maxActionInlineWidth">@dimen/design_snackbar_action_inline_max_width</item> </style>
最重要的design_layout_snackbar_include.xml文件来了;
<merge xmlns:android="http://schemas.android.com/apk/res/android"><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"/><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: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:visibility="gone" android:textColor="?attr/colorAccent" style="?attr/borderlessButtonStyle"/></merge>
这里很重要,我们可以通过上面那2个ID去获取到view,然后改变view的属性,去定制我们的SnackBar。
4.运行过程
我们从最开始的show方法入手。
public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); }
其中mDuration是我们设置的时长。
private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { @Override public void show() { sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this)); } @Override public void dismiss(int event) { sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this)); } };
这个对象,用来发送消息来控制显示和取消的。(消息传递)
public void show(int duration, Callback callback) { synchronized (mLock) { 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; } else if (isNextSnackbarLocked(callback)) { // We'll just update the duration mNextSnackbar.duration = duration; } else { // Else, we need to create a new record and queue it mNextSnackbar = new SnackbarRecord(duration, callback); } // 第二部分 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(); } } }
上面的判断步骤如下:
- 如果是当前正在显示的SnackBar对应的CallBack
- 更新显示时长
- 从消息队列中移除
- scheduleTimeoutLocked()发送定时消息
- 如果是下一个要显示的
- 更新显示时长
- 都不是 就创建一个SnackbarRecord对象
上面代码中第二部分解释如下
- 如果已经有在显示的了,直接返回
- 没有的话 显示下一个。
在这里要说一下,mCurrentSnackbar 和mNextSnackbar 有点绕
- mCurrentSnackbar 当前正在显示的
- 和mNextSnackbar 下一个要显示的
要想知道Snackbar的显示和隐藏的调用过程。我们从上面的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; } } }
在这里我们能看到会调用callback的show方法,而这个calllback对象就是我们在调用snackbar的show方法是传进去的那个。
在callback的show方法中,发送了一个MSG_SHOW的消息。那么我们就跟踪下这里的Hanlder了。
static { sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_SHOW: ((Snackbar) message.obj).showView(); return true; case MSG_DISMISS: ((Snackbar) message.obj).hideView(message.arg1); return true; } return false; } }); }
- 看到这里简单粗暴的调用了showview方法。这个showview方法是我们想要的。由于这里代码比较场,就不贴代码了,简单的介绍下过程。
showview方法的逻辑
- 判断当前view的parent是否为null
- 为null,当我们的view 的LayoutParams instanceof CoordinatorLayout.LayoutParams 的时候,设置behavior。
- 设置OnAttachStateChangeListener 监听器
- 是否在屏幕之外
- 是,动画进入
- 不是,setOnLayoutChangeListener,在layout改变的时候动画。
到这里我们就完成了显示过程。其中很重要的一个方法为animateViewIn。这里会根据版本去选择合适的动画类型,并且,隐藏部分的逻辑也在这里,不信,你们继续看。
在animateViewIn中有下面这一句代码。
SnackbarManager.getInstance().onShown(mManagerCallback);
我们看看onShow方法干了什么。
public void onShown(Callback callback) { synchronized (mLock) { if (isCurrentSnackbarLocked(callback)) { scheduleTimeoutLocked(mCurrentSnackbar); } } }
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); }
可以很明显的看到,在上面发送了一个叫做MSG_TIMEOUT的消息,继续追终,最后会到达cancelSnackbarLocked方法。
private boolean cancelSnackbarLocked(SnackbarRecord record, int event) { final Callback callback = record.callback.get(); if (callback != null) { callback.dismiss(event); return true; } return false; }
恩,绕一大圈,终于看到了dismiss方法。那么,接下来的代码我们就不看了。
- SnackBar源码解析-了解它的工作原理
- SnackBar源码解析
- Snackbar源码解析
- Android源码解析AsyncTask的工作原理
- Android 源码解析AsyncTask的工作原理
- 【React Native】从源码一步一步解析它的实现原理
- 【React Native】从源码一步一步解析它的实现原理
- 【React Native】从源码一步一步解析它的实现原理
- 注解工作原理源码解析
- 关于Snackbar的简单了解
- 源码解析Android中AsyncTask的工作原理
- 从AsyncTask的源码解析AsyncTask工作原理
- caffe源码解析—caffe layer的工作原理理解
- 源码解析Android中AsyncTask的工作原理
- 源码解析Android中AsyncTask的工作原理
- SnackBar 源码bug修复、源码解析
- 什么是CORBA?以及它的工作原理?
- Servlet工作原理以及源码解析
- [BZOJ3309] DZY Loves Math - 莫比乌斯反演
- SPSS——数据预处理
- NOJ - 1987 集训队选拔
- 1-3 倒序输出
- java运算符中值的注意的地方
- SnackBar源码解析-了解它的工作原理
- Java POI生成Excel表文件
- NOJ - 1899 树木枝干问题
- 动态内存分配
- 让安卓的Dialog与屏幕等宽的方法
- 39. Combination Sum
- 手机安全卫士06
- NOJ - 1900 闰年
- IO model