Snackbar新版Toast 从源码角度完全解析

我们将会从一下几个角度了解Snackbar。

1.什么是Snackbar

2.如何使用Snackbar

3.有哪些常用的API

4.从源码角度分析其实现

5.总结

1.什么是Snackbar

Snackbar是一个轻量级的用户操作反馈工具,类似于Toast,但是比Toast更美观和实用,当你操作它的时候,Snackbar会置顶显示一个一定时间的简要的信息在屏幕的底部,并且它还可以添加事件。

2.如何使用Snackbar

    Snackbar
          .make(parentLayout, R.string.snackbar_text,
                                        Snackbar.LENGTH_LONG)
           .setAction(R.string.snackbar_action, myOnClickListener)
                  .show(); // Don’t forget to show!
这是官方给的一句话,是不是很简单。当然 你需要使用  Android Support Library 22.2以上的版本。

3.有哪些常用的API

由于它很简单,官方也只暴露出了几个常用的方法。我们来看一下

dismiss() 不解释

getDuration() 不解释

getView() 返回Snackbar的布局

make(View view, int resId, int duration) 这里的view代 、

表 Snackbar的父view,因为它要找一个依赖

make(View view, CharSequence text, int duration) 不解释

setAction(int resId, View.OnClickListener listener) 设置动作

setAction(CharSequence text, View.OnClickListener listener)

setActionTextColor(ColorStateList colors) 设置文本的颜色

setActionTextColor(int color) 设置动作文本的颜色

setDuration(int duration) 设置时间段

setText(int resId)

setText(CharSequence message)

show()

就这几个方法

4.从源码角度分析其实现

首先我们看Snakebar类的相关方法


static {
        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                switch (message.what) {
                    case MSG_SHOW:
                        ((Snackbar) message.obj).showView();
                        return true;
                    case MSG_DISMISS:
                        ((Snackbar) message.obj).hideView();
                        return true;
                }
                return false;
            }
        });
    }
//首先设置了一个handler

Snackbar(ViewGroup parent) {
        mParent = parent;
        mContext = parent.getContext();

        LayoutInflater inflater = LayoutInflater.from(mContext);
        mView = (SnackbarLayout) inflater.inflate(R.layout.layout_snackbar, mParent, false);
    }
    //在构造方法中获得了父控件,获得了context,初始化自己的view,由于它的构造函数是protected的,大家懂的。

//我们来看看最主要的方法make,在看make方法之前我们来看看findSuitableParent这个make方法里主要的功能。
@Nullable
    private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) { //判断是不是CoordinatorLayout,如果是直接用,CoordinatorLayout也是22.2包下的新的layout
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) { //如果是FrameLayout,则进入else if
                if (view.getId() == android.R.id.content) { //如果id == android.R.id.content 那么直接拿来用android.R.id.content代表根视图,至于根视图为什么是FrameLayout,我就不说了
        return (ViewGroup) view;
                } else {                            //是FrameLayout但不是根视图的话,先添加进callback再说
                    fallback = (ViewGroup) view;
                }
            }
            if (view != null) {
                final ViewParent parent = view.getParent();         //获得view的父控件,并循环执行,直到找到FrameLayout,或者找到CoordinatorLayout
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);
        return fallback;
    }
//排版有点乱,大家将就看,其实就是循环找到FrameLayout或者CoordinatorLayout才罢休

//好了,我们再看make方法。
      public static Snackbar make(View view, CharSequence text, @Duration int duration) {
        Snackbar snackbar = new Snackbar(findSuitableParent(view)); //初始化一个Snackbar,并传入父控件
        snackbar.setText(text);  //做一些初始化操作
        snackbar.setDuration(duration);
        return snackbar; //返回过去
    }

//我们再来看看setAction方法,没啥可说的。这里采用的是链式编程。
    public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
        final TextView tv = mView.getActionView();

        if (TextUtils.isEmpty(text) || listener == null) {
            tv.setVisibility(View.GONE);
            tv.setOnClickListener(null);
        } else {
            tv.setVisibility(View.VISIBLE);
            tv.setText(text);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listener.onClick(view);
                    dismiss();
                }
            });
        }
        return this;
    }
    //Snackbar内部自定义了一个继承自LinearLayout的SnackbarLayout用于显示自己的view,代码也不是很复杂,这想看的可以看看,毕竟自定义控件还是很重要的一项技能,这里就不说了。

//再来看看show方法
     public void show() { //调用了SnackbarManager来显示了,那我们就去SnackbarManager看看
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }

//首先看看SnackbarManager的类结构图
![image](http://img.blog.csdn.net/20150608221707832)
//我们通过看SnackbarManager类结构可以看出,SnackbarManager主要的工作是显示和隐藏Snackbar,我们主要看show和dismiss方法

 public void show(int duration, Callback callback) {
    synchronized (mLock) { //同步线程锁,一次只能有一个对象进来。
         if (isCurrentSnackbar(callback)) {
         //判断是不是当前的callback,如果是那么说明这个cakllback已经在队列里了,那么只需要
         mCurrentSnackbar.duration = duration;
         mHandler.removeCallbacksAndMesssage(mCurrentSnackbar);
             //如果正在显示,则调用handler重新显示一遍removeCallbacksAndMessages的参数如果为null则表示移除所有的Callbacks和Messages,在activityondestory执行能有效的避免内存泄露
         scheduleTimeoutLocked(mCurrentSnackbar);
         return;
       } else if (isNextSnackbar(callback)) {
       //如果是下一个SnackbarRecord则直接设置时间,handler会自动调用, 这里就用到了handler looper  mq的概念
         mNextSnackbar.duration = duration;
       } else {//如果都不是则重新创建一个SnackbarRecord,    SnackbarRecord  我们等会再说
        mNextSnackbar = new SnackbarRecord(duration, callback);
     }

    if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar)) {
    //如果mCurrentSnackbar不为空,试图去取消它。
    return;
    } else {
                mCurrentSnackbar = null; //清空mCurrentSnackbar
                showNextSnackbarLocked(); //然后直接显示,showNextSnackbarLocked该方法会先把mNextSnackbar赋值给mCurrentSnackbar,然后让mCurrentSnackbar显示
            }
  } 

    //总结一下这个show方法,首先会判断mCurrentSnackbar是不是空,如果不是,并且它的callback和之前的不一致,则直接显示(会先把之前的隐藏再显示现在的); 如果是空或者说并且它的callback和之前的不一致,则判断NextSnackbar是不是空,如果不是空,并且它的callback和之前一致,则直接设置至,等待handler的取值;如果mNextSnackbar是空,或者说并且它的callback和之前的不一致则直接先干掉mCurrentSnackbar,然后给它重新赋值,逻辑还是有点绕的。   

