<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="trs.com.fabdemo.MainActivity">
<android.support.design.widget.FloatingActionButton
android:layout_gravity="bottom|right"
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_launcher" />
FrameLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v,"Test",Snackbar.LENGTH_LONG).show();
}
});
}
}
只有xml不同,因此只贴出xml
"1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="trs.com.fabdemo.MainActivity">
.support.design.widget.FloatingActionButton
android:layout_gravity="bottom|right"
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_launcher" />
.support.design.widget.CoordinatorLayout>
在CoordinatorLayout中使用的时候我们发现,SnackBar显示的时候,FloatingActionButton会自动上升,而SnackBar可以滑动删除。为什么会这样呢,首先看看我们SnackBar是怎么创建的。通过调用Snackbar.make方法,这个方法需要三个参数,一个view,一个标题,和显示时长。
Snackbar.make(v,"Test",Snackbar.LENGTH_LONG)
对于标题和显示时长我们能理解,但是为什么要需要一个View呢,这其实就是关键。看看make方法的实现。
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;
}
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
//遍历视图树
do {
if (view instanceof CoordinatorLayout) {
//判断,如果是CoordinatorLayout则直接返回。
return (ViewGroup) view;
} else if (view instanceof FrameLayout) {
if (view.getId() == android.R.id.content) {
//如果查找到最顶级的View,还没有找到CoordinatorLayout,那么我们就使用顶级view及decor,也就是Activity最上层的FrameLayout
return (ViewGroup) view;
} else {
fallback = (ViewGroup) view;
}
}
if (view != null) {
//继续循环遍历,父View
final ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
} while (view != null);
return fallback;
}
会什么要寻找CoordinatorLayout呢,因为SnackBar的滑动删除就是通过CoordinatorLayout.Behavior实现的,我原来说过Behavior的设置可以通过xml,注解,代码三种方式设置,查看SnackBar的申明。发现其不是一个View,所以不能直接添加到CoordinatorLayout中。也就不能通过注解,和xml设置。
接着我们看看Snackbar的显示,通过调用show()方法,我们展示了snackbar
Snackbar.make(v,"Test",Snackbar.LENGTH_LONG).show();
让我们看看show方法具体做了什么事情
/**
* Show the {@link Snackbar}.
*/
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
很简单的调用了SnackbarManager的show方法
public void show(int duration, Callback callback) {
//省略一些判断,最终调用此方法
...
showNextSnackbarLocked();
...
}
private void showNextSnackbarLocked() {
if (mNextSnackbar != null) {
mCurrentSnackbar = mNextSnackbar;
mNextSnackbar = null;
//获取当前Snackbar的callback
final Callback callback = mCurrentSnackbar.callback.get();
if (callback != null) {
//调用callback的show方法
callback.show();
} else {
// The callback doesn't exist any more, clear out the Snackbar
mCurrentSnackbar = null;
}
}
}
查看callback发现其是一个接口
interface Callback {
void show();
void dismiss(int event);
}
它的实现如下
private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
@Override
public void show() {
//使用handler发送一个显示的消息。
sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
}
@Override
public void dismiss(int event) {
sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));
}
};
看看Handler的实现发现最终会调用此方法
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
//如果我们的LayoutParams 是CoordinatorLayout.LayoutParams,就设置我们自己的Behavior,
//而LayoutParams是在构造方法时通过,
//mView=(SnackbarLayout)inflater.inflate(R.layout.design_layout_snackbar, mTargetParent, false);生成的。
final Behavior behavior = new Behavior();
behavior.setStartAlphaSwipeDistance(0.1f);
behavior.setEndAlphaSwipeDistance(0.6f);
behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
}
@Override
public void onDragStateChanged(int state) {
switch (state) {
case SwipeDismissBehavior.STATE_DRAGGING:
case SwipeDismissBehavior.STATE_SETTLING:
// If the view is being dragged or settling, cancel the timeout
SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
break;
case SwipeDismissBehavior.STATE_IDLE:
// If the view has been released and is idle, restore the timeout
SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
break;
}
}
});
((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
}
mTargetParent.addView(mView);
}
查看Behavior,发现继承自SwipeDismissBehavior,关于SwipeDismissBehavior请看之前的一遍博客
final class Behavior extends SwipeDismissBehavior<SnackbarLayout>
自此,我们明白的为什么在不同布局中会有不同的效果,关于FloatingActionButton为什么会上升,我可以简单的介绍一下。查看FloatingActionButton的声明。
发现它通过注解设置了自己的默认Behavior。查看此Behavior发现有如下代码。
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
// 监听所有的Snackbar
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
//如果是SnackbarLayout变化,则根据Snackbar更新自己的Translation
updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
// If we're depending on an AppBarLayout we will show/hide it automatically
// if the FAB is anchored to the AppBarLayout
//如果是AppBarlayout变化,则实现自动显示和隐藏
updateFabVisibility(parent, (AppBarLayout) dependency, child);
}
return false;
}
自此,SnackBar的分析就告一段落了,目前源码还有一些值得分析的地方,以后会继续更新的。谢谢大家的支持。