为 Snackbar 添加 “取消” 按钮

版权声明:本文为博主原创文章,转载请注明出处,如有问题,欢迎指正,谢谢。 https://blog.csdn.net/qq_33404903/article/details/87934424

Material Design 是真的不错,但是其中的 Snackbar 却不流行,推出它的本意可能是取代 Toast,不过天朝没几家 app 用,只发现在微信上当收藏文章时底部提示 “已收藏   添加标签”,好像是个自定义之后的 Snackbar?!。

Snackbar 上可以有一个操作按钮,像这样

但是总觉得 Snackbar 上缺个取消按钮,或者是指定其他 action 的按钮, 既然Google不给加,那就自己加。

点卡开 Snackbar 的源码,在第 70 行有这么一行代码

SnackbarContentLayout content = (SnackbarContentLayout)inflater
.inflate(hasSnackbarButtonStyleAttr(parent.getContext()) 
? layout.mtrl_layout_snackbar_include 
: layout.design_layout_snackbar_include,
 parent, false);

两个 layout 就是 Snackbar 的布局,Snackbar 也是  inflate 出来的 View,我们点开其中的一个布局 

android.support.design.R.layout.design_layout_snackbar_include

可以看到 Snackbar 的 layout

<?xml version="1.0" encoding="utf-8"?>

<view
    xmlns:android="http://schemas.android.com/apk/res/android"
    class="android.support.design.widget.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>

那这个 SnackbarContentLayout 又是什么呢?ctrl+单击,一目了然,就是个 LinearLayout

public class SnackbarContentLayout extends LinearLayout implements ContentViewCallback {
    ...
}

当然最终呈现在屏幕上,这个 SnackbarContentLayout 还是要包裹若干层布局的。

Snackbar 上的 TextView 和 Button的横向排列的,这个 SnackbarContentLayout 的 oriention 应该是 horizontal 无疑了。

如果你想让添加到 Snackbar上的“取消”按钮样式和 Action 确定按钮一致,你可以copy button控件的放到自己新建的 snack_bar_cacel_button_layout.xml 中。或者你想添加其他控件,那这个layout就看你发挥了,最终这个 layout 也是要 inflate 成 view 并添加到 SnackbarContentLayout 中的 。

snack_bar_cacel_button_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/snackbar_action"
    style="?android:attr/borderlessButtonStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical|right|end"
    android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"
    android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
    android:paddingBottom="@dimen/design_snackbar_padding_vertical"
    android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
    android:paddingRight="@dimen/design_snackbar_padding_horizontal"
    android:paddingTop="@dimen/design_snackbar_padding_vertical"
    android:text="@string/cancel"
    android:textColor="@android:color/white" />

可以假定一个场景,当设备网络不可用时,就弹出一个 Snackbar 来提示用户网络已经断开了,时间设置为Snackbar.LENGTH_INDEFINITE 一直显示,然后默认的 Action 可以设为打开系统网络连接设置页,我们自己添加一个“取消”按钮,来 dismiss 这个Snackbar。当然也可以通过右滑 Snackbar 来使其dismiss,不过可能用户不太了解这种方式。

为了方便操作,我们来写个 SnackbarUtil 类

package com.leading.xxx.util;

import android.app.Activity;
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import com.google.android.material.snackbar.Snackbar;
import com.leading.xxx.R;


/**
 * 用来展示SnackBar提示用户的工具类
 * Created by: ZJ
 */
public class SnackBarUtil {
    private static final String TAG = "SnackBarUtil";

    private SnackBarUtil() {
    }

    public static SnackBarUtil getInstance() {
        return SnackBarUtilSingletonHolder.INSTANCE;
    }