//再来看看 dismiss
    public void dismiss(Callback callback) {
     //很容易就看懂了。不解释了
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                cancelSnackbarLocked(mCurrentSnackbar);
            }
            if (isNextSnackbar(callback)) {
                cancelSnackbarLocked(mNextSnackbar);
            }
        }
    }

    //而SnackbarRecord 类很简单,内部维持了一个若引用。
     private static class SnackbarRecord {
        private final WeakReference<Callback> callback;
        private int duration;

        SnackbarRecord(int duration, Callback callback) {
            this.callback = new WeakReference<>(callback);
            this.duration = duration;
        }

        boolean isSnackbar(Callback callback) {
            return callback != null && this.callback.get() == callback;
        }
    }

5.总结

好了,Snackbar整体就差不多了,总结一下,所有Snackbar的初始化工作都是在Snackbar类里执行的,而最后的show和dismiss都在snackbarManager里执行的,

而snackbarManager里维持了一个SnackbarRecord,SnackbarRecord内部拥有mCurrentSnackbar, mNextSnackbar两个回调,用于维持Snackbar的显示和隐藏

时间: 2024-12-05 20:33:14

Snackbar新版Toast 从源码角度完全解析的相关文章

从源码角度深入理解Toast

Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(this, "333", Toast.LENGTH_LONG).show(); 但是我们经常会遇到这样一种情况,比如说我们有两个按钮,每次点击之后就会弹出一个Toast,但是如果这两个按钮快速点击,我们看到的效果是这样的:   但实际上我们想要的效果应该是这样的:   源码解读 看了上面两张效果图的对比之后,我们应该明白了,第一种情况是在我们点击完之后,Toast还在不断的显示

从源码角度入手实现RecyclerView的Item点击事件

转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6758373.html RecyclerView 作为 ListView 和 GridView 的替代产物,相信在Android界已广为流传. RecyclerView 本是不会有类似 ListView 的那种点击事件,但是知道和会用又是两种情况,跟随我一起从源码角度分析,RecyclerView 点击事件. 首先看一下 Google 对 ListView 家族谱的介绍: 可以看出 ListView 归根

Android布局性能优化—从源码角度看ViewStub延迟加载技术

在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码中通过控制View.VISIABLE动态的更改它的可见性.这样的做法的优点是逻辑简单而且控制起来比较灵活.但是它的缺点就是,耗费资源,虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性.也就是说,会

从源码角度来修改PreferenceActivity界面

       从源码角度来修改PreferenceActivity界面,布布扣,bubuko.com

Android 高级自定义Toast及源码解析

Toast概述 Toast的作用 不需要和用户交互的提示框. 更多参见官网:https://developer.android.com/guide/topics/ui/notifiers/toasts.html Toast的简单使用 Toast.makeText(MainActivity.this.getApplicationContext(),"沉迷学习,日渐消瘦",Toast.LENGTH_SHORT).show() 自定义Toast Toast customToast = new

从源码角度分析linearLayout测量过程以及weight机制

???上文从源码角度分析了view和viewGroup的measure机制,如果还没有阅读的同志们,可以前往从源码角度分析Android View的绘制机制(一)阅读.下面我再结合linearLayout的measure过程解释以下两个问题的缘由. 问题一: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent

Android进阶:二、从源码角度看透 HandlerThread 和 IntentService

上篇文章我们讲日志的存储策略的时候用到了HandlerThread,它适合处理"多而小的任务"的耗时任务的时候,避免产生太多线程影响性能,那这个HandlerThread的原理到底是怎样的呢?我们现在从源码角度解读 HandlerThread:继承自Thread,是一个可以使用Handler的Thread.因为在run方法内维护了一个Looper,可以通过Handler发送消息的方式,来通知HandlerThread执行一个具体的任务. public void run() { mTid

从源码角度了解SpringMVC的执行流程

目录 从源码角度了解SpringMVC的执行流程 SpringMVC介绍 源码分析思路 源码解读 几个关键接口和类 前端控制器 DispatcherServlet 结语 从源码角度了解SpringMVC的执行流程 SpringMVC的执行流程网上有很多帖子都有讲解,流程图和文字描述都很详细,但是你如果没有通过具体源码自己走一遍流程,其实只是死记硬背.所以想开个帖子从源码角度再梳理一遍SpringMVC的执行流程,加深印象. SpringMVC介绍 SpringMVC采用的是前端控制器(Front

从源码角度看Android系统SystemServer进程启动过程

copy frome :https://blog.csdn.net/salmon_zhang/article/details/93208135 SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动过程以及SystemServer进程做了哪些重要工作. 1. SystemServer进程启动的起点从<从源码角度看Android系统Zygote进程启动过