一.简介.
SnackBar的提出实际上是界于Toast和Dialog的中间产物。
Toast: 用户无法交互;Dialog:用户可以交互,但是体验会打折扣,会阻断用户的连贯性操作;
Snackbar既可以做到轻量级的用户提醒效果,又可以有交互的功能(是一种非必须的操作)
二.SnackBar 源码分析.
/**
* Make a Snackbar to display a message
*
* <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given
* to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent,
* which is defined as a {@link CoordinatorLayout} or the window decor's content view,
* whichever comes first.
*
* <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable
* certain features, such as swipe-to-dismiss and automatically moving of widgets like
* {@link FloatingActionButton}.
*
* @param view The view to find a parent from.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link
* #LENGTH_LONG}
*/
@NonNull
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
Snackbar snackbar = new Snackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
return snackbar;
}
这里就是说 SnackBar 会根据我们传入的View作为锚点一层一层的寻找特定View作为SnackBar的父容器 直到找到CoordinatorLayout或者the window decor's content view,做为SnackBar的父容器,代码如下:
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;
}
为SnackBar找到父容器以后创建布局:
private Snackbar(ViewGroup parent) {
mParent = parent;
mContext = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(mContext);
mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false);
}
这里
R.layout.design_layout_snackbar该布局文件应该就是我们SnackBar的布局文件,我们可以在design包的Layout文件夹中找到:
<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" />
但是发现并不是,继续找,一看就知道 这个布局是我们SnackBar的一个内部类,我们找到这个内部类,这里面我们真正的找到了我们布局文件:
public SnackbarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// Now inflate our content. We need to do this manually rather than using an <include>
// in the layout since older versions of the Android do not inflate includes with
// the correct Context.
LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);
}
查看
R.layout.design_layout_snackbar_include:
<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"
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: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>
这里面用了merge标签减少视图层级(这里涉及到merge、include、ViewStub的区别大家可以看Google的文档或者百度一大堆)如果我们要改变我们SnackBar text的颜色 你就可以在你的主题中重写该主题:
<style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance">
<item name="android:textSize">@dimen/design_snackbar_text_size</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
最后mParent.addView(mView);将SnackBar的view添加到父容器中:
final void showView() {
//...................
mParent.addView(mView);
// 动画的加载
if (ViewCompat.isLaidOut(mView)) {
// If the view is already laid out, animate it now
animateViewIn();
} else {
// Otherwise, add one of our layout change listeners and animate it in when laid out
mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom) {
animateViewIn();
mView.setOnLayoutChangeListener(null);
}
});
}
}
哪里调用了这个方法呢我们一步一步的看:
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;
}
});
再继续:
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));
}
};
这里结合SnackBar.show()方法;
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
看到这里我们可以猜想我们SnackManger作为我们的SackBar控制器通过SnackbarManager.Callback()进行通信,控制SnackBar的show 和 dismiss
那我们看看SnackBarManager里做了什么事情?
我们直接看SnackBarManger的show方法:
public void show(int duration, Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbar(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 (isNextSnackbar(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();
}
}
}
这里差不多就是Manger的心脏了,一些复杂的逻辑处理,比如你的SnackBar正在显示 显示其他 或者再次显示自己的处理 及更新,
我们传进来的cSnackBarManger.Callback 保存进了SnackbarRecord中,这个SnackBarRecord是干的呢?
private static class SnackbarRecord {
//值得学习的地方 用于优化
private final WeakReference<Callback> callback;
private int duration;
SnackbarRecord(int duration, Callback callback) {
this.callback = new WeakReference<>(callback);
this.duration = duration;
}
boolean isSnackbar(Callback callback) {
return callback != null && this.callback.get() == callback;
}
}
看到这里我们应该对SnackBarManger有了解 就是对我们的SnackBar.Callback做了管理保存在了SnackbarRecord,做了一个管理和控制
到此我们就分析完了,相信大家也大概了解了SnackBar的原理了。