五、自定义SnackBar以及封装

作者: 爱吃土豆的土豆 | 来源:发表于2018-11-22 21:45 被阅读29次

  咳咳,好久不见,因为土豆某天突发奇想,觉得作为一只猿,还是应该有自己的爱好,所以就去买了一把吉他,然后开始了长达三个多月的扰民生活,然后就没空写文章啦。。。扯远了,上一次说这章原本应该是讲popupWindow的,我是想用使用popupWindow选取相册以及调用摄像头来做案例的,不过安卓6.0之后,为了让用户的手机更加安全,所以将一些比较重要的权限比如开启摄像头,需要动态获得权限,后来土豆觉得如何优雅的动态获取权限也是比较重要的,所以只能先放着。今天本章就讲讲snackBar吧
  废话不多说,先看图吧,效果图展示的是系统样式的snackBar以及我们自己封装的snackBar


snackBar.gif

  想必大家都是知道toast的,这里我没有使用原生的toast,而是用的toasty。(土豆前面有讲哦,感兴趣的可以看看,不感兴趣的也不用管,不影响功能实现)今天的主角SnackBar是谷歌取代推出来取代toast的一个组件,为了不让toast伤心,土豆都没有在这个案例中使用toast,暖心吧。。
  那么这时候有好事者又要问了,土豆土豆,那snackBar有什么优点呢,我觉得toast很好用的啊。要说snackBar的优点的话,我们要先讲讲toast的缺陷:
第一:toast会重复显示,比如这样一个场景,我们点击一个控件,会弹出一个toast,但是如果一直点,不断的点,会发现,toast的提示会显示很久很久很久,这是因为系统将toast放进一个队列中,前一个toast关闭后,后一个toast才显示。网上也有很多解决这个问题的办法,大多是采用单例来实现,土豆后面也会讲。那么作为竞争对手的snackBar,自然抓住了toast的痛处,不会犯这样的错误啦,snackBar在重复点击时,会立即关闭上一个snackBar,显示新的snackBar,这样就提升了用户体验
第二:toast作为提示的组件,是不能与用户进行交互的,就像一个大喇叭,只会把消息传递给你,至于你有啥反应,它通通不管,而snackBar呢,就像一个知心大姐姐,不仅把消息传递给你,还可以接收你的反馈,并作出反应。举个小例子,我们使用recyclerView的时候,在没数据的时候需要通知用户,这次请求没有数据,toast只会原封不动的告诉用户没有数据,但是使用snackBar的话,可以增加一个“重试”的按钮,这样一来,用户不仅知道了这条消息,还可以与这条消息进行交互,好感度upupup
第三:颜值高,Em。。颜值高,存在感就高,存在感高,用户就高兴。。。
  安利了这么多,该咋用呢,相信有心急的同学已经迫不及待了,那么久正式进入snackBar的使用吧。
  第一步:snackBar是support design下的一个组件,我们自然要先导入这个啦,在app级别的gradle中导入它

 compile 'com.android.support:design:26.1.0'

  值得注意的是,26.1.0是土豆当前用的sdk,你的不一样也是正常的
  第二步:导入成功之后,就是开始使用啦,首先讲讲系统的snackbar

Snackbar.make(toolbar, "这是系统的snackBar", Snackbar.LENGTH_INDEFINITE)
                .setAction("你瞅啥", new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        ToastMessage.toastWarn("瞅你咋地", true);
                    }
                })
                .show();

  看到这个结构,是不是想起了这个

Toast.makeText(UseSnackBarActivity.this,"测试的toast",Toast.LENGTH_SHORT)
.show();

  没错,几乎一样的结构,不得不说snackBar可谓是把取其精华,去其槽粕理解的很透彻,这样也方便我们的学习啦,我们依次来说说这些属性的作用:
