SnackBar源码分析(来自design包)

来源:互联网 发布:婚礼入场音乐知乎 编辑:程序博客网 时间:2024/05/01 20:14

一.简介.

 SnackBar的提出实际上是界于Toast和Dialog的中间产物。

 Toast: 用户无法交互;
 Dialog:用户可以交互,但是体验会打折扣,会阻断用户的连贯性操作;

 Snackbar既可以做到轻量级的用户提醒效果,又可以有交互的功能(是一种非必须的操作)

二.SnackBar 源码分析.

/**     * 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 like     * {@link FloatingActionButton}.     *     * @param view     The view to find a parent from.     * @param text     The text to show.  Can be formatted text.     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or {@link     *                 #LENGTH_LONG}     */    @NonNull    public static Snackbar make(@NonNull View view, @NonNull CharSequence text,            @Duration int duration) {        Snackbar snackbar = new Snackbar(findSuitableParent(view));        snackbar.setText(text);        snackbar.setDuration(duration);        return snackbar;    }
这里就是说 SnackBar 会根据我们传入的View作为锚点一层一层的寻找特定View作为SnackBar的父容器 直到找到CoordinatorLayout或者the window decor's content view,做为SnackBar的父容器,代码如下:

 private static ViewGroup findSuitableParent(View view) {        ViewGroup fallback = null;        do {            if (view instanceof CoordinatorLayout) {                // We've found a CoordinatorLayout, use it                return (ViewGroup) view;            } else if (view instanceof FrameLayout) {                if (view.getId() == android.R.id.content) {                    // If we've hit the decor content view, then we didn't find a CoL in the                    // hierarchy, so use it.                    return (ViewGroup) view;                } else {                    // It's not the content view but we'll use it as our fallback                    fallback = (ViewGroup) view;                }            }            if (view != null) {                // Else, we will loop and crawl up the view hierarchy and try to find a parent                final ViewParent parent = view.getParent();                view = parent instanceof View ? (View) parent : null;            }        } while (view != null);        // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback        return fallback;    }
 为SnackBar找到父容器以后创建布局:

private Snackbar(ViewGroup parent) {mParent = parent;mContext = parent.getContext();LayoutInflater inflater = LayoutInflater.from(mContext);mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false); }
这里R.layout.design_layout_snackbar该布局文件应该就是我们SnackBar的布局文件,我们可以在design包的Layout文件夹中找到:

<view xmlns:android="http://schemas.android.com/apk/res/android"      class="android.support.design.widget.Snackbar$SnackbarLayout"      android:layout_width="match_parent"      android:layout_height="wrap_content"      android:layout_gravity="bottom"      style="@style/Widget.Design.Snackbar" />
但是发现并不是,继续找,一看就知道 这个布局是我们SnackBar的一个内部类,我们找到这个内部类,这里面我们真正的找到了我们布局文件:

public SnackbarLayout(Context context, AttributeSet attrs) {            super(context, attrs);        // Now inflate our content. We need to do this manually rather than using an <include>    // in the layout since older versions of the Android do not inflate includes with    // the correct Context.    LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);    }
查看R.layout.design_layout_snackbar_include


 <merge xmlns:android="http://schemas.android.com/apk/res/android">    <TextView            android:id="@+id/snackbar_text"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_weight="1"            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:textAppearance="@style/TextAppearance.Design.Snackbar.Message"            android:maxLines="@integer/design_snackbar_text_max_lines"            android:layout_gravity="center_vertical|left|start"            android:ellipsize="end"            android:textAlignment="viewStart"/>    <Button            android:id="@+id/snackbar_action"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"            android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"            android:layout_gravity="center_vertical|right|end"            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:visibility="gone"            android:textColor="?attr/colorAccent"            style="?attr/borderlessButtonStyle"/> </merge>
这里面用了merge标签减少视图层级(这里涉及到merge、include、ViewStub的区别大家可以看Google的文档或者百度一大堆)如果我们要改变我们SnackBar text的颜色 你就可以在你的主题中重写该主题:
 <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance">        <item name="android:textSize">@dimen/design_snackbar_text_size</item>        <item name="android:textColor">?android:textColorPrimary</item> </style>
 最后mParent.addView(mView);将SnackBar的view添加到父容器中:

final void showView() {    //...................   mParent.addView(mView);   // 动画的加载   if (ViewCompat.isLaidOut(mView)) {            // If the view is already laid out, animate it now            animateViewIn();        } else {            // Otherwise, add one of our layout change listeners and animate it in when laid out            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {                @Override                public void onLayoutChange(View view, int left, int top, int right, int bottom) {                    animateViewIn();                    mView.setOnLayoutChangeListener(null);                }            });        } }
 哪里调用了这个方法呢我们一步一步的看:

 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(message.arg1);                        return true;                }                return false;            }        });
 再继续:
private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {        @Override        public void show() {            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));        }        @Override        public void dismiss(int event) {            sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));        }    };
这里结合SnackBar.show()方法;
 public void show() {        SnackbarManager.getInstance().show(mDuration, mManagerCallback);    }
看到这里我们可以猜想我们SnackManger作为我们的SackBar控制器通过SnackbarManager.Callback()进行通信,控制SnackBar的show 和 dismiss
 那我们看看SnackBarManager里做了什么事情?

我们直接看SnackBarManger的show方法:

 public void show(int duration, Callback callback) {        synchronized (mLock) {            if (isCurrentSnackbar(callback)) {                // Means that the callback is already in the queue. We'll just update the duration                mCurrentSnackbar.duration = duration;                // If this is the Snackbar currently being shown, call re-schedule it's                // timeout                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);                scheduleTimeoutLocked(mCurrentSnackbar);                return;            } else if (isNextSnackbar(callback)) {                // We'll just update the duration                mNextSnackbar.duration = duration;            } else {                // Else, we need to create a new record and queue it                mNextSnackbar = new SnackbarRecord(duration, callback);            }            if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,                    Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {                // If we currently have a Snackbar, try and cancel it and wait in line                return;            } else {                // Clear out the current snackbar                mCurrentSnackbar = null;                // Otherwise, just show it now                showNextSnackbarLocked();            }        }    }

 这里差不多就是Manger的心脏了,一些复杂的逻辑处理,比如你的SnackBar正在显示 显示其他 或者再次显示自己的处理 及更新,
 我们传进来的cSnackBarManger.Callback 保存进了SnackbarRecord中,这个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;        }    }
看到这里我们应该对SnackBarManger有了解 就是对我们的SnackBar.Callback做了管理保存在了SnackbarRecord,做了一个管理和控制
到此我们就分析完了,相信大家也大概了解了SnackBar的原理了。










1 0
原创粉丝点击