1.Snackbar
Snackbar是Material Design中的一个控件,用来代替Toast。Snackbar是一个类似Toast的快速弹出消息提示的控件。Snackbar在显示上比Toast丰富,而且提供了用户交互的接口。
①默认情况下,Snackbar显示在屏幕底部,它出现在屏幕所有元素之上,且同时最多只能显示一个Snackbar。
②Snackbar与某些视图相关联,并且仅当视图在屏幕上时才会显示Snackbar。
③Snackbar出现时不会阻碍用户在屏幕上的输入。Snackbar可以自定义时长。
④当Snackbar在CoordinatorLayout下使用时,支持右滑删除功能。
2.Snackbar的用法
Snackbar的用法很简单,不需要在xml中写布局,像Toast一样直接在代码里使用即可。
首先需要添加依赖:
implementation "com.google.android.material:$latest_version"
然后就可以在代码中使用了:
①最基本的用法
Snackbar.make(view, "Show some message here",Snackbar.LENGTH_LONG)
.setAction("Action", v1 -> {
Log.e(TAG, "点击了确定按钮");
}).show();
注意:Snackbar不支持设置多个action,如果设置多个action,只有最后一个生效。
②设置颜色
Snackbar.make(view, "Show some message here", Snackbar.LENGTH_SHORT)
.setBackgroundTint(ContextCompat.getColor(this, R.color.baseCyan))
.setActionTextColor(ContextCompat.getColor(this, R.color.white))
.setTextColor(ContextCompat.getColor(this,R.color.black))
.setAction("Action") {
Log.e(TAG, "点击了确定按钮");
}
.show()
③添加回调
addCallback()用于给snackbar添加回调,回调Snackbar弹出和关闭动作。
Snackbar.make(view, "Show some message here", Snackbar.LENGTH_SHORT)
.addCallback(new Snackbar.Callback() {
public void onShown(Snackbar sb) {
super.onShown(sb)
Log.d(TAG, "onShown")
}
public void onDismissed( transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
Log.d(TAG, "onDismissed")
}
}).show();
④在文本前面添加图片
Snackbar snackbar = Snackbar.make(view, "这是一个snackbar", Snackbar.LENGTH_SHORT);
snackbar.setAction("取消", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
TextView textView = snackbar.getView().findV iewById(R.id.snackbar_text);
Drawable drawable = getResources().getDrawa ble(R.mipmap.ic_launcher_round);
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
textView.setCompoundDrawables(drawable, null, null, null);
//增加文字和图标的距离
textView.setCompoundDrawablePadding(20);
textView.setGravity(Gravity.CENTER);
snackbar.show();
⑤自定义布局
自定义布局的步骤:
1)通过Snackbar.getView获取到view;
1)通过LayoutInflater去加载布局得到自定义的布局view;
3)通过①中获取到的view添加②中加载好的布局view;
4)通过①中得到的自定义布局获取里面的控件去执行一些操作,比如点击事件,设置文字和文字颜色等。
View rootView = getWindow().getDecorView();
View coordinatorLayout = rootView.findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make( coordinatorLayout, "", Snackbar.LENGTH_SHORT);
// 获取到Snackbar.getView获取的Snackbar的view
Snackbar.SnackbarLayout snackbarView = (Snackbar.SnackbarLayout) snackbar.getView();
// 加载自定义布局
View inflate = LayoutInflater.from( snackbar.getView().getContext()).inflate(R.layout.snacbar_layout, null);
// 获取自定义布局中的控件
TextView text = inflate.findViewById( R.id.textView);
text.setText("自定义布局的Snackbar");
ImageView imageView = inflate.findViewById( R.id.imageView);
imageView.setOnClickListener(v1 -> Log.d("TAG", "点击了自定义布局中的控件"));
// 将自定义布局view添加到SnackbarView中
snackbarView.addView(inflate);
snackbar.show();
⑥修改Snackbar的位置
自定义位置的步骤:
1)获取到SnackbarView的LayoutParams;
2)通过①中获取到的LayoutParams创建新的LayoutParams;
③给②中的LayoutParams设置Gravity;
④将新的LayoutParams设置给SnackbarView。
View rootView = getWindow().getDecorView();
View coordinatorLayout = rootView.findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make(coordinatorLayout, "", Snackbar.LENGTH_SHORT);
// 设置SnackbarView的padding都为0,避免上图中出现黑色边框背景的情况
snackbar.getView().setPadding(0,0,0,0);
// 将SnackbarView的背景颜色设置为透明,避免在自定义布局中有圆角或者自适应宽度的时候显示一块黑色背景的情况
snackbar.getView().setBackgroundColor(Color.TRANSPARENT);
// 获取到Snackbar.getView获取的Snackbar的view
Snackbar.SnackbarLayout snackbarView = (Snackbar.SnackbarLayout) snackbar.getView();
// 获取到SnackbarView的LayoutParams
ViewGroup.LayoutParams layoutParams = snackbarView.getLayoutParams();
// 新建一个LayoutParams将SnackbarView的LayoutParams的宽高传入
FrameLayout.LayoutParams fl = new FrameLayout.LayoutParams(layoutParams.width, layoutParams.height);
// 设置新的元素位置
fl.gravity = Gravity.CENTER;
// 将新的LayoutParams设置给SnackbarView
snackbarView.setLayoutParams(fl);
// 自定义的布局
View inflate = LayoutInflater.from( snackbar.getView().getContext()).inflate(R.layout.snacbar_layout, null);
TextView text = inflate.findViewById( R.id.textView);
text.setText("自定义布局的Snackbar");
ImageView imageView = inflate.findViewById(R.id.imageView);
imageView.setOnClickListener(v1 -> Log.d("TAG", "点击了自定义布局中的控件"));
snackbarView.addView(inflate);
snackbar.show();
3.Snackbar源码
①Snackbar使用静态方法make()创建实例
public static Snackbar make(View view, int resId, int duration) {
return make(view, view.getResources().getText(resId), duration);
}
public static Snackbar make(View view, CharSequence text, int duration) {
ViewGroup parent = findSuitableParent( view);
if (parent == null) {
throw new IllegalArgumentException("No suitable parent found from the given view. Please provide a valid view.");
}
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;
}
②findSuitableParent()
创建Snackbar实例,需要寻找合适的父视图,优先选择CoordinatorLayout作为父视图。
private static ViewGroup findSuitableParent( View view) {
ViewGroup fallback = null;
do {
if (view instanceof CoordinatorLayout) {
return (ViewGroup) view;
} else if (view instanceof FrameLayout) {
if(view.getId() == android.R.id.content) {
return (ViewGroup) view;
} else {
fallback = (ViewGroup) view;
}
}
if (view != null) {
final ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
} while (view != null);
return fallback;
}
③SnackbarContentLayout
SnackbarContentLayout继承LinearLayout,并实现了BaseTransientBottomBar.ContentViewC allback,包含一个TextView和Button。
design_layout_snackbar_include.xml文件:
<view xmlns:android="http://schemas.andro id.com/apk/res/android"
class="android.support.design.internal.Sna ckbarContentLayout"
android:theme="@style/ThemeOverlay.Ap pCompat.Dark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<TextView
android:id="@+id/snackbar_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="@dimen/design_sn ackbar_padding_vertical"
android:paddingBottom="@dimen/desig n_snackbar_padding_vertical"
android:paddingLeft="@dimen/design_sn ackbar_padding_horizontal"
android:paddingRight="@dimen/design_s nackbar_padding_horizontal"
android:textAppearance="@style/TextAp pearance.Design.Snackbar.Message"
android:maxLines="@integer/design_sna ckbar_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/de sign_snackbar_extra_spacing_horizontal"
android:layout_marginStart="@dimen/de sign_snackbar_extra_spacing_horizontal"
android:layout_gravity="center_vertical| right|end"
android:minWidth="48dp"
android:visibility="gone"
android:textColor="?attr/colorAccent"
style="?attr/borderlessButtonStyle"/>
</view>
④SnackbarManager类
SnackbarManager用来管理Snackbar控件的状态。
Snackbar的show()方法会调用SnackbarManager的show(int, Callback)方法,而mManagerCallback会回调Snackbar的showView()和hideView(int)方法。
static {
sHandler = new Handler( Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case MSG_SHOW:
((BaseTransientBottomBar) message.obj).showView();
return true;
case MSG_DISMISS:
((BaseTransientBottomBar) message.obj).hideView(message.arg1);
return true;
}
return false;
}
});
}
final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
@Override
public void show() {
sHandler.sendMessage( sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this));
}
@Override
public void dismiss(int event) {
sHandler.sendMessage( sHandler.obtainMessage(MSG_DISMISS, event, 0, BaseTransientBottomBar.this));
}
};
public void show() {
SnackbarManager.getInstance().show( mDuration, mManagerCallback);
}
SnackbarManager内部包含两个记录mCurrentSnackbar和mNextSnackbar。在SnackbarManager的show(int, Callback)方法中,①查看是否是当前Snackbar,如果是,更新超时时间,结束。②查看是否是NextSnackbar,如果是,更新数据,如果不是创建新的NextSnackbar。③取消当前Snackbar或者显示NextSnackbar。
⑤show(int, Callback)方法
public void show(int duration, Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) {
// 如果是当前Snackbar,更新duration和超时提示
mCurrentSnackbar.duration = duration;
mHandler.removeCallbacksAndMessag es( mCurrentSnackbar);
scheduleTimeoutLocked( mCurrentSnackbar);
return;
} else if (isNextSnackbarLocked(callback)){
// 如果是NextSnackbar,更新duration
mNextSnackbar.duration = duration;
} else {
// 否则就创建新的NextSnackbar
mNextSnackbar = new SnackbarRecord(duration, callback);
}
if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
// 如果当前Snackbar存在,取消显示当前Snackbar
return;
} else {
mCurrentSnackbar = null;
// 如果当前Snackbar不存在,显示NextSnackbar
showNextSnackbarLocked();
}
}
}
// 取消显示当前Snackbar,调用callback的dismiss(DISMISS_EVENT_CONSECUTIVE)方法
private boolean cancelSnackbarLocked( SnackbarRecord record, int event) {
final Callback callback = record.callback.get();
if (callback != null) {
mHandler.removeCallbacksAndMessages( record);
callback.dismiss(event);
return true;
}
return false;
}
private boolean isCurrentSnackbarLocked( Callback callback) {
return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback);
}
private boolean isNextSnackbarLocked( Callback callback) {
return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback);
}
// 更新超时提示
private void scheduleTimeoutLocked( SnackbarRecord r) {
if (r.duration == Snackbar.LENGTH_INDEFINITE) {
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);
}
// 显示NextSnackbar,调用callback的show方法
private void showNextSnackbarLocked() {
if (mNextSnackbar != null) {
mCurrentSnackbar = mNextSnackbar;
mNextSnackbar = null;
final Callback callback = mCurrentSnackbar.callback.get();
if (callback != null) {
callback.show();
} else {
mCurrentSnackbar = null;
}
}
}
Snackbar的showView()会调用onViewShown(),hideView(int)会调用onViewHidden(int):
final void showView() {
... ...
if (shouldAnimate()) {
// If animations are enabled, animate it in
animateViewIn();
} else {
// Else if anims are disabled just call back now
onViewShown();
}
... ...
}
final void hideView(final int event) {
if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) {
animateViewOut(event);
} else {
// If anims are disabled or the view isn't visible, just call back now
onViewHidden(event);
}
}
void onViewShown() {
SnackbarManager.getInstance().onShown( mManagerCallback);
}
void onViewHidden(int event) {
SnackbarManager.getInstance().onDismi ssed(mManagerCallback);
}
SnackbarManager的onShown(Callback)和onDismissed(Callback)方法:
public void onShown(Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) {
scheduleTimeoutLocked( mCurrentSnackbar);
}
}
}
public void onDismissed(Callback callback) {
synchronized (mLock) {
if (isCurrentSnackbarLocked(callback)) {
// If the callback is from a Snackbar currently show, remove it and show a new one
mCurrentSnackbar = null;
if (mNextSnackbar != null) {
showNextSnackbarLocked();
}
}
}
}