SnackBar源码分析(来自design包)

一.简介.

 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值