//Snackbar 源码分析:
//
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
//找到一个合适的parent,循环遍历父view 直到取到合适的view
final ViewGroup parent = findSuitableParent(view);
if (parent == null) {
throw new IllegalArgumentException("No suitable parent found from the given view. "
+ "Please provide a valid view.");
}
//如果传进来的view为null 抛出异常
//
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
//R.layout.design_layout_snackbar_include 应该为Snackbar 的布局
//将 Snackbar布局初始化关联到 上面找到的一个合适的parent
// LayoutInflater.inflate 三个参数介绍的博客:https://blog.csdn.net/u012702547/article/details/52628453
// inflate 三个参数说明 第一个要加载的xml 布局
// 第二个参数 要依赖的父view
// 是否添加到父View 中 如果为true 那么这个方法返回的是parent 的布局并
//且将需要加载的布局添加到parent中,如果为false 返回的则是 需要加载布局的view
//
// 不明白这种填充布局的方式,为什么能直接转换为自定义的SnackbarContentLayout
// 而SnackbarContentLayout类文件中并未发现有关联到 R.layout.design_layout_snackbar_include 布局的代码 ???
final SnackbarContentLayout content =
(SnackbarContentLayout) inflater.inflate(
R.layout.design_layout_snackbar_include, parent, false);
//创建Snackbar
final Snackbar snackbar = new Snackbar(parent, content, content);
snackbar.setText(text); //设置Snackbar文字
snackbar.setDuration(duration); //设置Snackbar时长
return snackbar;
}
//找到合适的父View
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
do {
if (view instanceof CoordinatorLayout) {
//如果此View 是 CoordinatorLayout 转换为ViewGroup返回
// We've found a CoordinatorLayout, use it
return (ViewGroup) view;
} else if (view instanceof FrameLayout) { // 如果传入的view是 FrameLayout
if (view.getId() == android.R.id.content) {
//如果当前是 点击的是 DecorView 中的ContentView 转换为ViewGroup返回
// 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; // 直接转换为ViewGroup返回
}
}
if (view != null) {
//如果当前view不为null ,获取父view 判断父view 是否是View如果是 则进行下一次循环
//如果 不是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 snackbar = new Snackbar(parent, content, content); Snackbar的构造方法
//首先调用父类的构造方法
private Snackbar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) {
super(parent, content, contentViewCallback);
}
//Snackbar 继承于BaseTransientBottomBar
//父类的构造方法
/**
* Constructor for the transient bottom bar.
*
* @param parent The parent for this transient bottom bar.
* @param content The content view for this transient bottom bar.
* @param contentViewCallback The content view callback for this transient bottom bar.
*/
protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content,
@NonNull ContentViewCallback contentViewCallback) {
if (parent == null) {
throw new IllegalArgumentException("Transient bottom bar must have non-null parent");
}
if (content == null) {
throw new IllegalArgumentException("Transient bottom bar must have non-null content");
}
if (contentViewCallback == null) {
throw new IllegalArgumentException("Transient bottom bar must have non-null callback");
}
//将 获取到的父View parent 赋值给 mTargetParent,此时父View parent 并未包含 Snackbar 的View
mTargetParent = parent;
//SnackbarContentLayout 实现 ContentViewCallback 接口 有两个方法 进入进出动画
mContentViewCallback = contentViewCallback;
mContext = parent.getContext();
ThemeUtils.checkAppCompatTheme(mContext);
LayoutInflater inflater = LayoutInflater.from(mContext);
// Note that for backwards compatibility reasons we inflate a layout that is defined
// in the extending Snackbar class. This is to prevent breakage of apps that have custom
// coordinator layout behaviors that depend on that layout.
mView = (SnackbarBaseLayout) inflater.inflate(
R.layout.design_layout_snackbar, mTargetParent, false);
mView.addView(content);
ViewCompat.setAccessibilityLiveRegion(mView,
ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
ViewCompat.setImportantForAccessibility(mView,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
// Make sure that we fit system windows and have a listener to apply any insets
ViewCompat.setFitsSystemWindows(mView, true);
ViewCompat.setOnApplyWindowInsetsListener(mView,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
// Copy over the bottom inset as padding so that we're displayed
// above the navigation bar
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight(), insets.getSystemWindowInsetBottom());
return insets;
}
});
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
// SnackbarContentLayout 代码, 继承 LinearLayout
public class SnackbarContentLayout extends LinearLayout implements
BaseTransientBottomBar.ContentViewCallback {
private TextView mMessageView; // Snackbar 的文字
private Button mActionView; // Snackbar 的按钮
private int mMaxWidth;
private int mMaxInlineActionWidth;
public SnackbarContentLayout(Context context) {
this(context, null);
}
public SnackbarContentLayout(Context context, AttributeSet attrs) {
super(context, attrs);
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);
a.recycle();
}
// Finalize inflating a view from XML. This is called as the last phase of inflation, after all child views have //been added.
//
///Even if the subclass overrides onFinishInflate, they should always be sure to call the super method, so that we //get called.
///即使子类覆盖了onFinishInflate,也应该总是调用super方法,这样我们就可以被调用了
// xml 被inflate 结束之后调用的方法
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMessageView = findViewById(R.id.snackbar_text);
mActionView = findViewById(R.id.snackbar_action);
}
public TextView getMessageView() {
return mMessageView;
}
public Button getActionView() {
return mActionView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//如果文字宽度大于设置的最大宽度 需要重新测量View
if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
final int multiLineVPadding = getResources().getDimensionPixelSize(
R.dimen.design_snackbar_padding_vertical_2lines);
final int singleLineVPadding = getResources().getDimensionPixelSize(
R.dimen.design_snackbar_padding_vertical);
//判断文本行数是否大于1行
final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1;
boolean remeasure = false;
if (isMultiLine && mMaxInlineActionWidth > 0
&& mActionView.getMeasuredWidth() > mMaxInlineActionWidth) {
if (updateViewsWithinLayout(VERTICAL, multiLineVPadding,
multiLineVPadding - singleLineVPadding)) {
remeasure = true;
}
} else {
final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding;
if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) {
remeasure = true;
}
}
if (remeasure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private boolean updateViewsWithinLayout(final int orientation,
final int messagePadTop, final int messagePadBottom) {
boolean changed = false;
if (orientation != getOrientation()) {
setOrientation(orientation);
changed = true;
}
if (mMessageView.getPaddingTop() != messagePadTop
|| mMessageView.getPaddingBottom() != messagePadBottom) {
updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom);
changed = true;
}
return changed;
}
private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) {
if (ViewCompat.isPaddingRelative(view)) {
ViewCompat.setPaddingRelative(view,
ViewCompat.getPaddingStart(view), topPadding,
ViewCompat.getPaddingEnd(view), bottomPadding);
} else {
view.setPadding(view.getPaddingLeft(), topPadding,
view.getPaddingRight(), bottomPadding);
}
}
@Override
public void animateContentIn(int delay, int duration) {
mMessageView.setAlpha(0f);
mMessageView.animate().alpha(1f).setDuration(duration)
.setStartDelay(delay).start();
if (mActionView.getVisibility() == VISIBLE) {
mActionView.setAlpha(0f);
mActionView.animate().alpha(1f).setDuration(duration)
.setStartDelay(delay).start();
}
}
@Override
public void animateContentOut(int delay, int duration) {
mMessageView.setAlpha(1f);
mMessageView.animate().alpha(0f).setDuration(duration)
.setStartDelay(delay).start();
if (mActionView.getVisibility() == VISIBLE) {
mActionView.setAlpha(1f);
mActionView.animate().alpha(0f).setDuration(duration)
.setStartDelay(delay).start();
}
}
}