Android Snackbar

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();

}

}

}

}

相关推荐
阿巴斯甜8 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker9 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952710 小时前
Andorid Google 登录接入文档
android
黄林晴11 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android