引言
2015年5月,Google发布了Design Support Library
,添加了很多组件用于支持Material Design。至今过去已经两年了,版本也由当初的22.2.0
到现在的26.0.0 Alpha 1
。想要了解其中控件的实现原理,当然是从最简单的开始,那就是这篇文章的主角——Snackbar
。
基本使用
- 只有文本提示
Snackbar.make(view, "This is a message", Snackbar.LENGTH_LONG).show();
- 有点击按钮
Snackbar.make(view, "This is a message", Snackbar.LENGTH_LONG)
.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO do something
}
})
.show();
当然还有其他的属性及方法,具体的可参考Google官方文档。
带着问题去阅读
- Snackbar是如何添加到界面上的?
- Snackbar的显示位置如何修改?
- Snackbar的布局是否可以修改?
- 多个连续的Snackbar是如何管理显示的?
- 在CoordinatorLayout中使用FloatingActionButton和SnackBar时,为什么Snackbar不会遮挡FloatingActionButton?
源码解析
源码基于25.3.0
解读源码,应该从什么地方下手呢?当然是从我们使用SnackBar
最常用的方法下手了,第一个使用到的那就是make
方法了。
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.");
}
...
// 后面代码省略
}
SnackBar中有两个make方法,区别是提示文字传递的类型,一个是CharSequence
,一个是Resouse id
。传Resouse id
最终也会走到上述方法中。
先来看看方法内第一行代码,调用了findSuitableParent(View view)
方法,代码如下:
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;
}
代码量不大,而且注释也很清楚。此方法的作用就是循环查找view的上层ViewGroup,直到找到CoordinatorLayout或到根布局结束,返回找到的ViewGroup。
根布局:id为android.R.id.content的布局实际上就是我们setContentView设置自己写的布局的父ViewGroup,类型是FrameLayout,具体的可以去了解DecorView。
再回过头来看看Snackbar的make方法:
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
....//前面代码省略
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;
}
通过inflate获取到SnackBarContentLayout布局,SnackBarContentLayout实际上是一个LinearLayout,再来看看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