第一个参数是一个view,以这个view作为父布局,从而控制snackBar显示的位置,土豆这里是以我的toorBar控件作父布局的
第二个参数自然是snackBar要显示的内容啦
第三个属性是snackBar显示的时间,分别有LENGTH_SHORT,LENGTH_LONG和LENGTH_INDEFINITE,前两个属性和toast一致,后一个是表示一直在这页面显示的
第四个是setAction,这就是snackBar的交互特性啦,通过这个可以设置当用户点击后的事件,onClick中的 ToastMessage.toastWarn("瞅你咋地", true);则是土豆自己的工具类啦,有兴趣的可以看看土豆前面写的第二章哦
  可以看到,使用snackBar也是超级方便的,但是有个问题,系统默认的snackBar是黑色的样式,这样很容易与我们的APP样式冲突,因为,我们需要自己来封装一下snackBar啦
  在实际项目中,我们的activity基本都是直接继承我们的基类BaseActivity的,因此,将snackBar封装在BaseActivity中,方便又实在。至于如何封装BaseActivity,我上一篇有说过一点,但是对我们本章影响不大,此处不提,文章最后也会把我目前为止封装的BaseActivity贴出来,仅供参考,下面是关于snackBar部分的代码

/**
     * 展示snackBar
     *
     * @param view                view
     * @param msg                 消息
     * @param isDismiss           是否自动消失
     * @param action              事件名
     * @param iSnackBarClickEvent 事件处理接口
     */
    protected void showSnackBar(@NonNull View view, @NonNull String msg, boolean isDismiss, String action, final ISnackBarClickEvent iSnackBarClickEvent) {
        //snackBar默认显示时间为LENGTH_LONG
        int duringTime = Snackbar.LENGTH_LONG;
        if (isDismiss) {
            duringTime = Snackbar.LENGTH_LONG;
        } else {
            duringTime = Snackbar.LENGTH_INDEFINITE;
        }
        Snackbar snackbar;
        snackbar = Snackbar.make(view, msg, duringTime)
                .setAction(action, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        //以接口方式发送出去,便于使用者处理自己的业务逻辑
                        iSnackBarClickEvent.clickEvent();
                    }
                });
        //设置snackBar和toorBar颜色一致
        snackbar.getView().setBackgroundColor(getResources().getColor(R.color.toolbar_color));
        //设置action文字的颜色
        snackbar.setActionTextColor(getResources().getColor(R.color.normal_white));
        //设置snackBar图标 这里是获取到snackBar的textView 然后给textView增加左边图标的方式来实现的
        View snackBarView = snackbar.getView();
        TextView textView = (TextView) snackBarView.findViewById(R.id.snackbar_text);
        Drawable drawable = getResources().getDrawable(R.drawable.icon_vector_notification);//图片自己选择
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        textView.setCompoundDrawables(drawable, null, null, null);
        //增加文字和图标的距离
        textView.setCompoundDrawablePadding(20);
        //展示snackBar
        snackbar.show();
    }

    /**
     * snackBar的action事件
     */
    public interface ISnackBarClickEvent {
        void clickEvent();
    }

  注释也写的很详细了,我就不再每行代码进行解释,但是值得注意的是,我们给snackbar开头增加了一个图标,采用的是给textView增加内部图标一样。另外,我们还以内部接口的形式将snackBar的action事件派发出去,这样,任何一个activity都可以通过接口实现各自的业务逻辑了,接下来给出本章案例的代码
布局文件代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.administrator.potato.activity.UseSnackBarActivity">
    <include layout="@layout/app_toolbar"/>
    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinatorLayout"
        android:layout_marginTop="36dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/buttonNormal"
            android:text="使用系统snackBar"
            android:layout_width="match_parent"
            android:layout_height="50dp" />

    </android.support.design.widget.CoordinatorLayout>
    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinatorCustom"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/buttonCustom"
            android:text="使用封装的snackbar"
            android:layout_width="match_parent"
            android:layout_height="50dp" />
    </android.support.design.widget.CoordinatorLayout>

</LinearLayout>

相应的activity代码:

package com.example.administrator.potato.activity;

