Android Snackbar实现Toast效果和顶部弹窗提醒效果

1,046 阅读4分钟

Android Snackbar实现Toast效果和顶部弹窗提醒效果

Snackbar的make方法自定义View包含两种CoordinatorLayout or the window decor's content view

/**
 * Make a Snackbar to display a message
 *
 * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given to
 * {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, which is
 * defined as a {@link CoordinatorLayout} or the window decor's content view, whichever comes
 * first.
 *
 * <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable certain
 * features, such as swipe-to-dismiss and automatically moving of widgets.
 *
 * @param view The view to find a parent from. This view is also used to find the anchor view when
 *     calling {@link Snackbar#setAnchorView(int)}.
 * @param text The text to show. Can be formatted text.
 * @param duration How long to display the message. Can be {@link #LENGTH_SHORT}, {@link
 *     #LENGTH_LONG}, {@link #LENGTH_INDEFINITE}, or a custom duration in milliseconds.
 */
@NonNull
public static Snackbar make(
    @NonNull View view, @NonNull CharSequence text, @Duration int duration) {
  return makeInternal(/* context= */ null, view, text, duration);
}

系统布局文件代码中关键代码和具体布局文件如下

/**
 * Makes a Snackbar using the given context if non-null, otherwise uses the parent view context.
 */
@NonNull
private static Snackbar makeInternal(
    @Nullable Context context,
    @NonNull View view,
    @NonNull CharSequence text,
    @Duration int duration) {
  final ViewGroup parent = findSuitableParent(view);
  if (parent == null) {
    throw new IllegalArgumentException(
        "No suitable parent found from the given view. Please provide a valid view.");
  }

  if (context == null) {
    context = parent.getContext();
  }

  final LayoutInflater inflater = LayoutInflater.from(context);
  final SnackbarContentLayout content =
      (SnackbarContentLayout)
          inflater.inflate(
              hasSnackbarContentStyleAttrs(context)
                  ? R.layout.mtrl_layout_snackbar_include
                  : R.layout.design_layout_snackbar_include,
              parent,
              false);
  final Snackbar snackbar = new Snackbar(context, parent, content, content);
  snackbar.setText(text);
  snackbar.setDuration(duration);
  return snackbar;
}

布局文件有两个,找其中任何一个布局如design_layout_snackbar_include打开,关注两个点id和父布局SnackbarContentLayout


<view
    xmlns:android="http://schemas.android.com/apk/res/android"
    class="com.google.android.material.snackbar.SnackbarContentLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

  <TextView
      android:id="@+id/snackbar_text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:layout_gravity="center_vertical|left|start"
      android:paddingTop="@dimen/design_snackbar_padding_vertical"
      android:paddingBottom="@dimen/design_snackbar_padding_vertical"
      android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
      android:paddingRight="@dimen/design_snackbar_padding_horizontal"
      android:ellipsize="end"
      android:maxLines="@integer/design_snackbar_text_max_lines"
      android:textAlignment="viewStart"
      android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"/>

  <Button
      android:id="@+id/snackbar_action"
      style="?attr/borderlessButtonStyle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
      android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"
      android:layout_gravity="center_vertical|right|end"
      android:minWidth="48dp"
      android:textColor="?attr/colorAccent"
      android:visibility="gone"/>

</view>

Snackbar文件中make中自定义view的根布局SnackbarLayout

private SnackbarContentLayout getContentLayout() {
  return (SnackbarContentLayout) view.getChildAt(0);
}
@NonNull protected final SnackbarBaseLayout view;
public static final class SnackbarLayout extends BaseTransientBottomBar.SnackbarBaseLayout

Snackbar的版本用的是1.8.0,之前用的1.6.0 报异常Attempt to invoke virtual method 'int android.text.Layout.getLineCount()'

implementation("com.google.android.material:material:1.8.0")

Java代码具体实现自定义Toast

