Snackbar是类似与Toast的一种信息提示控件,但是与Toast不同的是Snackbar是从界面底部弹出的且支持一个点击事件,默认情况下Snackbar内部有两个子控件分别是TextView和Button,两者水平排列,TextView用于显示信息,Button用于实现点击事件。

snackbar0

1. Snackbar使用

Snackbar属于Material组件中的一种,如果你的应用使用了Material Theme以及AppCompatActivity,则Snackbar会获得圆角、四周有margin空隙的效果。

默认情况下使用Snackbar,调用的方式也非常类似Toast

// view是当前页面内的某个view,根据源码可知,make方法会找view的父view,
// 直到父view是FrameLayout或者CoordinateLayout,然后将其作为root给inflate方法调用
// inflate会加载默认的布局文件,这里根据是否使用Material Theme会加载不同的布局文件,
// 即上文我提到的效果,与此同时会将text的内容赋给布局文件中的TextView,
// 如果父view是CoordinateLayout,则Snackbar还支持右滑取消的功能,
// Snackbar.LENGTH_SHORT就类似于Toast.LENGTH_SHORT用于控制Snackbar的持续时间
Snackbar snackbar = Snackbar.make(view, text, Snackbar.LENGTH_SHORT);
snackbar.show();

2. Snackbar自定义Content

显然原生的Snackbar没有提供setContentView的方法,为了能够自定义Snackbar的布局,我们需要对Snackbar的一些参数进行修改,比如如果我们需要自定义Snackbar的margin以及自定义Snackbar的内容

public class SnackbarUtil {

    private int duration;
    private View anchor;
    private ViewGroup customView;
    private int sideMargin;
    private int bottomMargin;
    private Snackbar delegete;

    /**
     * SnackbarUtil is used to create Snackbar, if you setEnableCustom(false) or in default
     * you will get the origin Snackbar from Snackbar.make(anchor, text, duration); if you
     * setEnableCustom(true) in Builder, you must add your defined customView and you can
     * modify the margin of the customView in Snackbar.
     *
     * Your customView's layout should container 2 layer of Layout, because there is some
     * UI bug if you just only use 1 layer
     *
     * <?xml version="1.0" encoding="utf-8"?>
     * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     * android:layout_width="match_parent"
     * android:layout_height="match_parent">
     *
     *      <LinearLayout
     *      android:layout_width="match_parent"
     *      android:layout_height="wrap_content"
     *      android:background="@drawable/radius_background"
     *      android:orientation="vertical">
     *
     *          <TextView
     *          android:layout_width="wrap_content"
     *          android:layout_height="wrap_content"
     *          android:layout_gravity="center"
     *          android:padding="8dp"
     *          android:id="@+id/textView"
     *          android:text="hahahahh"
     *          android:textColor="@color/colorPrimaryDark"
     *          android:textSize="24sp" />
     *      </LinearLayout>
     * </LinearLayout>
     *
     * Usage:
     *
     * snackView is your customView, layout_snackbar is the layout above.
     * button is the anchor view, if your button is in CoordinatorLayout
     * the snackbar can be dismissed with swipe action.
     *
     * ViewGroup snackView = (ViewGroup) LayoutInflater.from(MainActivity.this).inflate(
     *                                      R.layout.layout_snackbar,
     *                                      new LinearLayout(MainActivity.this),
     *                                      false);
     *
     * TextView textView = snackView.findViewById(R.id.textView);
     * textView.setOnClickListener(new View.OnClickListener() {
     *
     * @param duration     Snackbar duration, default Snackbar.LENGTH_SHORT;
     * @param anchor       must need, with anchor the Snackbar will find its root;
     * @param customView   must need if setEnableCustom(true) in Builder;
     * @param sideMargin   customView left and right margin in Snackbar;
     * @param bottomMargin customView bottom margin in Snackbar;
     * @Override public void onClick(View v) {
     * Toast.makeText(MainActivity.this, "12121", Toast.LENGTH_LONG).show();
     * }
     * });
     * Snackbar snackbar = new SnackbarUtil.Builder()
     *                  .setAnchor(button)
     *                  .setBottomMargin(80)
     *                  .setDuration(Snackbar.LENGTH_SHORT)
     *                  .setText("32323")
     *                  .setCustomView(snackView)
     *                  .setSideMargin(20)
     *                  .build();
     * snackbar.show();
     */
    SnackbarUtil(int duration, View anchor, ViewGroup customView, int sideMargin, int bottomMargin) {
        this.duration = duration;
        this.anchor = anchor;
        this.customView = customView;
        this.sideMargin = sideMargin;
        this.bottomMargin = bottomMargin;
        this.delegete = Snackbar.make(anchor, "", duration);
    }