    /**
     * 向Snackbar中添加一个取消按钮
     *
     * @param snackbar
     * @param layoutId
     * @param index    新加布局在Snackbar中的位置
     */
    public static void addCancelButtonToSnackBar(final Snackbar snackbar, int layoutId, int index) {
        View snackBarView = snackbar.getView();
        Snackbar.SnackbarLayout snackbarLayout = (Snackbar.SnackbarLayout) snackBarView;

        Button add_view = (Button) LayoutInflater.from(snackBarView.getContext()).inflate(layoutId, null);

        add_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snackbar.dismiss();
            }
        });

        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        p.gravity = Gravity.CENTER_VERTICAL;

        ((LinearLayout) snackbarLayout.getChildAt(0)).addView(add_view, index, p);
    }

    /**
     * 展示断网提示SnackBar
     *
     * @param activity 需要展示提示的activity
     * @return 断网提示snackBar, 找不到activity的根布局会返回null
     */
    public Snackbar showNoAvailableNetworkSnackBar(final Activity activity) {
        ViewGroup activityViewGroup = activity.findViewById(android.R.id.content);
        Snackbar networkStatusSnackBar = null;
        if (activityViewGroup != null) {
            networkStatusSnackBar = Snackbar.make(activityViewGroup.getChildAt(0),
                    "网络连接已断开(右滑解除)", Snackbar.LENGTH_INDEFINITE)
                    .setAction("前往设置", new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // 开启设置界面
                            activity.startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
                        }
                    });
// 你也可以自定义 snackbar 的背景色         
//networkStatusSnackBar.getView().setBackgroundColor(activity.getResources()
//.getColor(R.color.colorSnackBarBackground));
            addCancelButtonToSnackBar(networkStatusSnackBar, R.layout.snack_bar_cacel_button_layout, 2);
            networkStatusSnackBar.show();
        } else {
            Log.e(TAG, "showNoAvailableNetworkSnackBar: 没有找到显示SnackBar的 ViewGroup");
        }
        return networkStatusSnackBar;
    }

    private static class SnackBarUtilSingletonHolder {
        private static final SnackBarUtil INSTANCE = new SnackBarUtil();
    }

}

其中 

public Snackbar showNoAvailableNetworkSnackBar(final Activity activity)

方法,需要传入你需要显示这个 Snackbar 的 activity,

至于其中的

 ViewGroup activityViewGroup = activity.findViewById(android.R.id.content);
        Snackbar networkStatusSnackBar = null;
        if (activityViewGroup != null) {
            networkStatusSnackBar = Snackbar.make(activityViewGroup.getChildAt(0),
            ...

是为了找到 make snackbar 的布局,在我们的例子中,需要找到 activty 的根布局,

为什么是  activity.findViewById(android.R.id.content) 呢,又为什么是 activityViewGroup.getChildAt(0) 呢,这个就请参看这篇文章:Window、PhoneWindow、DecorView和android.R.id.content 。当然,你也可以改造一下这个方法,直接传入想要显示 Snackbar 的view或者layoutId都可以。我这样的写法可能适合 BaseActivity 中调用。

在 snackbar show之前, 我们用下面这个方法来添加取消按钮

public static void addCancelButtonToSnackBar(final Snackbar snackbar, int layoutId, int index) {
    ...
}

其中 layoutId 是你要添加的 view 的 layoutId,index 是你想要把这个 view 添加到 snackbar 的哪个位置,前面说了 Snackbar 包裹 TextView 和 Button 的 SnackbarContentLayout 本质是个方向为水平的线性布局。通过 snackbarLayout.getChildAt(0) 可以获取 SnackbarContentLayout(事实上 Snackbar.SnackbarLayout 就只有这一个 child)。

可以在监听了网络状态的 BaseActivity 中使用这个 SnackbarUtil,用来提示用户网络不可用

MainActivity.kt
package cn.net.leading.mynetworkstatesapplication

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    ...

    /**
     * 网络不可用
     */
    private fun onNetUnavailable() {
        Log.e(TAG, "onNetUnavailable")
            mTipNetworkUnavailableSnackbar = SnackBarUtil.getInstance().showNoAvailableNetworkSnackBar(this)
    }

    /**
     * 网络可用
     */
    fun onNetAvailable() {
        Log.e(TAG, "onNetAvailable")
        if (mTipNetworkUnavailableSnackbar != null && mTipNetworkUnavailableSnackbar?.isShown!!) {
            mTipNetworkUnavailableSnackbar?.dismiss()
        }
    }

    ...
}

下面是效果图

完整 Demo 可以参看 MyNetworkStatesApplication

猜你喜欢

转载自blog.csdn.net/qq_33404903/article/details/87934424