布局文件customer_snackbar_toast.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:backgroundTint="#333333"
    android:minHeight="@dimen/dp_50"
    app:cardCornerRadius="@dimen/dp_24"
    app:contentPadding="0dp">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/iv_snack_icon"
        android:layout_width="@dimen/dp_18"
        android:layout_height="@dimen/dp_18"
        android:layout_marginLeft="@dimen/dp_12"
        android:layout_marginTop="@dimen/dp_16"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/icon_avatar"/>
    <!--    app:srcCompat="@mipmap/ic_app_notice"-->
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_snack_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/dp_40"
        android:layout_marginTop="@dimen/dp_12"
        android:layout_marginRight="@dimen/dp_12"
        android:layout_marginBottom="@dimen/dp_12"
        android:includeFontPadding="false"
        android:textColor="@color/white"
        android:textSize="@dimen/text_15"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/iv_snack_icon"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="哈哈哈哈哈哈哈哈哈哈" />
</androidx.cardview.widget.CardView>

代码封装,系统id R.id.snackbar_text出处android.support.design.R.id.snackbar_text或者com.google.android.material.R.id.snackbar_text,getViewTreeObserver()是测量报异常后加的如没问题可以不加,隐藏原有布局样式,添加自定义布局到Activity的android.R.id.content父布局中,好处是不用在每个布局里埋样式,左侧icon从application获取应用app icon

  public static Snackbar toast(AppCompatActivity activity, String message, int duration) {
        ViewGroup rootView = activity.findViewById(android.R.id.content);
        Snackbar mSnackbar =
                Snackbar.make(rootView, "Replace with your own action", duration);
        Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) mSnackbar.getView();
        layout.setBackgroundColor(Color.TRANSPARENT);
        layout.setElevation(0);
        layout.setPadding(0, 0, 0, 0);
        TextView textView =
                layout.findViewById(R.id.snackbar_text);
        textView.setVisibility(View.GONE);

        View view = LayoutInflater.from(activity).inflate(R.layout.customer_snackbar_toast, layout);
        AppCompatImageView ivIcon = view.findViewById(R.id.iv_snack_icon);
        ivIcon.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ivIcon.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//                try {
                    ivIcon.setImageResource(activity.getApplicationInfo().icon);
//                    ivIcon.setImageDrawable( activity.getPackageManager().getApplicationIcon(activity.getPackageName()));
//                } catch (PackageManager.NameNotFoundException e) {
//                    throw new RuntimeException(e);
//                }
            }
        });
        AppCompatTextView tvMessage = view.findViewById(com.platform.view.R.id.tv_snack_message);
        tvMessage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                tvMessage.setText(message);
                tvMessage.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            }
        });
        FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) view.getLayoutParams();
        param.gravity = Gravity.CENTER;
        mSnackbar.setBackgroundTint(Color.TRANSPARENT);
        return mSnackbar;
    }

效果仿照Android弹框样式

toast.png

Kotlin代码具体实现自定义顶部提示框

布局样式

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="top"
    android:background="#00FFFFFF"
    android:minHeight="120dp">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/iv_icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/icon_warning_shield" />


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="12dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="12dp"
        android:includeFontPadding="false"
        android:text="权限使用说明"
        android:textColor="@color/black"
        android:textSize="17sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="@id/iv_icon"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_desc"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="12dp"
        android:layout_marginTop="4dp"
        android:layout_marginRight="12dp"
        android:layout_marginBottom="12dp"
        android:text="您的应用在运行时,未同步告知权限申请的使用目的,向用户索取(相机、存储)等权限,不符合华为应用市场审核标准。"
        android:textColor="#4d4d4d"
        android:textSize="15sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/iv_icon"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

封装代码

private fun snackHint(activity: AppCompatActivity): Snackbar {
    var rootView = activity.findViewById<ViewGroup>(android.R.id.content)
    var mSnackbar =
        Snackbar.make(rootView, "Replace with your own action", Snackbar.LENGTH_INDEFINITE)
    val layout = mSnackbar.view as Snackbar.SnackbarLayout
    layout.setPadding(0, 0, 0, 0)
    val textView =
        layout.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
    textView.visibility = View.INVISIBLE
    var view = LayoutInflater.from(activity).inflate(R.layout.customer_snackview, layout)
    var param = view.layoutParams as FrameLayout.LayoutParams
    param.gravity = Gravity.TOP
    mSnackbar.setBackgroundTint(Color.WHITE)
    return mSnackbar
}

Screen_recording_20231125_100315 00_00_00-00_00_30.gif