import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.Toolbar;
import android.view.View;

import com.example.administrator.potato.R;
import com.example.administrator.potato.utils.ToastMessage;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class UseSnackBarActivity extends BaseActivity {

    @Bind(R.id.toolbar)
    Toolbar toolbar;
    @Bind(R.id.coordinatorLayout)
    CoordinatorLayout coordinatorLayout;
    @Bind(R.id.coordinatorCustom)
    CoordinatorLayout coordinatorCustom;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_use_snack_bar);
        ButterKnife.bind(this);
        initView();
        initData();
    }

    @Override
    protected void initView() {
        initToolBar(toolbar, "使用SnackBar并封装", true, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
    }

    @Override
    protected void initData() {

    }

    @OnClick(R.id.buttonNormal)
    public void onViewClicked() {
        Snackbar.make(toolbar, "这是系统的snackBar", Snackbar.LENGTH_INDEFINITE)
                .setAction("你瞅啥", new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        ToastMessage.toastWarn("瞅你咋地", true);
                    }
                })
                .show();
    }

    @OnClick(R.id.buttonCustom)
    public void onButtonCustomClicked() {
        showSnackBar(coordinatorCustom, "这是我们自己封装的snackBar", false, "再瞅一个试试", new ISnackBarClickEvent() {
            @Override
            public void clickEvent() {
                ToastMessage.toastWarn("试试就试试", true);
            }
        });
    }
}

最后是BaseActivity代码,由于我平时将所有的案例都写在一个项目中,所以你会看到很多与snackBar无关的代码(才不是为了凑字数呢),不过没关系,这些东西我以后也会讲的,封装BaseActivity的作用在于方便日常开发,所以大家平时也可以将自己觉得有用的代码封装进去,避免写重复代码,写程序,如果不是为了偷懒,那将毫无意义,嘿嘿嘿

package com.example.administrator.potato.activity;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.MenuRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;

import com.example.administrator.potato.application.MyApplication;
import com.example.administrator.potato.interfaces.ConfirmDialogInterface;
import com.example.administrator.potato.R;
import com.lzy.okgo.OkGo;