    private Snackbar create() {
        // 通过getView获取Snackbar的layout
        Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) delegete.getView();
        // 为了自定义margin,这里需要将Snackbar的背景设置为透明,textView可以设置为INVISIBLE也可以不设置
        // 只要没有在make加入text即可
        layout.setBackgroundColor(Color.TRANSPARENT);
        TextView textView = layout.findViewById(com.google.android.material.R.id.snackbar_text);
        textView.setVisibility(View.INVISIBLE);

        // customView为我们传入的自定义view,自定义view是如何调用inflate可以参考注释,
        // 但是customView必须包含两层layout这是因为UI上有bug,具体可以自行测试,
        // 所以我们实际设置的margin是第2层layout的margin,第2层layout有背景色,
        // 所以最终呈现出Snackbar有margin的效果,但是要知道实际上Snackbar的布局
        // 还是占据了整个底部空间
        View childLayout = customView.getChildAt(0);
        final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) childLayout.getLayoutParams();
        // MarginLayoutParams设置margin
        params.setMargins(params.leftMargin + sideMargin,
                params.topMargin,
                params.rightMargin + sideMargin,
                params.bottomMargin + bottomMargin);
        childLayout.setLayoutParams(params);

        // Add the view to the Snackbar's layout
        layout.addView(customView, 0);
        // Show the Snackbar
        return delegete;
    }

    // 由于参数较多,所以采用建造者模式
    public static final class Builder {
        private boolean enableCustom;
        private String text;
        private int duration;
        private View anchor;
        private ViewGroup customView;
        private int sideMargin;
        private int bottomMargin;

        Builder(boolean enableCustom) {
            this.enableCustom = enableCustom;
            this.duration = Snackbar.LENGTH_SHORT;
            this.text = "";
            this.sideMargin = 0;
            this.bottomMargin = 0;
        }

        public Builder() {
            this(false);
        }

        public Builder setEnableCustom(boolean enableCustom) {
            this.enableCustom = enableCustom;
            return this;
        }

        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        public Builder setDuration(int duration) {
            this.duration = duration;
            return this;
        }

        public Builder setAnchor(View anchor) {
            this.anchor = anchor;
            return this;
        }

        public Builder setCustomView(ViewGroup customView) {
            this.customView = customView;
            return this;
        }

        public Builder setSideMargin(int sideMargin) {
            this.sideMargin = sideMargin;
            return this;
        }

        public Builder setBottomMargin(int bottomMargin) {
            this.bottomMargin = bottomMargin;
            return this;
        }

        public Snackbar build() {
            // 通过enableCustom控制是否使用自定义Content,自定义Content的点击事件需要
            // 在Snackbar的外面处理
            if (!enableCustom) {
                return Snackbar.make(anchor, text, duration);
            }
            if (anchor == null) {
                throw new IllegalArgumentException(
                        "No suitable parent found from the given view. Please provide a valid view.");
            }
            if (customView == null) {
                throw new IllegalArgumentException(
                        "No custom view found. Please provide a valid view or setEnableCustom(false).");
            }
            return new SnackbarUtil(duration, anchor, customView, sideMargin, bottomMargin).create();
        }
    }
}

最终效果

snackbar1

参考:

  1. MATERIAL DESIGN