public abstract class BaseActivity extends AppCompatActivity {
    //上下文对象
    protected Context mContext;
    //耗时操作的进度提示
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //获取上下文对象
        mContext = this;
        initStateBar();
        initProgressDialog();
    }

    @Override
    protected void onDestroy() {
        //统一关闭OK HTTP请求
        OkGo.getInstance().cancelTag(this);
        super.onDestroy();
    }

    //初始化ProgressDialog
    private void initProgressDialog() {
        progressDialog = new ProgressDialog(mContext);
        //无标题
        progressDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        //设置手指点击dialog不消失
        progressDialog.setCanceledOnTouchOutside(false);
        //设置dialog样式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    }

    /**
     * 设置状态栏的颜色与toolbar一致 只有安卓5.0以上才能用
     */
    private void initStateBar() {
        Window window = getWindow();
        //设置状态栏为透明
        //5.0以上使用原生方法
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            //设置状态栏的颜色与toolBar的颜色一致
            window.setStatusBarColor(getResources().getColor(R.color.toolbar_color));
        }
    }

    /**
     * 初始视图
     */
    protected abstract void initView();

    /**
     * 初始数据
     */
    protected abstract void initData();

    /**
     * 显示确认框形式的dialog
     *
     * @param title                  dialog的标题
     * @param msg                    dialog的消息
     * @param confirmDialogInterface 监听dialog确认键以及取消键的点击事件
     */
    protected void showConfirmDialog(@Nullable String title, @Nullable String msg, @NonNull final ConfirmDialogInterface confirmDialogInterface) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        //加载布局
        View view = View.inflate(mContext, R.layout.dialog_confirm, null);
        //获取组件实例
        TextView textTitle = view.findViewById(R.id.textTitle);
        TextView textContent = view.findViewById(R.id.textContent);
        TextView textConfirm = view.findViewById(R.id.textConfirm);
        TextView textCancel = view.findViewById(R.id.textCancel);
        //设置标题
        textTitle.setText(title);
        //设置消息内容
        textContent.setText(msg);
        //设置需要显示的view
        builder.setView(view);
        //赋值给其父类以获取dismiss方法
        final AlertDialog alertDialog = builder.create();
        //显示dialog
        alertDialog.show();
        //设置确定按钮内容
        textConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //确认键业务逻辑处理接口
                confirmDialogInterface.onConfirmClickListener();
                //业务逻辑处理完毕使dialog消失
                alertDialog.dismiss();
            }
        });
        //设置取消按钮内容
        textCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //取消键业务逻辑处理接口
                confirmDialogInterface.onCancelClickListener();
                //业务逻辑处理完毕使dialog消失
                alertDialog.dismiss();
            }
        });

    }

    protected void showProgressDialog(@NonNull String msg) {

        if (progressDialog != null && !progressDialog.isShowing()) {
            progressDialog.setMessage(msg);
            progressDialog.show();
        }
    }

    protected void closeProgressDialog() {

        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    /**
     * 初始化toolBar 完整版 左右两边都需要设置
     *
     * @param toolbar                   toolbar
     * @param title                     toolbar标题
     * @param isShowLeft                是否显示左边图标
     * @param navigationOnClickListener 左边图标监听事件
     * @param isShowRight               是否显示右边menu
     * @param menuId                    menu的id
     * @param onMenuItemClickListener   menu的子项监听事件
     */
    protected void initToolBar(android.support.v7.widget.Toolbar toolbar, @NonNull String title, Boolean isShowLeft, View.OnClickListener navigationOnClickListener, Boolean isShowRight
            , @MenuRes Integer menuId, android.support.v7.widget.Toolbar.OnMenuItemClickListener onMenuItemClickListener) {
        //使用toolbar替代actionBar
        //setSupportActionBar(toolbar); 这行代码删除后 toolbar.inflateMenu才能生效
        toolbar.setTitle(title);
        //设置左边icon
        if (isShowLeft) {
            toolbar.setNavigationIcon(R.drawable.icon_vector_back);
            if (navigationOnClickListener != null) {
                toolbar.setNavigationOnClickListener(navigationOnClickListener);
            }
        }
        //设置右边menu
        if (isShowRight) {
            if (onMenuItemClickListener != null) {
                toolbar.setOnMenuItemClickListener(onMenuItemClickListener);
            }
            if (menuId != null) {
                //如果要使用toolbar.inflateMenu 则不能使用setSupportActionBar
                toolbar.inflateMenu(menuId);
            } else {
                //不传菜单文件时使用默认的menu
                toolbar.inflateMenu(R.menu.app_toolbar_menu);
            }
        }
    }

    /**
     * 初始toolbar 简略版 因为大多数的活动是不需要设置右边的 所以可以简化操作
     *
     * @param toolbar                   toolBar
     * @param title                     toolBar的标题
     * @param isShowLeft                是否显示左边图标
     * @param navigationOnClickListener 左边图标的点击事件
     */
    protected void initToolBar(android.support.v7.widget.Toolbar toolbar, @NonNull String title, Boolean isShowLeft, View.OnClickListener navigationOnClickListener) {
        //使用toolbar替代actionBar
        setSupportActionBar(toolbar);
        toolbar.setTitle(title);
        //设置左边icon
        if (isShowLeft) {
            toolbar.setNavigationIcon(R.drawable.icon_vector_back);
            if (navigationOnClickListener != null) {
                toolbar.setNavigationOnClickListener(navigationOnClickListener);
            }
        }
    }

    /**
     * 活动跳转
     *
     * @param clazz 要跳转的活动
     */
    protected void gotoActivity(Class<?> clazz) {
        Intent intent = new Intent(MyApplication.getContext(), clazz);
        startActivity(intent);
    }

    /**
     * 活动跳转
     *
     * @param clazz  目标跳转活动
     * @param bundle 参数
     */
    protected void gotoActivity(Class<?> clazz, Bundle bundle) {
        Intent intent = new Intent(MyApplication.getContext(), clazz);
        if (bundle != null) {
            intent.putExtras(bundle);
        }
        startActivity(intent);
    }

    /**
     * 活动跳转
     *
     * @param clazz  目标跳转活动
     * @param bundle 参数
     * @param action action
     */
    protected void gotoActivity(Class<?> clazz, Bundle bundle, String action) {
        Intent intent = new Intent(MyApplication.getContext(), clazz);
        if (bundle != null) {
            intent.putExtras(bundle);
        }
        if (!TextUtils.isEmpty(action)) {
            intent.setAction(action);
        }
        startActivity(intent);
    }

    /**
     * 展示snackBar
     *
     * @param view                view
     * @param msg                 消息
     * @param isDismiss           是否自动消失
     * @param action              事件名
     * @param iSnackBarClickEvent 事件处理接口
     */
    protected void showSnackBar(@NonNull View view, @NonNull String msg, boolean isDismiss, String action, final ISnackBarClickEvent iSnackBarClickEvent) {
        //snackBar默认显示时间为LENGTH_LONG
        int duringTime = Snackbar.LENGTH_LONG;
        if (isDismiss) {
            duringTime = Snackbar.LENGTH_LONG;
        } else {
            duringTime = Snackbar.LENGTH_INDEFINITE;
        }
        Snackbar snackbar;
        snackbar = Snackbar.make(view, msg, duringTime)
                .setAction(action, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        //以接口方式发送出去,便于使用者处理自己的业务逻辑
                        iSnackBarClickEvent.clickEvent();
                    }
                });
        //设置snackBar和titleBar颜色一致
        snackbar.getView().setBackgroundColor(getResources().getColor(R.color.toolbar_color));
        //设置action文字的颜色
        snackbar.setActionTextColor(getResources().getColor(R.color.normal_white));
        //设置snackBar图标 这里是获取到snackBar的textView 然后给textView增加左边图标的方式来实现的
        View snackBarView = snackbar.getView();
        TextView textView = (TextView) snackBarView.findViewById(R.id.snackbar_text);
        Drawable drawable = getResources().getDrawable(R.drawable.icon_vector_notification);//图片自己选择
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        textView.setCompoundDrawables(drawable, null, null, null);
        //增加文字和图标的距离
        textView.setCompoundDrawablePadding(20);
        //展示snackBar
        snackbar.show();
    }

    /**
     * snackBar的action事件
     */
    public interface ISnackBarClickEvent {
        void clickEvent();
    }
}

  那么本章内容也讲得差不多了,接下来进行一下总结
一、有喜新厌旧的同学就会问了,snackBar这么方便,那么是不是可以抛弃Toast了呢,其实不然,所谓存在即合理,虽然Toast有一些缺点,但是它依旧可以传达一些不那么重要的消息,两者配合使用,这样可以使程序与用户进行交互时,方式变得多样化,毕竟天天大鱼大肉,也会腻的撒
二、刚刚忘讲了,snackBar还有一个特性是可以滑动删除,相信大家在我的布局文件中看到了这个

 <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinatorLayout"
        android:layout_marginTop="36dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/buttonNormal"
            android:text="使用系统snackBar"
            android:layout_width="match_parent"
            android:layout_height="50dp" />

    </android.support.design.widget.CoordinatorLayout>

CoordinatorLayout的作用是什么呢,我只能告诉你的是,snackBar+CoordinatorLayout=可以滑动删除的snackBar,这个暂时不讲
3、今天发现平时后排睡觉的同学竟然全部跑光了,估计以为土豆要饭去了
4、下一章讲讲进程通信神器,EventBus吧,下次见,白白~

相关文章

网友评论

    本文标题:五、自定义SnackBar以及封装

    本文链接:https://www.haomeiwen.com/subject/qtmhqqtx.html